@catbee/utils 2.0.5 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/logger/index.cjs CHANGED
@@ -57,57 +57,140 @@ var defaultSensitiveFields = [
57
57
  "passphrase",
58
58
  "otp",
59
59
  "api_secret",
60
- "token_secret"
60
+ "token_secret",
61
+ "cookie",
62
+ "cookies",
63
+ "set-cookie",
64
+ "session_id",
65
+ "credit_card",
66
+ "card_number",
67
+ "cvv",
68
+ "cvc",
69
+ "ssn",
70
+ "pin"
61
71
  ];
62
- var defaultRedactPaths = [
63
- "req.authorization",
64
- "req.headers.authorization",
65
- "req.headers.cookie",
66
- 'req.headers["x-api-key"]',
67
- 'req.headers["x-auth-token"]',
68
- 'req.headers["x-access-token"]',
69
- "req.body.password",
70
- "req.body.token",
71
- "req.body.secret",
72
- "req.query.token",
73
- "req.query.api_key",
74
- "req.query.apiKey",
75
- "res.authorization",
76
- "res.headers.authorization",
77
- 'res.headers["set-cookie"]',
72
+ var requestSubPaths = [
73
+ "authorization",
78
74
  "headers.authorization",
75
+ "headers.cookie",
79
76
  "headers.cookies",
80
77
  'headers["set-cookie"]',
81
78
  'headers["x-api-key"]',
82
79
  'headers["x-auth-token"]',
83
80
  'headers["x-access-token"]',
81
+ "body.password",
82
+ "body.token",
83
+ "body.secret",
84
+ "query.token",
85
+ "query.api_key",
86
+ "query.apiKey",
87
+ "url",
88
+ "originalUrl"
89
+ ];
90
+ var responseSubPaths = [
91
+ "authorization",
92
+ "headers.authorization",
93
+ 'headers["set-cookie"]'
94
+ ];
95
+ var commonHeaderPaths = [
96
+ "headers.authorization",
97
+ "headers.cookie",
98
+ "headers.cookies",
99
+ 'headers["set-cookie"]',
100
+ 'headers["x-api-key"]',
101
+ 'headers["x-auth-token"]',
102
+ 'headers["x-access-token"]'
103
+ ];
104
+ var defaultRedactPaths = [
105
+ ...[
106
+ "req",
107
+ "request"
108
+ ].flatMap((p) => requestSubPaths.map((sp) => `${p}.${sp}`)),
109
+ ...[
110
+ "res",
111
+ "response"
112
+ ].flatMap((p) => responseSubPaths.map((sp) => `${p}.${sp}`)),
113
+ ...commonHeaderPaths,
84
114
  "url",
85
115
  "uri",
86
116
  "href",
87
117
  "redirect_uri",
88
118
  "redirectUri"
89
119
  ];
90
- var globalRedactCensor = /* @__PURE__ */ __name((value, path, sensitiveFields = getExpandedSensitiveFields()) => {
120
+ var escapeRegExp = /* @__PURE__ */ __name((value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "escapeRegExp");
121
+ function buildUrlRedactRegex(lowerSensitiveFields) {
122
+ if (lowerSensitiveFields.length === 0) return null;
123
+ const pattern = lowerSensitiveFields.map(escapeRegExp).join("|");
124
+ return new RegExp(`([?&](${pattern})=)[^&#]*`, "gi");
125
+ }
126
+ __name(buildUrlRedactRegex, "buildUrlRedactRegex");
127
+ var URL_PATH_KEYS = /* @__PURE__ */ new Set([
128
+ "url",
129
+ "uri",
130
+ "href",
131
+ "originalurl",
132
+ "redirect_uri",
133
+ "redirecturi"
134
+ ]);
135
+ var cachedExpandedFields = null;
136
+ var cachedLowerSensitiveFields = null;
137
+ var cachedLowerSensitiveSet = null;
138
+ var cachedNormalizedMultiWordFields = null;
139
+ var cachedUrlRegex = null;
140
+ function invalidateSensitiveFieldsCache() {
141
+ cachedExpandedFields = null;
142
+ cachedLowerSensitiveFields = null;
143
+ cachedLowerSensitiveSet = null;
144
+ cachedNormalizedMultiWordFields = null;
145
+ cachedUrlRegex = null;
146
+ }
147
+ __name(invalidateSensitiveFieldsCache, "invalidateSensitiveFieldsCache");
148
+ function extractNormalizedMultiWordFields(fields) {
149
+ return Array.from(new Set(fields.map((f) => f.replace(/[-]/g, "_")).filter((f) => f.includes("_")).map((f) => `_${f}_`)));
150
+ }
151
+ __name(extractNormalizedMultiWordFields, "extractNormalizedMultiWordFields");
152
+ function isSensitivePathSegment(segment, lowerFieldsSet, multiWordNormalizedList) {
153
+ if (!segment || typeof segment !== "string") return false;
154
+ const lower = segment.toLowerCase();
155
+ if (lowerFieldsSet.has(lower)) return true;
156
+ const tokens = segment.split(/[_-]+|(?<=[a-z0-9])(?=[A-Z])/g).filter(Boolean);
157
+ if (tokens.some((t) => lowerFieldsSet.has(t.toLowerCase()))) return true;
158
+ if (multiWordNormalizedList.length === 0) return false;
159
+ const normalized = `_${segment.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[-]/g, "_")}_`;
160
+ return multiWordNormalizedList.some((fNorm) => normalized.includes(fNorm));
161
+ }
162
+ __name(isSensitivePathSegment, "isSensitivePathSegment");
163
+ function getExpandedSensitiveFields() {
164
+ if (!cachedExpandedFields) {
165
+ cachedExpandedFields = expandSensitiveFields(defaultSensitiveFields);
166
+ cachedLowerSensitiveFields = cachedExpandedFields.map((f) => f.toLowerCase());
167
+ cachedLowerSensitiveSet = new Set(cachedLowerSensitiveFields);
168
+ cachedNormalizedMultiWordFields = extractNormalizedMultiWordFields(cachedLowerSensitiveFields);
169
+ cachedUrlRegex = buildUrlRedactRegex(cachedLowerSensitiveFields);
170
+ }
171
+ return cachedExpandedFields;
172
+ }
173
+ __name(getExpandedSensitiveFields, "getExpandedSensitiveFields");
174
+ var globalRedactCensor = /* @__PURE__ */ __name((value, path, sensitiveFields) => {
91
175
  if (typeof value !== "string") return "***";
92
- const lowerPath = path.filter((p) => typeof p === "string").map((p) => p.toLowerCase());
93
- const lowerSensitiveFields = sensitiveFields.map((f) => f.toLowerCase());
94
- if (lowerPath.some((p) => lowerSensitiveFields.includes(p))) return "***";
95
- if (lowerPath[0] === "url" || lowerPath[0] === "uri" || lowerPath[0] === "href" || lowerPath.includes("redirect_uri") || lowerPath.includes("redirecturi")) {
96
- return value.replace(new RegExp(`([?&](${lowerSensitiveFields.join("|")})=)[^&#]*`, "gi"), "$1***");
176
+ const stringPath = path.filter((p) => typeof p === "string");
177
+ const lowerPath = stringPath.map((p) => p.toLowerCase());
178
+ const isCustomFields = sensitiveFields !== void 0;
179
+ if (!isCustomFields) {
180
+ getExpandedSensitiveFields();
97
181
  }
98
- if (lowerPath.some((p) => p.includes("authorization") || p.includes("auth"))) {
99
- return value.replace(/^(\S+)\s+.+$/, "$1 ***") || "***";
182
+ const lowerFieldsList = isCustomFields ? sensitiveFields.map((f) => f.toLowerCase()) : cachedLowerSensitiveFields;
183
+ const lowerFieldsSet = isCustomFields ? new Set(lowerFieldsList) : cachedLowerSensitiveSet;
184
+ const multiWordList = isCustomFields ? extractNormalizedMultiWordFields(lowerFieldsList) : cachedNormalizedMultiWordFields;
185
+ if (lowerPath.some((p) => lowerFieldsSet.has(p))) return "***";
186
+ if (lowerPath.some((p) => URL_PATH_KEYS.has(p) || p.includes("redirect_uri") || p.includes("redirecturi"))) {
187
+ if (lowerFieldsList.length === 0) return value;
188
+ const urlRegex = isCustomFields ? buildUrlRedactRegex(lowerFieldsList) : cachedUrlRegex;
189
+ return urlRegex ? value.replace(urlRegex, "$1***") : value;
100
190
  }
101
- if (lowerPath.some((p) => lowerSensitiveFields.some((f) => p.includes(f)))) {
191
+ if (stringPath.some((p) => isSensitivePathSegment(p, lowerFieldsSet, multiWordList))) {
102
192
  return "***";
103
193
  }
104
- if (path.length > 0 && ![
105
- "req",
106
- "res",
107
- "headers"
108
- ].includes(lowerPath[0])) {
109
- return value;
110
- }
111
194
  return value;
112
195
  }, "globalRedactCensor");
113
196
  function setRedactCensor(fn) {
@@ -123,38 +206,36 @@ function redact(value, path, sensitiveFields) {
123
206
  }
124
207
  __name(redact, "redact");
125
208
  function addRedactFields(fields) {
209
+ addSensitiveFields(fields);
126
210
  const prev = globalRedactCensor;
127
211
  globalRedactCensor = /* @__PURE__ */ __name((value, path, sensitiveFields = getExpandedSensitiveFields()) => prev(value, path, [
128
212
  ...sensitiveFields || [],
129
213
  ...fields
130
214
  ]), "globalRedactCensor");
131
- cachedExpandedFields = null;
132
215
  }
133
216
  __name(addRedactFields, "addRedactFields");
134
- var cachedExpandedFields = null;
135
- function getExpandedSensitiveFields() {
136
- cachedExpandedFields ??= expandSensitiveFields(defaultSensitiveFields);
137
- return cachedExpandedFields;
138
- }
139
- __name(getExpandedSensitiveFields, "getExpandedSensitiveFields");
140
217
  function setSensitiveFields(fields) {
141
218
  defaultSensitiveFields.splice(0, defaultSensitiveFields.length, ...fields);
142
- cachedExpandedFields = null;
219
+ invalidateSensitiveFieldsCache();
143
220
  }
144
221
  __name(setSensitiveFields, "setSensitiveFields");
145
222
  function addSensitiveFields(fields) {
146
223
  defaultSensitiveFields.push(...fields);
147
- cachedExpandedFields = null;
224
+ invalidateSensitiveFieldsCache();
148
225
  }
149
226
  __name(addSensitiveFields, "addSensitiveFields");
150
227
  var _globalThis = typeof globalThis === "object" ? globalThis : global;
151
228
  var _global = _globalThis;
229
+ function resetLogger() {
230
+ delete _global[GLOBAL_LOGGER_KEY];
231
+ }
232
+ __name(resetLogger, "resetLogger");
152
233
  function setupLogger(isGlobal = true) {
153
234
  const sensitiveFields = getExpandedSensitiveFields();
154
235
  const { logger: loggerConfig } = config.getCatbeeGlobalConfig();
155
236
  const paths = /* @__PURE__ */ new Set([
156
237
  ...defaultRedactPaths,
157
- ...sensitiveFields.flatMap((field) => generateDeepPaths(field, 2))
238
+ ...sensitiveFields.flatMap((field) => generateDeepPaths(field, 5))
158
239
  ]);
159
240
  const logParams = {
160
241
  name: loggerConfig?.name ?? "@catbee/utils",
@@ -176,56 +257,50 @@ function setupLogger(isGlobal = true) {
176
257
  let logger;
177
258
  const logDir = loggerConfig?.dir?.trim();
178
259
  const hasFileLogging = Boolean(logDir);
260
+ const destination = logDir ? `${logDir.replace(/[/\\]+$/, "")}/app.log` : "app.log";
261
+ const prettyOptions = {
262
+ colorize: loggerConfig?.colorize,
263
+ translateTime: "SYS:standard",
264
+ ignore: "pid,hostname",
265
+ singleLine: loggerConfig?.singleLine,
266
+ levelFirst: true
267
+ };
268
+ const fileOptions = {
269
+ destination,
270
+ mkdir: true
271
+ };
179
272
  if (hasFileLogging && loggerConfig?.pretty) {
180
273
  logger = pino__default.default(logParams, pino__default.default.transport({
181
274
  targets: [
182
275
  {
183
276
  target: "pino-pretty",
184
277
  level: loggerConfig?.level ?? "info",
185
- options: {
186
- colorize: loggerConfig?.colorize,
187
- translateTime: "SYS:standard",
188
- ignore: "pid,hostname",
189
- singleLine: loggerConfig?.singleLine,
190
- levelFirst: true
191
- }
278
+ options: prettyOptions
192
279
  },
193
280
  {
194
281
  target: "pino/file",
195
282
  level: loggerConfig?.level ?? "info",
196
- options: {
197
- destination: `${logDir}/app.log`,
198
- mkdir: true
199
- }
283
+ options: fileOptions
200
284
  }
201
285
  ]
202
286
  }));
203
287
  } else if (hasFileLogging) {
204
288
  logger = pino__default.default(logParams, pino__default.default.transport({
205
289
  target: "pino/file",
206
- options: {
207
- destination: `${logDir}/app.log`,
208
- mkdir: true
209
- }
290
+ options: fileOptions
210
291
  }));
211
292
  } else if (loggerConfig?.pretty) {
212
293
  logger = pino__default.default(logParams, pino__default.default.transport({
213
294
  target: "pino-pretty",
214
- options: {
215
- colorize: loggerConfig?.colorize,
216
- translateTime: "SYS:standard",
217
- ignore: "pid,hostname",
218
- singleLine: loggerConfig?.singleLine,
219
- levelFirst: true
220
- }
295
+ options: prettyOptions
221
296
  }));
222
297
  } else {
223
298
  logger = pino__default.default(logParams);
224
299
  }
225
300
  if (isGlobal) {
226
301
  _global[GLOBAL_LOGGER_KEY] = logger;
227
- _global[GLOBAL_LOGGER_KEY]?.debug({
228
- logDir
302
+ logger.debug({
303
+ logDir: logDir || "none"
229
304
  }, "Global Logger Initialized");
230
305
  }
231
306
  return logger;
@@ -253,9 +328,15 @@ function createRequestLogger(requestId, additionalContext = {}) {
253
328
  requestId,
254
329
  ...additionalContext
255
330
  });
331
+ let stored = false;
256
332
  try {
257
- contextStore.ContextStore.set(contextStore.StoreKeys.LOGGER, logger);
333
+ if (typeof contextStore.ContextStore.getAll !== "function" || contextStore.ContextStore.getAll() !== void 0) {
334
+ contextStore.ContextStore.set(contextStore.StoreKeys.LOGGER, logger);
335
+ stored = true;
336
+ }
258
337
  } catch {
338
+ }
339
+ if (!stored) {
259
340
  logger.debug("Failed to store logger in context - AsyncLocalStorage not initialized");
260
341
  }
261
342
  return logger;
@@ -282,13 +363,29 @@ function expandSensitiveFields(fields) {
282
363
  }
283
364
  __name(expandSensitiveFields, "expandSensitiveFields");
284
365
  function expandSensitiveField(field) {
285
- const parts = field.split(/[_-]/g).filter(Boolean);
286
- const camel = parts[0].toLowerCase() + parts.slice(1).map((p) => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()).join("");
287
- const mergedLower = parts.join("").toLowerCase();
288
- const mergedUpper = parts.join("").toUpperCase();
289
- const kebab = parts.map((p) => p.toLowerCase()).join("-");
290
- const snakeLower = parts.map((p) => p.toLowerCase()).join("_");
291
- const snakeUpper = parts.map((p) => p.toUpperCase()).join("_");
366
+ if (!field || typeof field !== "string") {
367
+ return [];
368
+ }
369
+ const trimmed = field.trim();
370
+ if (!trimmed) {
371
+ return [
372
+ field
373
+ ];
374
+ }
375
+ const parts = trimmed.split(/[_-]+|(?<=[a-z0-9])(?=[A-Z])/g).filter(Boolean);
376
+ if (parts.length === 0) {
377
+ return [
378
+ field
379
+ ];
380
+ }
381
+ const lowerParts = parts.map((p) => p.toLowerCase());
382
+ const upperParts = parts.map((p) => p.toUpperCase());
383
+ const camel = lowerParts[0] + lowerParts.slice(1).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
384
+ const mergedLower = lowerParts.join("");
385
+ const mergedUpper = upperParts.join("");
386
+ const kebab = lowerParts.join("-");
387
+ const snakeLower = lowerParts.join("_");
388
+ const snakeUpper = upperParts.join("_");
292
389
  return Array.from(/* @__PURE__ */ new Set([
293
390
  field,
294
391
  camel,
@@ -300,7 +397,7 @@ function expandSensitiveField(field) {
300
397
  ]));
301
398
  }
302
399
  __name(expandSensitiveField, "expandSensitiveField");
303
- function generateDeepPaths(field, depth) {
400
+ function generateDeepPaths(field, depth = 5) {
304
401
  const set = /* @__PURE__ */ new Set([
305
402
  field
306
403
  ]);
@@ -329,5 +426,6 @@ exports.getLogger = getLogger;
329
426
  exports.getRedactCensor = getRedactCensor;
330
427
  exports.logError = logError;
331
428
  exports.redact = redact;
429
+ exports.resetLogger = resetLogger;
332
430
  exports.setRedactCensor = setRedactCensor;
333
431
  exports.setSensitiveFields = setSensitiveFields;
package/logger/index.d.ts CHANGED
@@ -28,11 +28,26 @@ import pino, { Logger as Logger$1 } from 'pino';
28
28
  * Logger type for application-wide logging.
29
29
  */
30
30
  type Logger = Logger$1;
31
+ /**
32
+ * Logger level for application-wide logging.
33
+ */
34
+ type LoggerLevel = pino.Level;
31
35
  /**
32
36
  * Logger levels for application-wide logging.
37
+ *
38
+ * @deprecated Use `LoggerLevel` instead.
33
39
  */
34
- type LoggerLevels = pino.Level;
40
+ type LoggerLevels = LoggerLevel;
35
41
  declare const defaultSensitiveFields: string[];
42
+ /**
43
+ * Retrieves the expanded list of sensitive fields.
44
+ *
45
+ * This function expands the default sensitive fields into their various naming
46
+ * conventions (e.g., camelCase, snake_case) and caches the result for efficiency.
47
+ *
48
+ * @returns Array of expanded sensitive field names
49
+ */
50
+ declare function getExpandedSensitiveFields(): string[];
36
51
  /**
37
52
  * Sets the global redaction censor function used throughout the application for log redaction.
38
53
  *
@@ -75,7 +90,7 @@ declare function setRedactCensor(fn: (value: unknown, path: string[], sensitiveF
75
90
  *
76
91
  * @returns The current redaction censor function
77
92
  */
78
- declare function getRedactCensor(): (value: unknown, path: string[], sensitiveFields?: string[]) => string;
93
+ declare function getRedactCensor(): (value: unknown, path: string[], sensitiveFields?: string[]) => unknown;
79
94
  /**
80
95
  * Convenience function to redact sensitive data using the current global redact censor.
81
96
  *
@@ -93,7 +108,7 @@ declare function getRedactCensor(): (value: unknown, path: string[], sensitiveFi
93
108
  * @param sensitiveFields - Optional array of field names to consider sensitive
94
109
  * @returns The redacted string value or "***" for redacted content
95
110
  */
96
- declare function redact(value: unknown, path: string[], sensitiveFields?: string[]): string;
111
+ declare function redact(value: unknown, path: string[], sensitiveFields?: string[]): unknown;
97
112
  /**
98
113
  * @deprecated Use addSensitiveFields instead.
99
114
  *
@@ -111,15 +126,6 @@ declare function redact(value: unknown, path: string[], sensitiveFields?: string
111
126
  * @param fields - Array of additional field names to redact
112
127
  */
113
128
  declare function addRedactFields(fields: string[]): void;
114
- /**
115
- * Retrieves the expanded list of sensitive fields.
116
- *
117
- * This function expands the default sensitive fields into their various naming
118
- * conventions (e.g., camelCase, snake_case) and caches the result for efficiency.
119
- *
120
- * @returns Array of expanded sensitive field names
121
- */
122
- declare function getExpandedSensitiveFields(): string[];
123
129
  /**
124
130
  * Replaces the default list of sensitive fields with a new list.
125
131
  *
@@ -154,6 +160,11 @@ declare function addSensitiveFields(fields: string[]): void;
154
160
  * Use an object compatible with either modern or legacy global scopes.
155
161
  */
156
162
  declare const _globalThis: typeof globalThis;
163
+ /**
164
+ * Resets the cached root logger instance.
165
+ * Useful for tests or dynamic reconfiguration.
166
+ */
167
+ declare function resetLogger(): void;
157
168
  /**
158
169
  * Retrieves the current logger instance:
159
170
  * - Returns a request-scoped logger from AsyncLocalStorage if available
@@ -168,7 +179,7 @@ declare function getLogger(newInstance?: boolean): Logger$1;
168
179
  /**
169
180
  * Creates a child logger with additional context.
170
181
  *
171
- * @param {Record<string, any>} bindings - Properties to attach to all log records
182
+ * @param {Record<string, unknown>} bindings - Properties to attach to all log records
172
183
  * @param {Logger} [parentLogger] - Parent logger (defaults to current context logger or global)
173
184
  * @returns {Logger} Child logger with merged context
174
185
  */
@@ -184,11 +195,11 @@ declare function createRequestLogger(requestId: string, additionalContext?: Reco
184
195
  /**
185
196
  * Utility to safely log errors with proper stack trace extraction
186
197
  *
187
- * @param {Error|unknown} error - Error object to log
198
+ * @param {unknown} error - Error object to log
188
199
  * @param {string} [message] - Optional message to include
189
200
  * @param {Record<string, unknown>} [context] - Additional context properties
190
201
  */
191
- declare function logError(error: Error | string, message?: string, context?: Record<string, unknown>): void;
202
+ declare function logError(error: unknown, message?: string, context?: Record<string, unknown>): void;
192
203
  /**
193
204
  * Expands multiple sensitive field names into their variants.
194
205
  * Useful to match fields like `api_key`, `apiKey`, `apikey`, `APIKEY`, etc.
@@ -207,7 +218,7 @@ declare function expandSensitiveField(field: string): string[];
207
218
  * *.password
208
219
  * *.*.password
209
220
  */
210
- declare function generateDeepPaths(field: string, depth: number): string[];
221
+ declare function generateDeepPaths(field: string, depth?: number): string[];
211
222
 
212
- export { _globalThis, addRedactFields, addSensitiveFields, createChildLogger, createRequestLogger, defaultSensitiveFields, expandSensitiveField, expandSensitiveFields, generateDeepPaths, getExpandedSensitiveFields, getLogger, getRedactCensor, logError, redact, setRedactCensor, setSensitiveFields };
213
- export type { Logger, LoggerLevels };
223
+ export { _globalThis, addRedactFields, addSensitiveFields, createChildLogger, createRequestLogger, defaultSensitiveFields, expandSensitiveField, expandSensitiveFields, generateDeepPaths, getExpandedSensitiveFields, getLogger, getRedactCensor, logError, redact, resetLogger, setRedactCensor, setSensitiveFields };
224
+ export type { Logger, LoggerLevel, LoggerLevels };