@catbee/utils
Version:
A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.
333 lines (326 loc) • 11.3 kB
JavaScript
/*
* The MIT License
*
* Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
;
var pino = require('pino');
var config = require('@catbee/utils/config');
var contextStore = require('@catbee/utils/context-store');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var pino__default = /*#__PURE__*/_interopDefault(pino);
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var GLOBAL_LOGGER_KEY = Symbol.for("logger");
var defaultSensitiveFields = [
"password",
"secret",
"token",
"api_key",
"auth",
"private_key",
"public_key",
"jwt",
"access_token",
"refresh_token",
"session_token",
"csrf_token",
"authorization",
"bearer",
"x-api-key",
"x-auth-token",
"x-access-token",
"client_secret",
"passphrase",
"otp",
"api_secret",
"token_secret"
];
var defaultRedactPaths = [
"req.authorization",
"req.headers.authorization",
"req.headers.cookie",
'req.headers["x-api-key"]',
'req.headers["x-auth-token"]',
'req.headers["x-access-token"]',
"req.body.password",
"req.body.token",
"req.body.secret",
"req.query.token",
"req.query.api_key",
"req.query.apiKey",
"res.authorization",
"res.headers.authorization",
'res.headers["set-cookie"]',
"headers.authorization",
"headers.cookies",
'headers["set-cookie"]',
'headers["x-api-key"]',
'headers["x-auth-token"]',
'headers["x-access-token"]',
"url",
"uri",
"href",
"redirect_uri",
"redirectUri"
];
var globalRedactCensor = /* @__PURE__ */ __name((value, path, sensitiveFields = getExpandedSensitiveFields()) => {
if (typeof value !== "string") return "***";
const lowerPath = path.filter((p) => typeof p === "string").map((p) => p.toLowerCase());
const lowerSensitiveFields = sensitiveFields.map((f) => f.toLowerCase());
if (lowerPath.some((p) => lowerSensitiveFields.includes(p))) return "***";
if (lowerPath[0] === "url" || lowerPath[0] === "uri" || lowerPath[0] === "href" || lowerPath.includes("redirect_uri") || lowerPath.includes("redirecturi")) {
return value.replace(new RegExp(`([?&](${lowerSensitiveFields.join("|")})=)[^&#]*`, "gi"), "$1***");
}
if (lowerPath.some((p) => p.includes("authorization") || p.includes("auth"))) {
return value.replace(/^(\S+)\s+.+$/, "$1 ***") || "***";
}
if (lowerPath.some((p) => lowerSensitiveFields.some((f) => p.includes(f)))) {
return "***";
}
if (path.length > 0 && ![
"req",
"res",
"headers"
].includes(lowerPath[0])) {
return value;
}
return value;
}, "globalRedactCensor");
function setRedactCensor(fn) {
globalRedactCensor = fn;
}
__name(setRedactCensor, "setRedactCensor");
function getRedactCensor() {
return globalRedactCensor;
}
__name(getRedactCensor, "getRedactCensor");
function redact(value, path, sensitiveFields) {
return globalRedactCensor(value, path, sensitiveFields);
}
__name(redact, "redact");
function addRedactFields(fields) {
const prev = globalRedactCensor;
globalRedactCensor = /* @__PURE__ */ __name((value, path, sensitiveFields = getExpandedSensitiveFields()) => prev(value, path, [
...sensitiveFields || [],
...fields
]), "globalRedactCensor");
cachedExpandedFields = null;
}
__name(addRedactFields, "addRedactFields");
var cachedExpandedFields = null;
function getExpandedSensitiveFields() {
cachedExpandedFields ??= expandSensitiveFields(defaultSensitiveFields);
return cachedExpandedFields;
}
__name(getExpandedSensitiveFields, "getExpandedSensitiveFields");
function setSensitiveFields(fields) {
defaultSensitiveFields.splice(0, defaultSensitiveFields.length, ...fields);
cachedExpandedFields = null;
}
__name(setSensitiveFields, "setSensitiveFields");
function addSensitiveFields(fields) {
defaultSensitiveFields.push(...fields);
cachedExpandedFields = null;
}
__name(addSensitiveFields, "addSensitiveFields");
var _globalThis = typeof globalThis === "object" ? globalThis : global;
var _global = _globalThis;
function setupLogger(isGlobal = true) {
const sensitiveFields = getExpandedSensitiveFields();
const paths = /* @__PURE__ */ new Set([
...defaultRedactPaths,
...sensitiveFields.flatMap((field) => generateDeepPaths(field, 2))
]);
const logParams = {
name: config.getCatbeeGlobalConfig().logger?.name || "@catbee/utils",
level: config.getCatbeeGlobalConfig().logger?.level || "info",
redact: {
paths: Array.from(paths),
censor: /* @__PURE__ */ __name((value, path) => redact(value, path), "censor")
},
serializers: {
req: pino__default.default.stdSerializers.req,
request: pino__default.default.stdSerializers.req,
res: pino__default.default.stdSerializers.res,
response: pino__default.default.stdSerializers.res,
err: pino__default.default.stdSerializers.err,
error: pino__default.default.stdSerializers.err
},
timestamp: pino.stdTimeFunctions.isoTime
};
let logger;
const logDir = config.getCatbeeGlobalConfig().logger?.dir?.trim();
const hasFileLogging = Boolean(logDir);
if (hasFileLogging && config.getCatbeeGlobalConfig().logger?.pretty) {
logger = pino__default.default(logParams, pino__default.default.transport({
targets: [
{
target: "pino-pretty",
level: config.getCatbeeGlobalConfig().logger?.level ?? "info",
options: {
colorize: config.getCatbeeGlobalConfig().logger?.colorize,
translateTime: "SYS:standard",
ignore: "pid,hostname",
singleLine: config.getCatbeeGlobalConfig().logger?.singleLine,
levelFirst: true
}
},
{
target: "pino/file",
level: config.getCatbeeGlobalConfig().logger?.level ?? "info",
options: {
destination: `${logDir}/app.log`,
mkdir: true
}
}
]
}));
} else if (hasFileLogging) {
logger = pino__default.default(logParams, pino__default.default.transport({
target: "pino/file",
options: {
destination: `${logDir}/app.log`,
mkdir: true
}
}));
} else if (config.getCatbeeGlobalConfig().logger?.pretty) {
logger = pino__default.default(logParams, pino__default.default.transport({
target: "pino-pretty",
options: {
colorize: config.getCatbeeGlobalConfig().logger?.colorize,
translateTime: "SYS:standard",
ignore: "pid,hostname",
singleLine: config.getCatbeeGlobalConfig().logger?.singleLine,
levelFirst: true
}
}));
} else {
logger = pino__default.default(logParams);
}
if (isGlobal) {
_global[GLOBAL_LOGGER_KEY] = logger;
_global[GLOBAL_LOGGER_KEY]?.debug({
logDir: logDir || "none"
}, "Global Logger Initialized");
}
return logger;
}
__name(setupLogger, "setupLogger");
function getLogger(newInstance = false) {
if (newInstance) {
return setupLogger(false);
}
const logger = contextStore.ContextStore.get(contextStore.StoreKeys.LOGGER);
if (logger) return logger;
if (!_global[GLOBAL_LOGGER_KEY]) {
setupLogger();
}
return _global[GLOBAL_LOGGER_KEY];
}
__name(getLogger, "getLogger");
function createChildLogger(bindings, parentLogger) {
const logger = parentLogger || getLogger();
return logger.child(bindings);
}
__name(createChildLogger, "createChildLogger");
function createRequestLogger(requestId, additionalContext = {}) {
const logger = createChildLogger({
requestId,
...additionalContext
});
try {
contextStore.ContextStore.set(contextStore.StoreKeys.LOGGER, logger);
} catch {
logger.debug("Failed to store logger in context - AsyncLocalStorage not initialized");
}
return logger;
}
__name(createRequestLogger, "createRequestLogger");
function logError(error, message, context) {
const logger = getLogger();
const errObj = error instanceof Error ? error : new Error(String(error));
const logContext = {
...context,
error: errObj
};
logger.error(logContext, message || errObj.message);
}
__name(logError, "logError");
function expandSensitiveFields(fields) {
const set = /* @__PURE__ */ new Set();
for (const field of fields) {
for (const v of expandSensitiveField(field)) {
set.add(v);
}
}
return Array.from(set);
}
__name(expandSensitiveFields, "expandSensitiveFields");
function expandSensitiveField(field) {
const parts = field.split(/[_-]/g).filter(Boolean);
const camel = parts[0].toLowerCase() + parts.slice(1).map((p) => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()).join("");
const mergedLower = parts.join("").toLowerCase();
const mergedUpper = parts.join("").toUpperCase();
const kebab = parts.map((p) => p.toLowerCase()).join("-");
const snakeLower = parts.map((p) => p.toLowerCase()).join("_");
const snakeUpper = parts.map((p) => p.toUpperCase()).join("_");
return Array.from(/* @__PURE__ */ new Set([
field,
camel,
mergedLower,
mergedUpper,
kebab,
snakeLower,
snakeUpper
]));
}
__name(expandSensitiveField, "expandSensitiveField");
function generateDeepPaths(field, depth) {
const set = /* @__PURE__ */ new Set([
field
]);
let prefix = "";
for (let i = 1; i <= depth; i++) {
prefix = prefix ? `${prefix}.*` : "*";
set.add(`${prefix}.${field}`);
}
return [
...set
];
}
__name(generateDeepPaths, "generateDeepPaths");
exports._globalThis = _globalThis;
exports.addRedactFields = addRedactFields;
exports.addSensitiveFields = addSensitiveFields;
exports.createChildLogger = createChildLogger;
exports.createRequestLogger = createRequestLogger;
exports.defaultSensitiveFields = defaultSensitiveFields;
exports.expandSensitiveField = expandSensitiveField;
exports.expandSensitiveFields = expandSensitiveFields;
exports.generateDeepPaths = generateDeepPaths;
exports.getExpandedSensitiveFields = getExpandedSensitiveFields;
exports.getLogger = getLogger;
exports.getRedactCensor = getRedactCensor;
exports.logError = logError;
exports.redact = redact;
exports.setRedactCensor = setRedactCensor;
exports.setSensitiveFields = setSensitiveFields;