@depup/nestjs__common 12.1.0-depup.0 → 12.1.1-depup.0

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/README.md CHANGED
@@ -13,8 +13,8 @@ npm install @depup/nestjs__common
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [@nestjs/common](https://www.npmjs.com/package/@nestjs/common) @ 12.1.0 |
17
- | Processed | 2026-09-23 |
16
+ | Original | [@nestjs/common](https://www.npmjs.com/package/@nestjs/common) @ 12.1.1 |
17
+ | Processed | 2026-09-28 |
18
18
  | Smoke test | passed |
19
19
  | Deps updated | 0 |
20
20
 
package/changes.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "bumped": {},
3
- "timestamp": "2026-09-23T08:11:51.547Z",
3
+ "timestamp": "2026-09-28T16:10:42.807Z",
4
4
  "totalUpdated": 0
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@depup/nestjs__common",
3
- "version": "12.1.0-depup.0",
3
+ "version": "12.1.1-depup.0",
4
4
  "description": "Nest - modern, fast, powerful node.js web framework (@common) (with updated dependencies)",
5
5
  "author": "Kamil Mysliwiec",
6
6
  "homepage": "https://nestjs.com",
@@ -44,7 +44,7 @@
44
44
  "optional": true
45
45
  }
46
46
  },
47
- "gitHead": "6739656fa3e8c98bd55883f0363a2254e6412232",
47
+ "gitHead": "9feedffde8ef8c86f0cd521153740575dc579d88",
48
48
  "keywords": [
49
49
  "@nestjs/common",
50
50
  "depup",
@@ -57,8 +57,8 @@
57
57
  "changes": {},
58
58
  "depsUpdated": 0,
59
59
  "originalPackage": "@nestjs/common",
60
- "originalVersion": "12.1.0",
61
- "processedAt": "2026-09-23T08:11:53.954Z",
60
+ "originalVersion": "12.1.1",
61
+ "processedAt": "2026-09-28T16:10:47.564Z",
62
62
  "smokeTest": "passed"
63
63
  }
64
64
  }
@@ -51,4 +51,5 @@ export declare class ParseArrayPipe implements PipeTransform {
51
51
  transform(value: unknown, metadata: ArgumentMetadata): Promise<any>;
52
52
  protected isExpectedTypePrimitive(): boolean;
53
53
  protected validatePrimitive(originalValue: any, index?: number): any;
54
+ protected isNumeric(value: unknown): boolean;
54
55
  }
@@ -121,11 +121,10 @@ let ParseArrayPipe = class ParseArrayPipe {
121
121
  }
