@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 +2 -2
- package/changes.json +1 -1
- package/package.json +4 -4
- package/pipes/parse-array.pipe.d.ts +1 -0
- package/pipes/parse-array.pipe.js +18 -3
- package/pipes/parse-enum.pipe.js +3 -1
- package/services/console-logger.service.d.ts +71 -4
- package/services/console-logger.service.js +182 -14
- package/services/log-levels.constant.d.ts +5 -0
- package/services/log-levels.constant.js +8 -0
- package/services/logger.service.d.ts +6 -6
- package/services/logger.service.js +25 -9
- package/services/utils/filter-log-levels.util.d.ts +1 -1
- package/services/utils/filter-log-levels.util.js +1 -1
- package/services/utils/get-env-log-levels.util.d.ts +14 -0
- package/services/utils/get-env-log-levels.util.js +25 -0
- package/services/utils/is-log-level.util.d.ts +1 -1
- package/services/utils/is-log-level.util.js +1 -1
- package/services/utils/redact.util.d.ts +27 -0
- package/services/utils/redact.util.js +225 -0
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.
|
|
17
|
-
| Processed | 2026-09-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@depup/nestjs__common",
|
|
3
|
-
"version": "12.1.
|
|
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": "
|
|
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.
|
|
61
|
-
"processedAt": "2026-09-
|
|
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
|
-
|
|
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
|
|
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(),
|
package/pipes/parse-enum.pipe.js
CHANGED
|
@@ -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 `
|
|
185
|
+
* Prints to `stderr` with newline.
|
|
163
186
|
*/
|
|
164
|
-
fatal(message: any,
|
|
165
|
-
fatal(message: any,
|
|
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
|
-
|
|
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.
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
?
|
|
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
|
-
|
|
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
|
|
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.
|
|
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') {
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|