UNPKG

@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
/* * 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. */ 'use strict'; 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;