122
122
  validatePrimitive(originalValue, index) {
123
123
  if (this.options.items === Number) {
124
- const value = originalValue !== null && originalValue !== '' ? +originalValue : NaN;
125
- if (isNaN(value)) {
124
+ if (!this.isNumeric(originalValue)) {
126
125
  throw this.exceptionFactory(`${isUndefined(index) ? '' : `[${index}] `}item must be a number`);
127
126
  }
128
- return value;
127
+ return Number(originalValue);
129
128
  }
130
129
  else if (this.options.items === String) {
131
130
  if (!isString(originalValue)) {
@@ -139,6 +138,22 @@ let ParseArrayPipe = class ParseArrayPipe {
139
138
  }
140
139
  return originalValue;
141
140
  }
141
+ isNumeric(value) {
142
+ if (typeof value === 'number') {
143
+ return Number.isFinite(value);
144
+ }
145
+ // Booleans, arrays and blank strings coerce to numbers (`+true === 1`,
146
+ // `+[2] === 2`, `+' ' === 0`), so only strings are converted.
147
+ if (typeof value !== 'string' || value.trim() === '') {
148
+ return false;
149
+ }
150
+ // Reject radix prefixes (`Number('0x10') === 16`), as ParseIntPipe and
151
+ // ParseFloatPipe do.
152
+ if (/^0[box]/i.test(value.trim())) {
153
+ return false;
154
+ }
155
+ return Number.isFinite(Number(value));
156
+ }
142
157
  };
143
158
  ParseArrayPipe = __decorate([
144
159
  Injectable(),
@@ -66,8 +66,10 @@ let ParseEnumPipe = class ParseEnumPipe {
66
66
  return Object.keys(this.enumType)
67
67
  .filter(key => {
68
68
  const enumValue = this.enumType[key];
69
+ // Only numeric reverse mappings point back to their own key.
69
70
  return !(typeof enumValue === 'string' &&
70
- typeof this.enumType[enumValue] === 'number');
71
+ typeof this.enumType[enumValue] === 'number' &&
72
+ String(this.enumType[enumValue]) === key);
71
73
  })
72
74
  .map(key => this.enumType[key]);
73
75
  }
@@ -6,6 +6,9 @@ import { LoggerService, LogLevel } from './logger.service.js';
6
6
  export interface ConsoleLoggerOptions {
7
7
  /**
8
8
  * Enabled log levels.
9
+ * When not set, the levels are read from the `NEST_LOG_LEVEL` environment
10
+ * variable (e.g. `warn`, `>=debug` or `warn,error`) the first time the
11
+ * logger checks a level. Without it, every level is enabled.
9
12
  */
10
13
  logLevels?: LogLevel[];
11
14
  /**
@@ -97,6 +100,22 @@ export interface ConsoleLoggerOptions {
97
100
  * @default false
98
101
  */
99
102
  flattenParams?: boolean;
103
+ /**
104
+ * Properties to mask in logged values, as key names (`'password'`, matched
105
+ * at any depth) or dotted paths (`'user.password'`, matched by the last
106
+ * keys leading to the property, at any depth). Keys are compared
107
+ * case-insensitively, and array indices are skipped.
108
+ * Applies to structured params and to messages that are not strings
109
+ * (objects, arrays, errors), in text and JSON mode. String messages, the
110
+ * error message and the stack trace are printed as is.
111
+ * The logged values are not mutated: only the objects that contain a
112
+ * matching property are copied.
113
+ * Pass an object to replace the default censor (`"[REDACTED]"`).
114
+ */
115
+ redact?: string[] | {
116
+ paths: string[];
117
+ censor?: string;
118
+ };
100
119
  }
101
120
  /**
102
121
  * @publicApi
@@ -122,6 +141,10 @@ export declare class ConsoleLogger implements LoggerService {
122
141
  * The last timestamp at which the log message was printed.
123
142
  */
124
143
  protected static lastTimestampAt?: number;
144
+ /**
145
+ * Masks the properties set in the `redact` option.
146
+ */
147
+ private readonly redactor?;
125
148
  constructor();
126
149
  constructor(context: string);
127
150
  constructor(options: ConsoleLoggerOptions);
@@ -159,10 +182,11 @@ export declare class ConsoleLogger implements LoggerService {
159
182
  verbose(message: any, ...optionalParams: [...any, string?]): void;
160
183
  /**
161
184
  * Write a 'fatal' level log, if the configured level allows for it.
162
- * Prints to `stdout` with newline.
185
+ * Prints to `stderr` with newline.
163
186
  */
164
- fatal(message: any, context?: string): void;
165
- fatal(message: any, ...optionalParams: [...any, string?]): void;
187
+ fatal(message: any, stackOrContext?: string): void;
188
+ fatal(message: any, stack?: string, context?: string): void;
189
+ fatal(message: any, ...optionalParams: [...any, string?, string?]): void;
166
190
  /**
167
191
  * Set log levels
168
192
  * @param levels log levels
@@ -178,6 +202,14 @@ export declare class ConsoleLogger implements LoggerService {
178
202
  */
179
203
  resetContext(): void;
180
204
  isLevelEnabled(level: LogLevel): boolean;
205
+ /**
206
+ * Returns the levels used when none were passed to the constructor: the
207
+ * `NEST_LOG_LEVEL` environment variable if it is set, every level otherwise.
208
+ * Called on the first level check rather than in the constructor, so that
209
+ * the default logger also sees variables loaded after "@nestjs/common" was
210
+ * imported (for example, from a `.env` file).
211
+ */
212
+ protected getDefaultLogLevels(): LogLevel[];
181
213
  protected getTimestamp(): string;
182
214
  protected printMessages(messages: unknown[], context?: string, logLevel?: LogLevel, writeStreamType?: 'stdout' | 'stderr', errorStack?: unknown, params?: Record<string, any>): void;
183
215
  protected printAsJson(message: unknown, options: {
@@ -186,6 +218,7 @@ export declare class ConsoleLogger implements LoggerService {
186
218
  writeStreamType?: 'stdout' | 'stderr';
187
219
  errorStack?: unknown;
188
220
  params?: Record<string, any>;
221
+ error?: Error;
189
222
  }): void;
190
223
  protected getJsonLogObject(message: unknown, options: {
191
224
  context: string;
@@ -193,6 +226,7 @@ export declare class ConsoleLogger implements LoggerService {
193
226
  writeStreamType?: 'stdout' | 'stderr';
194
227
  errorStack?: unknown;
195
228
  params?: Record<string, any>;
229
+ error?: Error;
196
230
  }): {
197
231
  [key: string]: unknown;
198
232
  level: LogLevel;
@@ -201,18 +235,51 @@ export declare class ConsoleLogger implements LoggerService {
201
235
  message: unknown;
202
236
  context?: string;
203
237
  stack?: unknown;
238
+ error?: Record<string, unknown>;
204
239
  params?: Record<string, any>;
205
240
  };
241
+ /**
242
+ * Pulls the first `Error` out of the messages so that it can be attached to
243
+ * the log record as a structured `error` field instead of being printed as
244
+ * a separate record. When the error is the message itself, the record's
245
+ * message becomes the error's message.
246
+ */
247
+ protected extractJsonError(messages: unknown[]): {
248
+ messages: unknown[];
249
+ error?: Error;
250
+ };
251
+ /**
252
+ * Converts an error into a plain object: `name`, `message`, `stack`, own
253
+ * primitive properties (e.g. `code`), and, recursively, `cause` and the
254
+ * `errors` of an `AggregateError`, up to a fixed depth.
255
+ */
256
+ protected serializeError(error: Error, depth?: number, ancestors?: Set<Error>): Record<string, unknown>;
257
+ /**
258
+ * Masks the properties set in the `redact` option. Returns the value itself
259
+ * when the option isn't set or nothing matched.
260
+ */
261
+ protected redact(value: unknown): unknown;
206
262
  protected formatPid(pid: number): string;
207
263
  protected formatContext(context: string): string;
208
264
  protected formatMessage(logLevel: LogLevel, message: unknown, pidMessage: string, formattedLogLevel: string, contextMessage: string, timestampDiff: string, params?: Record<string, any>): string;
209
265
  protected stringifyParams(params: Record<string, any>): string;
210
- protected stringifyMessage(message: unknown, logLevel: LogLevel): any;
266
+ /**
267
+ * Resolves a message passed as a function: a class resolves to its name,
268
+ * any other function is called (lazy message) and its result re-resolved.
269
+ */
270
+ protected resolveMessage(message: unknown): unknown;
271
+ protected stringifyMessage(message: unknown, logLevel: LogLevel): string;
211
272
  protected colorize(message: string, logLevel: LogLevel): string;
212
273
  protected printStackTrace(stack: string): void;
213
274
  protected updateAndGetTimestampDiff(): string;
214
275
  protected formatTimestampDiff(timestampDiff: number): string;
215
276
  protected getInspectOptions(): InspectOptions;
277
+ /**
278
+ * Serializes a JSON log object without ever throwing: circular references
279
+ * are replaced with "[Circular]", and a value that cannot be serialized
280
+ * (e.g. a throwing `toJSON()`) makes the whole record fall back to `inspect`.
281
+ */
282
+ protected stringifyJsonLogObject(logObject: Record<string, unknown>): string;
216
283
  protected stringifyReplacer(key: string, value: unknown): unknown;
217
284
  protected getContextAndMessagesToPrint(args: unknown[]): {
218
285
  messages: unknown[];
@@ -4,8 +4,15 @@ import { inspect } from 'util';
4
4
  import { Injectable, Optional } from '../decorators/core/index.js';
5
5
  import { clc, yellow, isColorAllowed } from '../utils/cli-colors.util.js';
6
6
  import { isFunction, isPlainObject, isString, isUndefined, } from '../utils/shared.utils.js';
7
+ import { getEnvLogLevels, LOG_LEVEL_ENV_VAR, } from './utils/get-env-log-levels.util.js';
7
8
  import { isLogLevelEnabled } from './utils/is-log-level-enabled.util.js';
9
+ import { createRedactor } from './utils/redact.util.js';
8
10
  const DEFAULT_DEPTH = 5;
11
+ /**
12
+ * How many levels of nested errors (`cause`, `AggregateError#errors`) are
13
+ * serialized in JSON mode.
14
+ */
15
+ const MAX_ERROR_DEPTH = 5;
9
16
  const DEFAULT_LOG_LEVELS = [
10
17
  'log',
11
18
  'error',
@@ -14,6 +21,11 @@ const DEFAULT_LOG_LEVELS = [
14
21
  'verbose',
15
22
  'fatal',
16
23
  ];
24
+ /**
25
+ * The last invalid `NEST_LOG_LEVEL` value that was reported, so that loggers
26
+ * sharing the environment warn about it once.
27
+ */
28
+ let reportedInvalidEnvLogLevel;
17
29
  const dateTimeFormatter = new Intl.DateTimeFormat(undefined, {
18
30
  year: 'numeric',
19
31
  hour: 'numeric',
@@ -47,6 +59,10 @@ let ConsoleLogger = class ConsoleLogger {
47
59
  * The last timestamp at which the log message was printed.
48
60
  */
49
61
  static lastTimestampAt;
62
+ /**
63
+ * Masks the properties set in the `redact` option.
64
+ */
65
+ redactor;
50
66
  constructor(contextOrOptions, options) {
51
67
  // eslint-disable-next-line prefer-const
52
68
  let [context, opts] = isString(contextOrOptions)
@@ -55,11 +71,15 @@ let ConsoleLogger = class ConsoleLogger {
55
71
  ? [undefined, options]
56
72
  : [contextOrOptions?.context, contextOrOptions];
57
73
  opts = opts ?? {};
58
- opts.logLevels ??= DEFAULT_LOG_LEVELS;
59
74
  opts.colors ??= opts.colors ?? (opts.json ? false : isColorAllowed());
60
75
  opts.prefix ??= 'Nest';
61
76
  this.options = opts;
62
77
  this.inspectOptions = this.getInspectOptions();
78
+ if (opts.redact) {
79
+ this.redactor = Array.isArray(opts.redact)
80
+ ? createRedactor(opts.redact)
81
+ : createRedactor(opts.redact.paths, opts.redact.censor);
82
+ }
63
83
  if (context) {
64
84
  this.context = context;
65
85
  this.originalContext = context;
@@ -117,11 +137,9 @@ let ConsoleLogger = class ConsoleLogger {
117
137
  if (!this.isLevelEnabled('fatal')) {
118
138
  return;
119
139
  }
120
- const { messages, context, params } = this.getContextAndMessagesToPrint([
121
- message,
122
- ...optionalParams,
123
- ]);
124
- this.printMessages(messages, context, 'fatal', 'stdout', undefined, params);
140
+ const { messages, context, stack, params } = this.getContextAndStackAndMessagesToPrint([message, ...optionalParams]);
141
+ this.printMessages(messages, context, 'fatal', 'stderr', stack, params);
142
+ this.printStackTrace(stack);
125
143
  }
126
144
  /**
127
145
  * Set log levels
@@ -147,24 +165,58 @@ let ConsoleLogger = class ConsoleLogger {
147
165
  this.context = this.originalContext;
148
166
  }
149
167
  isLevelEnabled(level) {
168
+ if (this.options && !this.options.logLevels) {
169
+ this.options.logLevels = this.getDefaultLogLevels();
170
+ }
150
171
  const logLevels = this.options?.logLevels;
151
172
  return isLogLevelEnabled(level, logLevels);
152
173
  }
174
+ /**
175
+ * Returns the levels used when none were passed to the constructor: the
176
+ * `NEST_LOG_LEVEL` environment variable if it is set, every level otherwise.
177
+ * Called on the first level check rather than in the constructor, so that
178
+ * the default logger also sees variables loaded after "@nestjs/common" was
179
+ * imported (for example, from a `.env` file).
180
+ */
181
+ getDefaultLogLevels() {
182
+ const envLogLevels = getEnvLogLevels();
183
+ if (envLogLevels !== false) {
184
+ return envLogLevels ?? DEFAULT_LOG_LEVELS;
185
+ }
186
+ const value = process.env[LOG_LEVEL_ENV_VAR];
187
+ if (reportedInvalidEnvLogLevel !== value) {
188
+ reportedInvalidEnvLogLevel = value;
189
+ // The default levels must be in place before this call, as "warn"
190
+ // checks the level again.
191
+ this.options.logLevels = DEFAULT_LOG_LEVELS;
192
+ this.warn(`Invalid ${LOG_LEVEL_ENV_VAR} value "${value}". Expected a log level ("warn"), a comma-separated list ("warn,error") or a threshold (">=warn"). Every log level is enabled instead.`, 'ConsoleLogger');
193
+ }
194
+ return DEFAULT_LOG_LEVELS;
195
+ }
153
196
  getTimestamp() {
154
197
  return dateTimeFormatter.format(Date.now());
155
198
  }
156
199
  printMessages(messages, context = '', logLevel = 'log', writeStreamType, errorStack, params) {
157
- messages.forEach(message => {
158
- if (this.options.json) {
200
+ if (this.redactor) {
201
+ messages = messages.map(message => this.redact(this.resolveMessage(message)));
202
+ params = params && this.redact(params);
203
+ }
204
+ if (this.options.json) {
205
+ const { messages: jsonMessages, error } = this.extractJsonError(messages.map(message => this.resolveMessage(message)));
206
+ jsonMessages.forEach((message, index) => {
159
207
  this.printAsJson(message, {
160
208
  context,
161
209
  logLevel,
162
210
  writeStreamType,
163
211
  errorStack,
164
212
  params,
213
+ // The error belongs to the first record (the log message).
214
+ error: index === 0 ? error : undefined,
165
215
  });
166
- return;
167
- }
216
+ });
217
+ return;
218
+ }
219
+ messages.forEach(message => {
168
220
  const pidMessage = this.formatPid(process.pid);
169
221
  const contextMessage = this.formatContext(context);
170
222
  const timestampDiff = this.updateAndGetTimestampDiff();
@@ -186,7 +238,7 @@ let ConsoleLogger = class ConsoleLogger {
186
238
  printAsJson(message, options) {
187
239
  const logObject = this.getJsonLogObject(message, options);
188
240
  const formattedMessage = !this.options.colors && this.inspectOptions.compact === true
189
- ? JSON.stringify(logObject, this.stringifyReplacer)
241
+ ? this.stringifyJsonLogObject(logObject)
190
242
  : inspect(logObject, this.inspectOptions);
191
243
  if (this.options.forceConsole) {
192
244
  if (options.writeStreamType === 'stderr') {
@@ -213,6 +265,9 @@ let ConsoleLogger = class ConsoleLogger {
213
265
  if (options.errorStack) {
214
266
  logObject.stack = options.errorStack;
215
267
  }
268
+ if (options.error) {
269
+ logObject.error = this.serializeError(options.error);
270
+ }
216
271
  if (options.params) {
217
272
  if (this.options.flattenParams) {
218
273
  // Framework fields win on key collisions: a param named "message" or
@@ -229,6 +284,74 @@ let ConsoleLogger = class ConsoleLogger {
229
284
  }
230
285
  return logObject;
231
286
  }
287
+ /**
288
+ * Pulls the first `Error` out of the messages so that it can be attached to
289
+ * the log record as a structured `error` field instead of being printed as
290
+ * a separate record. When the error is the message itself, the record's
291
+ * message becomes the error's message.
292
+ */
293
+ extractJsonError(messages) {
294
+ const errorIndex = messages.findIndex(message => message instanceof Error);
295
+ if (errorIndex === -1) {
296
+ return { messages };
297
+ }
298
+ const error = messages[errorIndex];
299
+ const remainingMessages = errorIndex === 0
300
+ ? [error.message, ...messages.slice(1)]
301
+ : messages.filter((_, index) => index !== errorIndex);
302
+ return { messages: remainingMessages, error };
303
+ }
304
+ /**
305
+ * Converts an error into a plain object: `name`, `message`, `stack`, own
306
+ * primitive properties (e.g. `code`), and, recursively, `cause` and the
307
+ * `errors` of an `AggregateError`, up to a fixed depth.
308
+ */
309
+ serializeError(error, depth = 0, ancestors = new Set()) {
310
+ const serialized = {
311
+ name: error.name,
312
+ message: error.message,
313
+ stack: error.stack,
314
+ };
315
+ for (const [key, value] of Object.entries(error)) {
316
+ if (key in serialized || key === 'cause' || key === 'errors') {
317
+ continue;
318
+ }
319
+ const isPrimitive = value === null ||
320
+ (typeof value !== 'object' && typeof value !== 'function');
321
+ if (isPrimitive) {
322
+ serialized[key] = value;
323
+ }
324
+ }
325
+ const serializeNested = (value) => {
326
+ if (!(value instanceof Error)) {
327
+ return value;
328
+ }
329
+ if (ancestors.has(value)) {
330
+ return '[Circular]';
331
+ }
332
+ if (depth + 1 > MAX_ERROR_DEPTH) {
333
+ return '[Truncated]';
334
+ }
335
+ return this.serializeError(value, depth + 1, ancestors);
336
+ };
337
+ ancestors.add(error);
338
+ if (error.cause !== undefined) {
339
+ serialized.cause = serializeNested(error.cause);
340
+ }
341
+ const errors = error.errors;
342
+ if (Array.isArray(errors)) {
343
+ serialized.errors = errors.map(serializeNested);
344
+ }
345
+ ancestors.delete(error);
346
+ return serialized;
347
+ }
348
+ /**
349
+ * Masks the properties set in the `redact` option. Returns the value itself
350
+ * when the option isn't set or nothing matched.
351
+ */
352
+ redact(value) {
353
+ return this.redactor ? this.redactor(value) : value;
354
+ }
232
355
  formatPid(pid) {
233
356
  return `[${this.options.prefix}] ${pid} - `;
234
357
  }
@@ -253,17 +376,25 @@ let ConsoleLogger = class ConsoleLogger {
253
376
  breakLength: Infinity,
254
377
  });
255
378
  }
256
- stringifyMessage(message, logLevel) {
379
+ /**
380
+ * Resolves a message passed as a function: a class resolves to its name,
381
+ * any other function is called (lazy message) and its result re-resolved.
382
+ */
383
+ resolveMessage(message) {
257
384
  if (isFunction(message)) {
258
385
  const messageAsStr = Function.prototype.toString.call(message);
259
386
  const isClass = messageAsStr.startsWith('class ');
260
387
  if (isClass) {
261
388
  // If the message is a class, we will display the class name.
262
- return this.stringifyMessage(message.name, logLevel);
389
+ return message.name;
263
390
  }
264
391
  // If the message is a non-class function, call it and re-resolve its value.
265
- return this.stringifyMessage(message(), logLevel);
392
+ return this.resolveMessage(message());
266
393
  }
394
+ return message;
395
+ }
396
+ stringifyMessage(message, logLevel) {
397
+ message = this.resolveMessage(message);
267
398
  if (typeof message === 'string') {
268
399
  return this.colorize(message, logLevel);
269
400
  }
@@ -333,6 +464,43 @@ let ConsoleLogger = class ConsoleLogger {
333
464
  }
334
465
  return inspectOptions;
335
466
  }
467
+ /**
468
+ * Serializes a JSON log object without ever throwing: circular references
469
+ * are replaced with "[Circular]", and a value that cannot be serialized
470
+ * (e.g. a throwing `toJSON()`) makes the whole record fall back to `inspect`.
471
+ */
472
+ stringifyJsonLogObject(logObject) {
473
+ const replacer = this.stringifyReplacer;
474
+ // Objects on the path from the root to the value being serialized.
475
+ const ancestors = [];
476
+ try {
477
+ return JSON.stringify(logObject, function (key, value) {
478
+ value = replacer.call(this, key, value);
479
+ if (typeof value !== 'object' || value === null) {
480
+ return value;
481
+ }
482
+ // `this` is the object holding `key`, so everything above it on the
483
+ // stack belongs to an already-finished sibling branch.
484
+ while (ancestors.length > 0 &&
485
+ ancestors[ancestors.length - 1] !== this) {
486
+ ancestors.pop();
487
+ }
488
+ if (ancestors.includes(value)) {
489
+ return '[Circular]';
490
+ }
491
+ ancestors.push(value);
492
+ return value;
493
+ });
494
+ }
495
+ catch {
496
+ return inspect(logObject, {
497
+ ...this.inspectOptions,
498
+ colors: false,
499
+ compact: true,
500
+ breakLength: Infinity,
501
+ });
502
+ }
503
+ }
336
504
  stringifyReplacer(key, value) {
337
505
  // Mimic util.inspect behavior for JSON logger with compact on and colors off
338
506
  if (typeof value === 'bigint') {
@@ -0,0 +1,5 @@
1
+ export declare const LOG_LEVELS: ["verbose", "debug", "log", "warn", "error", "fatal"];
2
+ /**
3
+ * @publicApi
4
+ */
5
+ export type LogLevel = (typeof LOG_LEVELS)[number];
@@ -0,0 +1,8 @@
1
+ export const LOG_LEVELS = [
2
+ 'verbose',
3
+ 'debug',
4
+ 'log',
5
+ 'warn',
6
+ 'error',
7
+ 'fatal',
8
+ ];
@@ -1,8 +1,5 @@
1
- export declare const LOG_LEVELS: ["verbose", "debug", "log", "warn", "error", "fatal"];
2
- /**
3
- * @publicApi
4
- */
5
- export type LogLevel = (typeof LOG_LEVELS)[number];
1
+ import { LogLevel } from './log-levels.constant.js';
2
+ export { LOG_LEVELS, type LogLevel } from './log-levels.constant.js';
6
3
  /**
7
4
  * @publicApi
8
5
  */
@@ -60,6 +57,10 @@ export declare class Logger implements LoggerService {
60
57
  protected static logLevels?: LogLevel[];
61
58
  private static isBufferAttached;
62
59
  protected localInstanceRef?: LoggerService;
60
+ /**
61
+ * The `Logger.logLevels` the local instance was last configured with.
62
+ */
63
+ private localInstanceLogLevels?;
63
64
  private static WrapBuffer;
64
65
  constructor();
65
66
  constructor(context: string);
@@ -149,4 +150,3 @@ export declare class Logger implements LoggerService {
149
150
  static isLevelEnabled(level: LogLevel): boolean;
150
151
  private registerLocalInstanceRef;
151
152
  }
152
- export {};
@@ -4,14 +4,9 @@ import { Injectable, Optional } from '../decorators/core/index.js';
4
4
  import { isObject } from '../utils/shared.utils.js';
5
5
  import { ConsoleLogger } from './console-logger.service.js';
6
6
  import { isLogLevelEnabled } from './utils/index.js';
7
- export const LOG_LEVELS = [
8
- 'verbose',
9
- 'debug',
10
- 'log',
11
- 'warn',
12
- 'error',
13
- 'fatal',
14
- ];
7
+ // Defined in a separate file so that the log level utilities don't import
8
+ // this file (and, through it, "ConsoleLogger") at runtime.
9
+ export { LOG_LEVELS } from './log-levels.constant.js';
15
10
  const DEFAULT_LOGGER = new ConsoleLogger();
16
11
  const dateTimeFormatter = new Intl.DateTimeFormat(undefined, {
17
12
  year: 'numeric',
@@ -33,6 +28,10 @@ let Logger = class Logger {
33
28
  static logLevels;
34
29
  static isBufferAttached;
35
30
  localInstanceRef;
31
+ /**
32
+ * The `Logger.logLevels` the local instance was last configured with.
33
+ */
34
+ localInstanceLogLevels;
36
35
  static WrapBuffer = (target, propertyKey, descriptor) => {
37
36
  const originalFn = descriptor.value;
38
37
  descriptor.value = function (...args) {
@@ -159,13 +158,30 @@ let Logger = class Logger {
159
158
  }
160
159
  }
161
160
  static isLevelEnabled(level) {
161
+ const instance = Logger_1.staticInstanceRef;
162
+ if (!instance) {
163
+ // Logging is disabled ("logger: false").
164
+ return false;
165
+ }
166
+ if (instance instanceof ConsoleLogger) {
167
+ return instance.isLevelEnabled(level);
168
+ }
162
169
  const logLevels = Logger_1.logLevels;
163
- return isLogLevelEnabled(level, logLevels);
170
+ // Without explicitly set levels, every level is enabled.
171
+ return logLevels ? isLogLevelEnabled(level, logLevels) : true;
164
172
  }
165
173
  registerLocalInstanceRef() {
166
174
  if (this.localInstanceRef) {
175
+ // Levels set at runtime (e.g. "app.useLogger(['error'])") must also
176
+ // reach instances created before the change.
177
+ if (Logger_1.logLevels &&
178
+ this.localInstanceLogLevels !== Logger_1.logLevels) {
179
+ this.localInstanceRef.setLogLevels?.(Logger_1.logLevels);
180
+ this.localInstanceLogLevels = Logger_1.logLevels;
181
+ }
167
182
  return this.localInstanceRef;
168
183
  }
184
+ this.localInstanceLogLevels = Logger_1.logLevels;
169
185
  this.localInstanceRef = new ConsoleLogger(this.context, {
170
186
  timestamp: this.options?.timestamp,
171
187
  logLevels: Logger_1.logLevels,
@@ -1,4 +1,4 @@
1
- import { LogLevel } from '../logger.service.js';
1
+ import { LogLevel } from '../log-levels.constant.js';
2
2
  /**
3
3
  * @publicApi
4
4
  */
@@ -1,4 +1,4 @@
1
- import { LOG_LEVELS } from '../logger.service.js';
1
+ import { LOG_LEVELS } from '../log-levels.constant.js';
2
2
  import { isLogLevel } from './is-log-level.util.js';
3
3
  /**
4
4
  * @publicApi
@@ -0,0 +1,14 @@
1
+ import { LogLevel } from '../log-levels.constant.js';
2
+ /**
3
+ * The environment variable that sets the default log levels of `ConsoleLogger`.
4
+ */
5
+ export declare const LOG_LEVEL_ENV_VAR = "NEST_LOG_LEVEL";
6
+ /**
7
+ * Reads the log levels from the `NEST_LOG_LEVEL` environment variable.
8
+ * Accepts the formats of `filterLogLevels`: a level (`warn`, that level and
9
+ * above), a list (`warn,error`) or a threshold (`>=warn`, `>debug`).
10
+ *
11
+ * @returns `undefined` when the variable is unset or empty, `false` when its
12
+ * value can't be parsed, the log levels otherwise.
13
+ */
14
+ export declare function getEnvLogLevels(): LogLevel[] | false | undefined;
@@ -0,0 +1,25 @@
1
+ import { filterLogLevels } from './filter-log-levels.util.js';
2
+ import { isLogLevel } from './is-log-level.util.js';
3
+ /**
4
+ * The environment variable that sets the default log levels of `ConsoleLogger`.
5
+ */
6
+ export const LOG_LEVEL_ENV_VAR = 'NEST_LOG_LEVEL';
7
+ /**
8
+ * Reads the log levels from the `NEST_LOG_LEVEL` environment variable.
9
+ * Accepts the formats of `filterLogLevels`: a level (`warn`, that level and
10
+ * above), a list (`warn,error`) or a threshold (`>=warn`, `>debug`).
11
+ *
12
+ * @returns `undefined` when the variable is unset or empty, `false` when its
13
+ * value can't be parsed, the log levels otherwise.
14
+ */
15
+ export function getEnvLogLevels() {
16
+ const value = process.env[LOG_LEVEL_ENV_VAR]?.replaceAll(' ', '');
17
+ if (!value) {
18
+ return undefined;
19
+ }
20
+ const threshold = /^>=?(.*)$/.exec(value.toLowerCase());
21
+ const isValid = threshold
22
+ ? isLogLevel(threshold[1])
23
+ : value.toLowerCase().split(',').every(isLogLevel);
24
+ return isValid ? filterLogLevels(value) : false;
25
+ }
@@ -1,4 +1,4 @@
1
- import { LogLevel } from '../logger.service.js';
1
+ import { LogLevel } from '../log-levels.constant.js';
2
2
  /**
3
3
  * @publicApi
4
4
  */
@@ -1,4 +1,4 @@
1
- import { LOG_LEVELS } from '../logger.service.js';
1
+ import { LOG_LEVELS } from '../log-levels.constant.js';
2
2
  /**
3
3
  * @publicApi
4
4
  */
@@ -0,0 +1,27 @@
1
+ export declare const DEFAULT_REDACT_CENSOR = "[REDACTED]";
2
+ /**
3
+ * Returns a copy of the value in which the properties matching the paths are
4
+ * replaced with the censor, or the value itself when nothing matched.
5
+ */
6
+ export type Redactor = (value: unknown) => unknown;
7
+ /**
8
+ * Creates a function that masks properties of logged values.
9
+ *
10
+ * - A path without dots (`password`) matches a property with that name at any
11
+ * depth.
12
+ * - A dotted path (`user.password`) matches when the last keys leading to a
13
+ * property are those keys, at any depth. Array indices are not keys, so
14
+ * `users.password` also matches `{ users: [{ password }] }`.
15
+ * - Keys are compared case-insensitively.
16
+ *
17
+ * Plain objects, arrays, class instances, errors (including `cause` and
18
+ * `AggregateError#errors`), `Map`s (string keys) and `Set`s are traversed.
19
+ * Only the objects on the way to a redacted property are copied; the rest is
20
+ * returned as is, and the input is never mutated. A circular reference is
21
+ * replaced with `"[Circular]"` (so the objects on the cycle are copied too). If
22
+ * the value can't be traversed (for example, a getter throws), the whole value
23
+ * is replaced with the censor.
24
+ *
25
+ * @returns `undefined` when there is nothing to redact.
26
+ */
27
+ export declare function createRedactor(paths: string[], censor?: string): Redactor | undefined;
@@ -0,0 +1,225 @@
1
+ export const DEFAULT_REDACT_CENSOR = '[REDACTED]';
2
+ const CIRCULAR = '[Circular]';
3
+ /**
4
+ * Creates a function that masks properties of logged values.
5
+ *
6
+ * - A path without dots (`password`) matches a property with that name at any
7
+ * depth.
8
+ * - A dotted path (`user.password`) matches when the last keys leading to a
9
+ * property are those keys, at any depth. Array indices are not keys, so
10
+ * `users.password` also matches `{ users: [{ password }] }`.
11
+ * - Keys are compared case-insensitively.
12
+ *
13
+ * Plain objects, arrays, class instances, errors (including `cause` and
14
+ * `AggregateError#errors`), `Map`s (string keys) and `Set`s are traversed.
15
+ * Only the objects on the way to a redacted property are copied; the rest is
16
+ * returned as is, and the input is never mutated. A circular reference is
17
+ * replaced with `"[Circular]"` (so the objects on the cycle are copied too). If
18
+ * the value can't be traversed (for example, a getter throws), the whole value
19
+ * is replaced with the censor.
20
+ *
21
+ * @returns `undefined` when there is nothing to redact.
22
+ */
23
+ export function createRedactor(paths, censor = DEFAULT_REDACT_CENSOR) {
24
+ const keys = new Set();
25
+ const dottedPaths = [];
26
+ for (const path of paths) {
27
+ const segments = path
28
+ .split('.')
29
+ .map(segment => segment.trim().toLowerCase())
30
+ .filter(segment => segment.length > 0);
31
+ if (segments.length === 1) {
32
+ keys.add(segments[0]);
33
+ }
34
+ else if (segments.length > 1) {
35
+ dottedPaths.push(segments);
36
+ }
37
+ }
38
+ if (keys.size === 0 && dottedPaths.length === 0) {
39
+ return undefined;
40
+ }
41
+ return (value) => {
42
+ // Lower-cased keys from the root to the value being visited.
43
+ const path = [];
44
+ // Objects on that path, to detect circular references.
45
+ const ancestors = [];
46
+ const isRedacted = (key) => {
47
+ if (keys.has(key)) {
48
+ return true;
49
+ }
50
+ return dottedPaths.some(segments => {
51
+ const last = segments.length - 1;
52
+ if (segments[last] !== key || last > path.length) {
53
+ return false;
54
+ }
55
+ for (let i = 1; i <= last; i++) {
56
+ if (segments[last - i] !== path[path.length - i]) {
57
+ return false;
58
+ }
59
+ }
60
+ return true;
61
+ });
62
+ };
63
+ // Each call starts with a fresh "path" and "ancestors", so an exception
64
+ // (caught below) doesn't need to restore them.
65
+ const visitProperty = (key, propertyValue) => {
66
+ const normalizedKey = key.toLowerCase();
67
+ if (isRedacted(normalizedKey)) {
68
+ return censor;
69
+ }
70
+ if (typeof propertyValue !== 'object' || propertyValue === null) {
71
+ return propertyValue;
72
+ }
73
+ path.push(normalizedKey);
74
+ const redacted = visit(propertyValue);
75
+ path.pop();
76
+ return redacted;
77
+ };
78
+ const visit = (current) => {
79
+ if (typeof current !== 'object' || current === null) {
80
+ return current;
81
+ }
82
+ if (ancestors.includes(current)) {
83
+ return CIRCULAR;
84
+ }
85
+ let redacted;
86
+ ancestors.push(current);
87
+ if (Array.isArray(current)) {
88
+ redacted = redactArray(current);
89
+ }
90
+ else if (Object.getPrototypeOf(current) === Object.prototype) {
91
+ redacted = redactObject(current);
92
+ }
93
+ else if (current instanceof Map) {
94
+ redacted = redactMap(current);
95
+ }
96
+ else if (current instanceof Set) {
97
+ redacted = redactSet(current);
98
+ }
99
+ else if (isOpaque(current)) {
100
+ redacted = current;
101
+ }
102
+ else {
103
+ redacted = redactObject(current);
104
+ }
105
+ ancestors.pop();
106
+ return redacted;
107
+ };
108
+ const redactArray = (array) => {
109
+ let copy;
110
+ for (let i = 0; i < array.length; i++) {
111
+ const redacted = visit(array[i]);
112
+ if (!Object.is(redacted, array[i])) {
113
+ copy ??= array.slice();
114
+ copy[i] = redacted;
115
+ }
116
+ }
117
+ return copy ?? array;
118
+ };
119
+ const redactMap = (map) => {
120
+ let changed = false;
121
+ const entries = [];
122
+ for (const [key, entryValue] of map) {
123
+ const redacted = typeof key === 'string'
124
+ ? visitProperty(key, entryValue)
125
+ : visit(entryValue);
126
+ changed ||= !Object.is(redacted, entryValue);
127
+ entries.push([key, redacted]);
128
+ }
129
+ return changed ? new Map(entries) : map;
130
+ };
131
+ const redactSet = (set) => {
132
+ let changed = false;
133
+ const values = [];
134
+ for (const setValue of set) {
135
+ const redacted = visit(setValue);
136
+ changed ||= !Object.is(redacted, setValue);
137
+ values.push(redacted);
138
+ }
139
+ return changed ? new Set(values) : set;
140
+ };
141
+ const redactObject = (object) => {
142
+ const source = object;
143
+ const keysToVisit = Object.keys(source);
144
+ if (object instanceof Error) {
145
+ // Non-enumerable, but part of the logged error.
146
+ for (const key of ['cause', 'errors']) {
147
+ if (Object.prototype.hasOwnProperty.call(object, key) &&
148
+ !keysToVisit.includes(key)) {
149
+ keysToVisit.push(key);
150
+ }
151
+ }
152
+ }
153
+ let changes;
154
+ for (const key of keysToVisit) {
155
+ const original = source[key];
156
+ const redacted = visitProperty(key, original);
157
+ if (!Object.is(redacted, original)) {
158
+ changes ??= new Map();
159
+ changes.set(key, redacted);
160
+ }
161
+ }
162
+ if (!changes) {
163
+ return object;
164
+ }
165
+ if (Object.getPrototypeOf(object) === Object.prototype) {
166
+ const copy = { ...source };
167
+ for (const [key, redacted] of changes) {
168
+ if (key === '__proto__') {
169
+ // An assignment would call the "__proto__" setter.
170
+ Object.defineProperty(copy, key, {
171
+ value: redacted,
172
+ enumerable: true,
173
+ writable: true,
174
+ configurable: true,
175
+ });
176
+ }
177
+ else {
178
+ copy[key] = redacted;
179
+ }
180
+ }
181
+ return copy;
182
+ }
183
+ // Keeps the prototype and the other properties (including the
184
+ // non-enumerable "message" and "stack" of errors).
185
+ const descriptors = Object.getOwnPropertyDescriptors(object);
186
+ if (object instanceof Error && descriptors.stack?.get) {
187
+ // V8 may define "stack" as an accessor that only works on the
188
+ // original error.
189
+ descriptors.stack = {
190
+ value: object.stack,
191
+ enumerable: descriptors.stack.enumerable,
192
+ writable: true,
193
+ configurable: true,
194
+ };
195
+ }
196
+ for (const [key, redacted] of changes) {
197
+ descriptors[key] = {
198
+ value: redacted,
199
+ enumerable: descriptors[key].enumerable,
200
+ writable: true,
201
+ configurable: true,
202
+ };
203
+ }
204
+ return Object.create(Object.getPrototypeOf(object), descriptors);
205
+ };
206
+ try {
207
+ return visit(value);
208
+ }
209
+ catch {
210
+ return censor;
211
+ }
212
+ };
213
+ }
214
+ /**
215
+ * Objects whose content isn't made of properties worth traversing.
216
+ */
217
+ function isOpaque(value) {
218
+ return (value instanceof Date ||
219
+ value instanceof RegExp ||
220
+ value instanceof Promise ||
221
+ value instanceof WeakMap ||
222
+ value instanceof WeakSet ||
223
+ value instanceof ArrayBuffer ||
224
+ ArrayBuffer.isView(value));
225
+ }