autotel-edge 3.16.0 → 3.16.2
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/dist/{chunk-YWMTEG63.js → chunk-QXL3GNVV.js} +3 -3
- package/dist/{chunk-YWMTEG63.js.map → chunk-QXL3GNVV.js.map} +1 -1
- package/dist/events.d.ts +2 -2
- package/dist/events.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/logger.d.ts +229 -20
- package/dist/logger.js +774 -33
- package/dist/logger.js.map +1 -1
- package/dist/sampling.d.ts +1 -1
- package/dist/{types-BOs00OpG.d.ts → types-CZFcIjJ4.d.ts} +1 -1
- package/package.json +1 -1
- package/src/api/logger.test.ts +1339 -99
- package/src/api/logger.ts +894 -62
- package/src/api/redact.test.ts +432 -0
- package/src/api/redact.ts +402 -0
- package/src/logger.ts +13 -0
package/src/api/logger.ts
CHANGED
|
@@ -1,32 +1,199 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Pino-compatible structured logger for edge environments.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
4
|
+
* Supports the practical Pino surface used by app code:
|
|
5
|
+
* - Pino-style `LogFn` calls: string-first, object-first, and error-first
|
|
6
|
+
* - Child loggers, level control, custom levels, and `msgPrefix`
|
|
7
|
+
* - Serializers, formatters, mixins, redaction, and browser-style `transmit`
|
|
8
|
+
* - Trace context injection (`traceId`, `spanId`, `correlationId`)
|
|
9
|
+
* - Worker-safe EventEmitter-like methods for `level-change`
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* The implementation stays dependency-light and console/write based so it
|
|
12
|
+
* works in Cloudflare Workers and similar edge runtimes.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
trace,
|
|
17
|
+
context as api_context,
|
|
18
|
+
createContextKey,
|
|
19
|
+
} from '@opentelemetry/api';
|
|
20
|
+
import { createRedactor, type RedactorConfig } from './redact';
|
|
15
21
|
|
|
16
|
-
export type LogLevel =
|
|
22
|
+
export type LogLevel =
|
|
23
|
+
| 'trace'
|
|
24
|
+
| 'debug'
|
|
25
|
+
| 'info'
|
|
26
|
+
| 'warn'
|
|
27
|
+
| 'error'
|
|
28
|
+
| 'fatal'
|
|
29
|
+
| 'silent';
|
|
30
|
+
type LogAttrs = Record<string, any>;
|
|
31
|
+
type LevelWithSilentOrString = LogLevel | (string & {});
|
|
32
|
+
type SerializerFn = (value: any) => any;
|
|
33
|
+
type LevelFormatter = (label: string, number: number) => object;
|
|
34
|
+
type BindingsFormatter = (bindings: LogAttrs) => object;
|
|
35
|
+
type LogFormatter = (object: Record<string, unknown>) => Record<string, unknown>;
|
|
36
|
+
type MixinFn = (mergeObject: object, level: number, logger: EdgeLogger) => object;
|
|
37
|
+
type MixinMergeStrategyFn = (mergeObject: object, mixinObject: object) => object;
|
|
38
|
+
type PlaceholderSpecifier = 'd' | 's' | 'j' | 'o' | 'O';
|
|
39
|
+
type PlaceholderTypeMapping<T extends PlaceholderSpecifier> = T extends 'd'
|
|
40
|
+
? number
|
|
41
|
+
: T extends 's'
|
|
42
|
+
? unknown
|
|
43
|
+
: T extends 'j' | 'o' | 'O'
|
|
44
|
+
? // eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
45
|
+
{} | null
|
|
46
|
+
: never;
|
|
47
|
+
type ParseLogFnArgs<T, Acc extends unknown[] = []> =
|
|
48
|
+
T extends `${infer _}%${infer Placeholder}${infer Rest}`
|
|
49
|
+
? Placeholder extends PlaceholderSpecifier
|
|
50
|
+
? ParseLogFnArgs<Rest, [...Acc, PlaceholderTypeMapping<Placeholder>]>
|
|
51
|
+
: ParseLogFnArgs<Rest, Acc>
|
|
52
|
+
: Acc;
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
54
|
+
export interface LogFnFields {}
|
|
55
|
+
type LogFn = {
|
|
56
|
+
<TMsg extends string = string>(msg: TMsg, ...args: ParseLogFnArgs<TMsg>): void;
|
|
57
|
+
<T, TMsg extends string = string>(
|
|
58
|
+
obj: [T] extends [object] ? T & LogFnFields : T,
|
|
59
|
+
msg?: T extends string ? never : TMsg,
|
|
60
|
+
...args: ParseLogFnArgs<TMsg> | []
|
|
61
|
+
): void;
|
|
62
|
+
<T, TMsg extends string = string>(
|
|
63
|
+
obj: [T] extends [object] ? T & LogFnFields : T,
|
|
64
|
+
msg?: T extends string ? never : TMsg,
|
|
65
|
+
...args: ParseLogFnArgs<TMsg> extends [unknown, ...unknown[]]
|
|
66
|
+
? ParseLogFnArgs<TMsg>
|
|
67
|
+
: unknown[]
|
|
68
|
+
): void;
|
|
69
|
+
};
|
|
70
|
+
type ChildLoggerOptions = {
|
|
71
|
+
level?: LevelWithSilentOrString;
|
|
72
|
+
msgPrefix?: string;
|
|
73
|
+
serializers?: Record<string, SerializerFn>;
|
|
74
|
+
formatters?: {
|
|
75
|
+
level?: LevelFormatter;
|
|
76
|
+
bindings?: BindingsFormatter;
|
|
77
|
+
log?: LogFormatter;
|
|
78
|
+
};
|
|
79
|
+
redact?: RedactorConfig;
|
|
80
|
+
};
|
|
81
|
+
type LevelMapping = {
|
|
82
|
+
values: Record<string, number>;
|
|
83
|
+
labels: Record<number, string>;
|
|
84
|
+
};
|
|
85
|
+
type LevelChangeEventListener = (
|
|
86
|
+
levelLabel: LevelWithSilentOrString,
|
|
87
|
+
levelValue: number,
|
|
88
|
+
previousLevelLabel: LevelWithSilentOrString,
|
|
89
|
+
previousLevelValue: number,
|
|
90
|
+
logger: EdgeLogger,
|
|
91
|
+
) => void;
|
|
92
|
+
type OnChildCallback = (child: EdgeLogger) => void;
|
|
93
|
+
type EventListener = (...args: any[]) => void;
|
|
94
|
+
export type WriteFn = (o: object) => void;
|
|
95
|
+
export type TimeFn = () => string;
|
|
96
|
+
|
|
97
|
+
export interface LogEvent {
|
|
98
|
+
ts: number;
|
|
99
|
+
messages: any[];
|
|
100
|
+
bindings: LogAttrs[];
|
|
101
|
+
level: { label: string; value: number };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface EdgeEventEmitter {
|
|
105
|
+
addListener(event: string | symbol, listener: (...args: any[]) => any): any;
|
|
106
|
+
emit(event: string | symbol, ...args: any[]): boolean;
|
|
107
|
+
eventNames(): (string | symbol)[];
|
|
108
|
+
getMaxListeners(): number;
|
|
109
|
+
listenerCount(event: string | symbol): number;
|
|
110
|
+
listeners(event: string | symbol): ((...args: any[]) => any)[];
|
|
111
|
+
off(event: string | symbol, listener: (...args: any[]) => any): any;
|
|
112
|
+
on(event: string | symbol, listener: (...args: any[]) => any): any;
|
|
113
|
+
once(event: string | symbol, listener: (...args: any[]) => any): any;
|
|
114
|
+
prependListener(event: string | symbol, listener: (...args: any[]) => any): any;
|
|
115
|
+
prependOnceListener(event: string | symbol, listener: (...args: any[]) => any): any;
|
|
116
|
+
rawListeners(event: string | symbol): ((...args: any[]) => any)[];
|
|
117
|
+
removeAllListeners(event?: string | symbol): any;
|
|
118
|
+
removeListener(event: string | symbol, listener: (...args: any[]) => any): any;
|
|
119
|
+
setMaxListeners(n: number): any;
|
|
120
|
+
}
|
|
17
121
|
|
|
18
122
|
/**
|
|
19
123
|
* Context key for storing active log level (enables per-request log levels)
|
|
20
124
|
*/
|
|
21
125
|
const LOG_LEVEL_KEY = createContextKey('autotel-edge-log-level');
|
|
22
126
|
|
|
23
|
-
export interface EdgeLogger {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
127
|
+
export interface EdgeLogger extends EdgeEventEmitter {
|
|
128
|
+
readonly version: string;
|
|
129
|
+
readonly name: string | undefined;
|
|
130
|
+
levels: LevelMapping;
|
|
131
|
+
useLevelLabels: boolean;
|
|
132
|
+
level: LevelWithSilentOrString;
|
|
133
|
+
readonly levelVal: number;
|
|
134
|
+
readonly msgPrefix: string | undefined;
|
|
135
|
+
onChild: OnChildCallback;
|
|
136
|
+
enabled: boolean;
|
|
137
|
+
info: LogFn;
|
|
138
|
+
error: LogFn;
|
|
139
|
+
warn: LogFn;
|
|
140
|
+
debug: LogFn;
|
|
141
|
+
trace: LogFn;
|
|
142
|
+
fatal: LogFn;
|
|
143
|
+
silent: LogFn;
|
|
144
|
+
/**
|
|
145
|
+
* Create a child logger with merged bindings.
|
|
146
|
+
* Like pino's child() — every log call from the child
|
|
147
|
+
* includes the parent's attrs plus the child's bindings.
|
|
148
|
+
*/
|
|
149
|
+
child(bindings: LogAttrs, options?: ChildLoggerOptions): EdgeLogger;
|
|
150
|
+
isLevelEnabled(level: LevelWithSilentOrString): boolean;
|
|
151
|
+
bindings(): LogAttrs;
|
|
152
|
+
setBindings(bindings: LogAttrs): void;
|
|
153
|
+
flush(cb?: (err?: Error) => void): void;
|
|
154
|
+
on(event: 'level-change', listener: LevelChangeEventListener): EdgeLogger;
|
|
155
|
+
addListener(
|
|
156
|
+
event: 'level-change',
|
|
157
|
+
listener: LevelChangeEventListener,
|
|
158
|
+
): EdgeLogger;
|
|
159
|
+
once(event: 'level-change', listener: LevelChangeEventListener): EdgeLogger;
|
|
160
|
+
prependListener(
|
|
161
|
+
event: 'level-change',
|
|
162
|
+
listener: LevelChangeEventListener,
|
|
163
|
+
): EdgeLogger;
|
|
164
|
+
prependOnceListener(
|
|
165
|
+
event: 'level-change',
|
|
166
|
+
listener: LevelChangeEventListener,
|
|
167
|
+
): EdgeLogger;
|
|
168
|
+
removeListener(
|
|
169
|
+
event: 'level-change',
|
|
170
|
+
listener: LevelChangeEventListener,
|
|
171
|
+
): EdgeLogger;
|
|
172
|
+
[key: string]: unknown;
|
|
28
173
|
}
|
|
29
174
|
|
|
175
|
+
const PINO_LEVELS: LevelMapping = {
|
|
176
|
+
values: {
|
|
177
|
+
trace: 10,
|
|
178
|
+
debug: 20,
|
|
179
|
+
info: 30,
|
|
180
|
+
warn: 40,
|
|
181
|
+
error: 50,
|
|
182
|
+
fatal: 60,
|
|
183
|
+
silent: Infinity,
|
|
184
|
+
},
|
|
185
|
+
labels: {
|
|
186
|
+
10: 'trace',
|
|
187
|
+
20: 'debug',
|
|
188
|
+
30: 'info',
|
|
189
|
+
40: 'warn',
|
|
190
|
+
50: 'error',
|
|
191
|
+
60: 'fatal',
|
|
192
|
+
[Infinity]: 'silent',
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
const LOGGER_VERSION = 'autotel-edge';
|
|
196
|
+
|
|
30
197
|
/**
|
|
31
198
|
* Get the active log level from context (if set)
|
|
32
199
|
* Falls back to undefined if no log level is set in context
|
|
@@ -50,7 +217,7 @@ export function getActiveLogLevel(): LogLevel | undefined {
|
|
|
50
217
|
* })
|
|
51
218
|
*
|
|
52
219
|
* // Disable logging temporarily
|
|
53
|
-
* runWithLogLevel('
|
|
220
|
+
* runWithLogLevel('silent', () => {
|
|
54
221
|
* log.info('This will NOT be logged')
|
|
55
222
|
* })
|
|
56
223
|
* ```
|
|
@@ -63,9 +230,11 @@ export function runWithLogLevel<T>(level: LogLevel, callback: () => T): T {
|
|
|
63
230
|
/**
|
|
64
231
|
* Get current trace context from active span
|
|
65
232
|
*/
|
|
66
|
-
function getTraceContext():
|
|
67
|
-
|
|
68
|
-
|
|
233
|
+
function getTraceContext(): {
|
|
234
|
+
traceId: string;
|
|
235
|
+
spanId: string;
|
|
236
|
+
correlationId: string;
|
|
237
|
+
} | null {
|
|
69
238
|
const span = trace.getActiveSpan();
|
|
70
239
|
if (!span) return null;
|
|
71
240
|
|
|
@@ -77,6 +246,184 @@ function getTraceContext():
|
|
|
77
246
|
};
|
|
78
247
|
}
|
|
79
248
|
|
|
249
|
+
function isRecord(value: unknown): value is LogAttrs {
|
|
250
|
+
return typeof value === 'object' && value !== null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function toErrorAttrs(error: unknown): LogAttrs {
|
|
254
|
+
if (error instanceof Error) {
|
|
255
|
+
return {
|
|
256
|
+
error: {
|
|
257
|
+
message: error.message,
|
|
258
|
+
type: error.name,
|
|
259
|
+
stack: error.stack,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { error: { message: String(error), type: 'Error' } };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function formatMessage(template: string, args: unknown[]): string {
|
|
268
|
+
if (args.length === 0) return template;
|
|
269
|
+
|
|
270
|
+
let argIndex = 0;
|
|
271
|
+
const formatted = template.replaceAll(/%[sdjifoO%]/g, (token) => {
|
|
272
|
+
if (token === '%%') return '%';
|
|
273
|
+
if (argIndex >= args.length) return token;
|
|
274
|
+
|
|
275
|
+
const value = args[argIndex++];
|
|
276
|
+
|
|
277
|
+
switch (token) {
|
|
278
|
+
case '%d':
|
|
279
|
+
case '%i':
|
|
280
|
+
case '%f': {
|
|
281
|
+
return String(Number(value));
|
|
282
|
+
}
|
|
283
|
+
case '%j': {
|
|
284
|
+
try {
|
|
285
|
+
return JSON.stringify(value);
|
|
286
|
+
} catch {
|
|
287
|
+
return '[Circular]';
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
case '%s': {
|
|
291
|
+
return String(value);
|
|
292
|
+
}
|
|
293
|
+
case '%o':
|
|
294
|
+
case '%O': {
|
|
295
|
+
try {
|
|
296
|
+
return JSON.stringify(value);
|
|
297
|
+
} catch {
|
|
298
|
+
return String(value);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
default: {
|
|
302
|
+
return token;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
return formatted;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ANSI color helpers (no dependencies)
|
|
311
|
+
const ANSI_RESET = '\u001B[0m';
|
|
312
|
+
const ANSI_DIM = '\u001B[2m';
|
|
313
|
+
const ANSI_BOLD = '\u001B[1m';
|
|
314
|
+
const ANSI_COLORS: Record<string, string> = {
|
|
315
|
+
fatal: '\u001B[41m\u001B[37m', // white on red bg
|
|
316
|
+
error: '\u001B[31m', // red
|
|
317
|
+
warn: '\u001B[33m', // yellow
|
|
318
|
+
info: '\u001B[36m', // cyan
|
|
319
|
+
debug: '\u001B[34m', // blue
|
|
320
|
+
trace: '\u001B[90m', // gray
|
|
321
|
+
};
|
|
322
|
+
const LEVEL_SYMBOLS: Record<string, string> = {
|
|
323
|
+
fatal: '✗',
|
|
324
|
+
error: '✗',
|
|
325
|
+
warn: '⚠',
|
|
326
|
+
info: '●',
|
|
327
|
+
debug: '◦',
|
|
328
|
+
trace: '…',
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
function formatPrettyTimestamp(): string {
|
|
332
|
+
const d = new Date();
|
|
333
|
+
const h = String(d.getHours()).padStart(2, '0');
|
|
334
|
+
const m = String(d.getMinutes()).padStart(2, '0');
|
|
335
|
+
const s = String(d.getSeconds()).padStart(2, '0');
|
|
336
|
+
const ms = String(d.getMilliseconds()).padStart(3, '0');
|
|
337
|
+
return `${h}:${m}:${s}.${ms}`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function formatPrettyAttrs(attrs: Record<string, any>): string {
|
|
341
|
+
const keys = Object.keys(attrs);
|
|
342
|
+
if (keys.length === 0) return '';
|
|
343
|
+
return '\n' + JSON.stringify(attrs, null, 2)
|
|
344
|
+
.split('\n')
|
|
345
|
+
.map((line) => ` ${line}`)
|
|
346
|
+
.join('\n');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function safeStringify(
|
|
350
|
+
obj: unknown,
|
|
351
|
+
depthLimit: number,
|
|
352
|
+
edgeLimit: number,
|
|
353
|
+
): string {
|
|
354
|
+
// Stack-based approach: track ancestors on the current path.
|
|
355
|
+
// Objects are added when entered and removed when the replacer
|
|
356
|
+
// moves back up, so shared (non-circular) references serialize
|
|
357
|
+
// normally while true cycles produce '[Circular]'.
|
|
358
|
+
const ancestors: object[] = [];
|
|
359
|
+
|
|
360
|
+
return JSON.stringify(obj, function replacer(this: any, _key: string, value: unknown) {
|
|
361
|
+
if (typeof value === 'object' && value !== null) {
|
|
362
|
+
// Unwind: `this` is the object that owns the current key.
|
|
363
|
+
// Pop ancestors until we find `this` (our direct parent).
|
|
364
|
+
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
|
|
365
|
+
ancestors.pop();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// True circular reference: value is already an ancestor
|
|
369
|
+
if (ancestors.includes(value as object)) return '[Circular]';
|
|
370
|
+
|
|
371
|
+
if (ancestors.length >= depthLimit) return '[Object]';
|
|
372
|
+
|
|
373
|
+
ancestors.push(value as object);
|
|
374
|
+
|
|
375
|
+
if (!Array.isArray(value)) {
|
|
376
|
+
const keys = Object.keys(value as Record<string, unknown>);
|
|
377
|
+
if (keys.length > edgeLimit) {
|
|
378
|
+
const truncated: Record<string, unknown> = {};
|
|
379
|
+
for (let i = 0; i < edgeLimit; i++) {
|
|
380
|
+
truncated[keys[i]] = (value as Record<string, unknown>)[keys[i]];
|
|
381
|
+
}
|
|
382
|
+
truncated['...'] = `[${keys.length - edgeLimit} more properties]`;
|
|
383
|
+
return truncated;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return value;
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function parseLogArgs(args: unknown[]): { msg: string; attrs?: LogAttrs } {
|
|
392
|
+
const [first, ...rest] = args;
|
|
393
|
+
|
|
394
|
+
if (typeof first === 'string') {
|
|
395
|
+
return {
|
|
396
|
+
msg: formatMessage(first, rest),
|
|
397
|
+
attrs: undefined,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (first instanceof Error) {
|
|
402
|
+
return {
|
|
403
|
+
msg:
|
|
404
|
+
typeof rest[0] === 'string'
|
|
405
|
+
? formatMessage(rest[0], rest.slice(1))
|
|
406
|
+
: first.message,
|
|
407
|
+
attrs: toErrorAttrs(first),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (isRecord(first)) {
|
|
412
|
+
return {
|
|
413
|
+
msg:
|
|
414
|
+
typeof rest[0] === 'string'
|
|
415
|
+
? formatMessage(rest[0], rest.slice(1))
|
|
416
|
+
: '',
|
|
417
|
+
attrs: first,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
msg: String(first ?? ''),
|
|
423
|
+
attrs: undefined,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
80
427
|
/**
|
|
81
428
|
* Create a lightweight structured logger
|
|
82
429
|
*
|
|
@@ -87,7 +434,7 @@ function getTraceContext():
|
|
|
87
434
|
* ```typescript
|
|
88
435
|
* const log = createEdgeLogger('user-service')
|
|
89
436
|
*
|
|
90
|
-
* log.info(
|
|
437
|
+
* log.info({ email: 'test@example.com' }, 'Creating user')
|
|
91
438
|
* // Output: {"level":"info","service":"user-service","msg":"Creating user",
|
|
92
439
|
* // "email":"test@example.com","traceId":"...","spanId":"..."}
|
|
93
440
|
*
|
|
@@ -97,86 +444,571 @@ function getTraceContext():
|
|
|
97
444
|
* })
|
|
98
445
|
* ```
|
|
99
446
|
*/
|
|
447
|
+
export type EdgeLoggerOptions = {
|
|
448
|
+
level?: LevelWithSilentOrString;
|
|
449
|
+
pretty?: boolean;
|
|
450
|
+
bindings?: Record<string, any>;
|
|
451
|
+
redact?: RedactorConfig | string[];
|
|
452
|
+
msgPrefix?: string;
|
|
453
|
+
useLevelLabels?: boolean;
|
|
454
|
+
onChild?: OnChildCallback;
|
|
455
|
+
enabled?: boolean;
|
|
456
|
+
messageKey?: string;
|
|
457
|
+
errorKey?: string;
|
|
458
|
+
nestedKey?: string;
|
|
459
|
+
serializers?: Record<string, SerializerFn>;
|
|
460
|
+
formatters?: {
|
|
461
|
+
level?: LevelFormatter;
|
|
462
|
+
bindings?: BindingsFormatter;
|
|
463
|
+
log?: LogFormatter;
|
|
464
|
+
};
|
|
465
|
+
mixin?: MixinFn;
|
|
466
|
+
mixinMergeStrategy?: MixinMergeStrategyFn;
|
|
467
|
+
customLevels?: Record<string, number>;
|
|
468
|
+
useOnlyCustomLevels?: boolean;
|
|
469
|
+
levelComparison?: 'ASC' | 'DESC' | ((current: number, expected: number) => boolean);
|
|
470
|
+
name?: string;
|
|
471
|
+
base?: Record<string, any> | null;
|
|
472
|
+
timestamp?: TimeFn | boolean;
|
|
473
|
+
safe?: boolean;
|
|
474
|
+
crlf?: boolean;
|
|
475
|
+
depthLimit?: number;
|
|
476
|
+
edgeLimit?: number;
|
|
477
|
+
hooks?: {
|
|
478
|
+
logMethod?: (
|
|
479
|
+
args: Parameters<LogFn>,
|
|
480
|
+
method: LogFn,
|
|
481
|
+
level: number,
|
|
482
|
+
) => void;
|
|
483
|
+
};
|
|
484
|
+
write?: WriteFn | ({ [level: string]: WriteFn });
|
|
485
|
+
transmit?: {
|
|
486
|
+
level?: LevelWithSilentOrString;
|
|
487
|
+
send: (level: string, logEvent: LogEvent) => void;
|
|
488
|
+
};
|
|
489
|
+
/** @internal Binding chain for hierarchical transmit bindings */
|
|
490
|
+
_bindingsChain?: LogAttrs[];
|
|
491
|
+
};
|
|
492
|
+
|
|
100
493
|
export function createEdgeLogger(
|
|
101
494
|
service: string,
|
|
102
|
-
options?:
|
|
103
|
-
level?: LogLevel;
|
|
104
|
-
pretty?: boolean; // For development
|
|
105
|
-
},
|
|
495
|
+
options?: EdgeLoggerOptions,
|
|
106
496
|
): EdgeLogger {
|
|
107
|
-
const
|
|
497
|
+
const customLevels: Record<string, number> = {
|
|
498
|
+
...options?.customLevels,
|
|
499
|
+
};
|
|
500
|
+
const availableLevels: Record<string, number> = options?.useOnlyCustomLevels
|
|
501
|
+
? { ...customLevels, silent: Infinity }
|
|
502
|
+
: { ...PINO_LEVELS.values, ...customLevels };
|
|
503
|
+
const defaultLevel = options?.level ?? (options?.useOnlyCustomLevels ? Object.keys(customLevels)[0] : 'info') ?? 'info';
|
|
504
|
+
let currentLevel = defaultLevel as LevelWithSilentOrString;
|
|
108
505
|
const pretty = options?.pretty || false;
|
|
506
|
+
const currentBindings = { ...options?.bindings };
|
|
507
|
+
const resolvedRedact = Array.isArray(options?.redact)
|
|
508
|
+
? { paths: options.redact }
|
|
509
|
+
: options?.redact;
|
|
510
|
+
const redactor = resolvedRedact ? createRedactor(resolvedRedact) : null;
|
|
511
|
+
const msgPrefix = options?.msgPrefix;
|
|
512
|
+
const levelValues = availableLevels;
|
|
513
|
+
const levelListeners: LevelChangeEventListener[] = [];
|
|
514
|
+
const onChildCallback: OnChildCallback = options?.onChild ?? (() => {});
|
|
515
|
+
const listeners = new Map<string | symbol, EventListener[]>();
|
|
516
|
+
let maxListeners = 10;
|
|
517
|
+
const messageKey = options?.messageKey ?? 'msg';
|
|
518
|
+
const errorKey = options?.errorKey ?? 'err';
|
|
519
|
+
const nestedKey = options?.nestedKey;
|
|
520
|
+
const serializers = { ...options?.serializers };
|
|
521
|
+
const bindingsFormatter = options?.formatters?.bindings;
|
|
522
|
+
const levelFormatter = options?.formatters?.level;
|
|
523
|
+
const logFormatter = options?.formatters?.log;
|
|
524
|
+
const mixin = options?.mixin;
|
|
525
|
+
const levelComparison = options?.levelComparison;
|
|
526
|
+
const mixinMergeStrategy =
|
|
527
|
+
options?.mixinMergeStrategy ?? ((mergeObject: object, mixinObject: object) => ({ ...mergeObject, ...mixinObject }));
|
|
528
|
+
let enabled = options?.enabled ?? true;
|
|
529
|
+
const loggerName = options?.name;
|
|
530
|
+
const base = options?.base;
|
|
531
|
+
const timestamp = options?.timestamp ?? true;
|
|
532
|
+
const safe = options?.safe ?? true;
|
|
533
|
+
const crlf = options?.crlf ?? false;
|
|
534
|
+
const depthLimit = options?.depthLimit ?? 5;
|
|
535
|
+
const edgeLimitOpt = options?.edgeLimit ?? 100;
|
|
536
|
+
const hooks = options?.hooks;
|
|
537
|
+
const writeFn = options?.write;
|
|
538
|
+
const transmit = options?.transmit;
|
|
539
|
+
const bindingsChain: LogAttrs[] = options?._bindingsChain ?? [];
|
|
109
540
|
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
541
|
+
const compareLevels = (
|
|
542
|
+
current: number,
|
|
543
|
+
expected: number,
|
|
544
|
+
comparison: 'ASC' | 'DESC' | ((current: number, expected: number) => boolean) | undefined,
|
|
545
|
+
) => {
|
|
546
|
+
if (typeof comparison === 'function') return comparison(current, expected);
|
|
547
|
+
if (comparison === 'DESC') return current <= expected;
|
|
548
|
+
return current >= expected;
|
|
116
549
|
};
|
|
117
550
|
|
|
118
|
-
const
|
|
551
|
+
const addListener = (
|
|
552
|
+
event: string | symbol,
|
|
553
|
+
listener: EventListener,
|
|
554
|
+
prepend = false,
|
|
555
|
+
) => {
|
|
556
|
+
const activeListeners = listeners.get(event) ?? [];
|
|
557
|
+
if (prepend) {
|
|
558
|
+
activeListeners.unshift(listener);
|
|
559
|
+
} else {
|
|
560
|
+
activeListeners.push(listener);
|
|
561
|
+
}
|
|
562
|
+
listeners.set(event, activeListeners);
|
|
563
|
+
if (event === 'level-change') {
|
|
564
|
+
levelListeners.length = 0;
|
|
565
|
+
levelListeners.push(
|
|
566
|
+
...(listeners.get(event) as LevelChangeEventListener[] | undefined ?? []),
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
const notifyLevelChange = (
|
|
572
|
+
nextLevel: LevelWithSilentOrString,
|
|
573
|
+
previousLevel: LevelWithSilentOrString,
|
|
574
|
+
instance: EdgeLogger,
|
|
575
|
+
) => {
|
|
576
|
+
const nextValue = levelValues[nextLevel] ?? Infinity;
|
|
577
|
+
const previousValue = levelValues[previousLevel] ?? Infinity;
|
|
578
|
+
const snapshot = [...levelListeners];
|
|
579
|
+
for (const listener of snapshot) {
|
|
580
|
+
listener(nextLevel, nextValue, previousLevel, previousValue, instance);
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const removeEventListener = (event: string | symbol, listener: EventListener) => {
|
|
585
|
+
const activeListeners = listeners.get(event);
|
|
586
|
+
if (!activeListeners) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
const index = activeListeners.indexOf(listener);
|
|
590
|
+
if (index !== -1) {
|
|
591
|
+
activeListeners.splice(index, 1);
|
|
592
|
+
}
|
|
593
|
+
if (activeListeners.length === 0) {
|
|
594
|
+
listeners.delete(event);
|
|
595
|
+
} else {
|
|
596
|
+
listeners.set(event, activeListeners);
|
|
597
|
+
}
|
|
598
|
+
if (event === 'level-change') {
|
|
599
|
+
levelListeners.length = 0;
|
|
600
|
+
levelListeners.push(
|
|
601
|
+
...((listeners.get(event) as LevelChangeEventListener[] | undefined) ??
|
|
602
|
+
[]),
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
const shouldLog = (level: LevelWithSilentOrString): boolean => {
|
|
608
|
+
if (!enabled) return false;
|
|
609
|
+
|
|
119
610
|
// Priority: context level > options level > 'info' default
|
|
120
|
-
const activeLevel = getActiveLogLevel() ??
|
|
611
|
+
const activeLevel = getActiveLogLevel() ?? currentLevel;
|
|
612
|
+
|
|
613
|
+
// 'silent' means suppress all logging
|
|
614
|
+
if (activeLevel === 'silent') return false;
|
|
615
|
+
const currentValue = levelValues[activeLevel];
|
|
616
|
+
const expectedValue = levelValues[level];
|
|
617
|
+
if (currentValue === undefined || expectedValue === undefined) return false;
|
|
618
|
+
|
|
619
|
+
return compareLevels(
|
|
620
|
+
expectedValue,
|
|
621
|
+
currentValue,
|
|
622
|
+
levelComparison,
|
|
623
|
+
);
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const stringify = (obj: unknown): string => {
|
|
627
|
+
if (safe) {
|
|
628
|
+
return safeStringify(obj, depthLimit, edgeLimitOpt);
|
|
629
|
+
}
|
|
630
|
+
return JSON.stringify(obj);
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
const getTimestamp = (): string | undefined => {
|
|
634
|
+
if (timestamp === false) return undefined;
|
|
635
|
+
if (typeof timestamp === 'function') return timestamp();
|
|
636
|
+
return new Date().toISOString();
|
|
637
|
+
};
|
|
121
638
|
|
|
122
|
-
|
|
123
|
-
if (
|
|
639
|
+
const writeOutput = (level: LevelWithSilentOrString, logObject: Record<string, any>) => {
|
|
640
|
+
if (writeFn) {
|
|
641
|
+
if (typeof writeFn === 'function') {
|
|
642
|
+
writeFn(logObject);
|
|
643
|
+
} else if (typeof writeFn === 'object' && writeFn[level]) {
|
|
644
|
+
writeFn[level](logObject);
|
|
645
|
+
} else {
|
|
646
|
+
console.log(stringify(logObject));
|
|
647
|
+
}
|
|
648
|
+
} else if (pretty) {
|
|
649
|
+
return; // pretty mode handles its own output
|
|
650
|
+
} else {
|
|
651
|
+
const json = stringify(logObject);
|
|
652
|
+
console.log(crlf ? json + '\r\n' : json);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
124
655
|
|
|
125
|
-
|
|
656
|
+
const applySerializers = (obj: Record<string, any>): Record<string, any> => {
|
|
657
|
+
if (Object.keys(serializers).length === 0) return obj;
|
|
658
|
+
return Object.fromEntries(
|
|
659
|
+
Object.entries(obj).map(([key, value]) => [
|
|
660
|
+
key,
|
|
661
|
+
key in serializers ? serializers[key](value) : value,
|
|
662
|
+
]),
|
|
663
|
+
);
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
const emitTransmit = (
|
|
667
|
+
level: LevelWithSilentOrString,
|
|
668
|
+
rawArgs: unknown[],
|
|
669
|
+
) => {
|
|
670
|
+
if (!transmit) return;
|
|
671
|
+
if (transmit.level) {
|
|
672
|
+
const transmitValue = levelValues[transmit.level];
|
|
673
|
+
const logValue = levelValues[level];
|
|
674
|
+
if (transmitValue !== undefined && logValue !== undefined && logValue < transmitValue) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
// Apply serializers then redaction to messages
|
|
679
|
+
const processedMessages = rawArgs.map((m) => {
|
|
680
|
+
if (typeof m !== 'object' || m === null) return m;
|
|
681
|
+
const serialized = applySerializers(m as Record<string, any>);
|
|
682
|
+
return redactor ? redactor(serialized) : serialized;
|
|
683
|
+
});
|
|
684
|
+
// Apply serializers then redaction to bindings
|
|
685
|
+
const processedBindings = bindingsChain.map((b) => {
|
|
686
|
+
const serialized = applySerializers({ ...b });
|
|
687
|
+
return redactor ? (redactor(serialized) as LogAttrs) : serialized;
|
|
688
|
+
});
|
|
689
|
+
const logEvent: LogEvent = {
|
|
690
|
+
ts: Date.now(),
|
|
691
|
+
messages: processedMessages,
|
|
692
|
+
bindings: processedBindings,
|
|
693
|
+
level: { label: level, value: levelValues[level] },
|
|
694
|
+
};
|
|
695
|
+
transmit.send(level, logEvent);
|
|
126
696
|
};
|
|
127
697
|
|
|
128
698
|
const log = (
|
|
129
|
-
level:
|
|
699
|
+
level: LevelWithSilentOrString,
|
|
130
700
|
msg: string,
|
|
131
701
|
attrs?: Record<string, any>,
|
|
702
|
+
rawArgs?: unknown[],
|
|
132
703
|
) => {
|
|
133
704
|
if (!shouldLog(level)) return;
|
|
134
705
|
|
|
135
706
|
const ctx = getTraceContext();
|
|
707
|
+
const message = msgPrefix ? `${msgPrefix}${msg}` : msg;
|
|
708
|
+
const baseBindings = bindingsFormatter
|
|
709
|
+
? (bindingsFormatter({ ...currentBindings }) as LogAttrs)
|
|
710
|
+
: { ...currentBindings };
|
|
711
|
+
const serializedAttrs = attrs
|
|
712
|
+
? Object.fromEntries(
|
|
713
|
+
Object.entries(attrs).map(([key, value]) => [
|
|
714
|
+
key,
|
|
715
|
+
key in serializers ? serializers[key](value) : value,
|
|
716
|
+
]),
|
|
717
|
+
)
|
|
718
|
+
: undefined;
|
|
719
|
+
const mergeObject = nestedKey
|
|
720
|
+
? ({ [nestedKey]: serializedAttrs ?? {} } as Record<string, unknown>)
|
|
721
|
+
: (serializedAttrs ?? {});
|
|
722
|
+
const mixinObject =
|
|
723
|
+
mixin?.(mergeObject, levelValues[level], logger) ?? {};
|
|
724
|
+
const mergedLogObject = mixinMergeStrategy(mergeObject, mixinObject) as Record<
|
|
725
|
+
string,
|
|
726
|
+
unknown
|
|
727
|
+
>;
|
|
728
|
+
const formattedLogObject = logFormatter
|
|
729
|
+
? logFormatter(mergedLogObject)
|
|
730
|
+
: mergedLogObject;
|
|
731
|
+
const formattedLevel = levelFormatter
|
|
732
|
+
? levelFormatter(level, levelValues[level])
|
|
733
|
+
: {
|
|
734
|
+
level: logger.useLevelLabels ? level : levelValues[level],
|
|
735
|
+
};
|
|
736
|
+
const ts = getTimestamp();
|
|
136
737
|
const logEntry: Record<string, any> = {
|
|
137
|
-
|
|
738
|
+
...formattedLevel,
|
|
739
|
+
...(loggerName === undefined ? {} : { name: loggerName }),
|
|
138
740
|
service,
|
|
139
|
-
|
|
140
|
-
...
|
|
741
|
+
...(message === '' ? {} : { [messageKey]: message }),
|
|
742
|
+
...(base === null || base === undefined ? {} : base),
|
|
743
|
+
...baseBindings,
|
|
744
|
+
...formattedLogObject,
|
|
141
745
|
...ctx, // Auto-inject traceId, spanId, correlationId
|
|
142
|
-
timestamp:
|
|
746
|
+
...(ts === undefined ? {} : { timestamp: ts }),
|
|
143
747
|
};
|
|
144
748
|
|
|
145
|
-
if (
|
|
749
|
+
if (serializedAttrs && !nestedKey && errorKey !== 'error' && 'error' in logEntry) {
|
|
750
|
+
logEntry[errorKey] = logEntry.error;
|
|
751
|
+
delete logEntry.error;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (pretty && !writeFn) {
|
|
146
755
|
// Pretty print for development
|
|
756
|
+
const color = ANSI_COLORS[level] ?? ANSI_COLORS.info;
|
|
757
|
+
const symbol = LEVEL_SYMBOLS[level] ?? '●';
|
|
758
|
+
const time = formatPrettyTimestamp();
|
|
147
759
|
const traceInfo = ctx
|
|
148
|
-
? ` [${ctx.traceId.slice(0, 8)}
|
|
760
|
+
? ` ${ANSI_DIM}[${ctx.traceId.slice(0, 8)}/${ctx.spanId.slice(0, 8)}]${ANSI_RESET}`
|
|
149
761
|
: '';
|
|
762
|
+
const prettyAttrs = {
|
|
763
|
+
...baseBindings,
|
|
764
|
+
...formattedLogObject,
|
|
765
|
+
};
|
|
766
|
+
const safeAttrs = redactor
|
|
767
|
+
? (redactor(prettyAttrs) as LogAttrs)
|
|
768
|
+
: prettyAttrs;
|
|
769
|
+
const attrsStr = formatPrettyAttrs(safeAttrs);
|
|
150
770
|
console.log(
|
|
151
|
-
|
|
152
|
-
attrs || '',
|
|
771
|
+
`${ANSI_DIM}${time}${ANSI_RESET} ${color}${ANSI_BOLD}${symbol} ${level.toUpperCase()}${ANSI_RESET}${traceInfo} ${ANSI_DIM}(${service})${ANSI_RESET} ${message}${attrsStr}`,
|
|
153
772
|
);
|
|
154
773
|
} else {
|
|
155
|
-
// Structured JSON for production
|
|
156
|
-
|
|
774
|
+
// Structured JSON for production (or custom write)
|
|
775
|
+
const safeEntry = redactor ? (redactor(logEntry) as LogAttrs) : logEntry;
|
|
776
|
+
writeOutput(level, safeEntry);
|
|
157
777
|
}
|
|
778
|
+
|
|
779
|
+
emitTransmit(level, rawArgs ?? []);
|
|
158
780
|
};
|
|
159
781
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
782
|
+
const makeLogMethod = (levelName: string): LogFn => {
|
|
783
|
+
return ((...args: unknown[]) => {
|
|
784
|
+
if (hooks?.logMethod) {
|
|
785
|
+
const method = ((...methodArgs: unknown[]) => {
|
|
786
|
+
const { msg, attrs } = parseLogArgs(methodArgs);
|
|
787
|
+
log(levelName, msg, attrs, methodArgs);
|
|
788
|
+
}) as LogFn;
|
|
789
|
+
hooks.logMethod.call(logger, args as Parameters<LogFn>, method, levelValues[levelName]);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const { msg, attrs } = parseLogArgs(args);
|
|
793
|
+
log(levelName, msg, attrs, args);
|
|
794
|
+
}) as LogFn;
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
const logger: EdgeLogger = {
|
|
798
|
+
version: LOGGER_VERSION,
|
|
799
|
+
|
|
800
|
+
get name() {
|
|
801
|
+
return loggerName;
|
|
802
|
+
},
|
|
803
|
+
|
|
804
|
+
levels: {
|
|
805
|
+
values: levelValues,
|
|
806
|
+
labels: Object.fromEntries(
|
|
807
|
+
Object.entries(levelValues).map(([label, value]) => [value, label]),
|
|
808
|
+
),
|
|
809
|
+
},
|
|
810
|
+
useLevelLabels: options?.useLevelLabels ?? false,
|
|
811
|
+
customLevels: { ...customLevels },
|
|
812
|
+
useOnlyCustomLevels: options?.useOnlyCustomLevels ?? false,
|
|
813
|
+
|
|
814
|
+
get level() {
|
|
815
|
+
return currentLevel;
|
|
816
|
+
},
|
|
817
|
+
|
|
818
|
+
set level(level: LevelWithSilentOrString) {
|
|
819
|
+
const previousLevel = currentLevel;
|
|
820
|
+
currentLevel = level;
|
|
821
|
+
notifyLevelChange(level, previousLevel, logger);
|
|
822
|
+
},
|
|
172
823
|
|
|
173
|
-
|
|
824
|
+
get levelVal() {
|
|
825
|
+
return levelValues[currentLevel] ?? Infinity;
|
|
174
826
|
},
|
|
175
827
|
|
|
176
|
-
|
|
828
|
+
get msgPrefix() {
|
|
829
|
+
return msgPrefix;
|
|
830
|
+
},
|
|
831
|
+
|
|
832
|
+
onChild: onChildCallback,
|
|
833
|
+
get enabled() {
|
|
834
|
+
return enabled;
|
|
835
|
+
},
|
|
836
|
+
|
|
837
|
+
set enabled(value: boolean) {
|
|
838
|
+
enabled = value;
|
|
839
|
+
},
|
|
840
|
+
|
|
841
|
+
info: makeLogMethod('info'),
|
|
842
|
+
|
|
843
|
+
error: makeLogMethod('error'),
|
|
844
|
+
|
|
845
|
+
warn: makeLogMethod('warn'),
|
|
846
|
+
|
|
847
|
+
debug: makeLogMethod('debug'),
|
|
177
848
|
|
|
178
|
-
|
|
849
|
+
trace: makeLogMethod('trace'),
|
|
850
|
+
|
|
851
|
+
fatal: makeLogMethod('fatal'),
|
|
852
|
+
|
|
853
|
+
silent: (() => {}) as LogFn,
|
|
854
|
+
|
|
855
|
+
child: (bindings: LogAttrs, childOptions?: ChildLoggerOptions) => {
|
|
856
|
+
const childLogger = createEdgeLogger(service, {
|
|
857
|
+
level: childOptions?.level ?? currentLevel,
|
|
858
|
+
pretty,
|
|
859
|
+
bindings: { ...currentBindings, ...bindings },
|
|
860
|
+
redact: childOptions?.redact ?? options?.redact,
|
|
861
|
+
msgPrefix:
|
|
862
|
+
childOptions?.msgPrefix === undefined
|
|
863
|
+
? msgPrefix
|
|
864
|
+
: `${msgPrefix ?? ''}${childOptions.msgPrefix}`,
|
|
865
|
+
useLevelLabels: logger.useLevelLabels,
|
|
866
|
+
onChild: onChildCallback,
|
|
867
|
+
enabled,
|
|
868
|
+
messageKey,
|
|
869
|
+
errorKey,
|
|
870
|
+
nestedKey,
|
|
871
|
+
serializers: { ...serializers, ...childOptions?.serializers },
|
|
872
|
+
formatters: {
|
|
873
|
+
level: childOptions?.formatters?.level ?? levelFormatter,
|
|
874
|
+
bindings: childOptions?.formatters?.bindings ?? bindingsFormatter,
|
|
875
|
+
log: childOptions?.formatters?.log ?? logFormatter,
|
|
876
|
+
},
|
|
877
|
+
mixin,
|
|
878
|
+
mixinMergeStrategy,
|
|
879
|
+
customLevels,
|
|
880
|
+
useOnlyCustomLevels: options?.useOnlyCustomLevels,
|
|
881
|
+
levelComparison: options?.levelComparison,
|
|
882
|
+
name: loggerName,
|
|
883
|
+
base,
|
|
884
|
+
timestamp,
|
|
885
|
+
safe,
|
|
886
|
+
crlf,
|
|
887
|
+
depthLimit,
|
|
888
|
+
edgeLimit: edgeLimitOpt,
|
|
889
|
+
hooks,
|
|
890
|
+
write: writeFn,
|
|
891
|
+
transmit,
|
|
892
|
+
_bindingsChain: [...bindingsChain, bindings],
|
|
893
|
+
});
|
|
894
|
+
onChildCallback(childLogger);
|
|
895
|
+
return childLogger;
|
|
896
|
+
},
|
|
897
|
+
|
|
898
|
+
isLevelEnabled: (level: LevelWithSilentOrString) => shouldLog(level),
|
|
899
|
+
|
|
900
|
+
bindings: () => ({ ...currentBindings }),
|
|
901
|
+
|
|
902
|
+
setBindings: (bindings: LogAttrs) => {
|
|
903
|
+
for (const [key, value] of Object.entries(bindings)) {
|
|
904
|
+
if (!(key in currentBindings)) {
|
|
905
|
+
currentBindings[key] = value;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
},
|
|
909
|
+
|
|
910
|
+
flush: (cb?: (err?: Error) => void) => {
|
|
911
|
+
cb?.();
|
|
912
|
+
},
|
|
913
|
+
|
|
914
|
+
emit: (event: string | symbol, ...args: any[]) => {
|
|
915
|
+
const activeListeners = listeners.get(event);
|
|
916
|
+
if (!activeListeners || activeListeners.length === 0) {
|
|
917
|
+
return false;
|
|
918
|
+
}
|
|
919
|
+
const snapshot = [...activeListeners];
|
|
920
|
+
for (const listener of snapshot) {
|
|
921
|
+
listener(...args);
|
|
922
|
+
}
|
|
923
|
+
return true;
|
|
924
|
+
},
|
|
925
|
+
|
|
926
|
+
eventNames: () => [...listeners.keys()],
|
|
927
|
+
|
|
928
|
+
getMaxListeners: () => maxListeners,
|
|
929
|
+
|
|
930
|
+
listenerCount: (event: string | symbol) => listeners.get(event)?.length ?? 0,
|
|
931
|
+
|
|
932
|
+
listeners: (event: string | symbol) => [...(listeners.get(event) ?? [])],
|
|
933
|
+
|
|
934
|
+
on: (event: string | symbol, listener: EventListener) => {
|
|
935
|
+
addListener(event, listener);
|
|
936
|
+
return logger;
|
|
937
|
+
},
|
|
938
|
+
|
|
939
|
+
addListener: (event: string | symbol, listener: EventListener) => {
|
|
940
|
+
addListener(event, listener);
|
|
941
|
+
return logger;
|
|
942
|
+
},
|
|
943
|
+
|
|
944
|
+
once: (event: string | symbol, listener: EventListener) => {
|
|
945
|
+
const wrapped: EventListener = (...args: any[]) => {
|
|
946
|
+
removeEventListener(event, wrapped);
|
|
947
|
+
listener(...args);
|
|
948
|
+
};
|
|
949
|
+
addListener(event, wrapped);
|
|
950
|
+
return logger;
|
|
951
|
+
},
|
|
952
|
+
|
|
953
|
+
prependListener: (
|
|
954
|
+
event: string | symbol,
|
|
955
|
+
listener: EventListener,
|
|
956
|
+
) => {
|
|
957
|
+
addListener(event, listener, true);
|
|
958
|
+
return logger;
|
|
959
|
+
},
|
|
960
|
+
|
|
961
|
+
prependOnceListener: (
|
|
962
|
+
event: string | symbol,
|
|
963
|
+
listener: EventListener,
|
|
964
|
+
) => {
|
|
965
|
+
const wrapped: EventListener = (...args: any[]) => {
|
|
966
|
+
removeEventListener(event, wrapped);
|
|
967
|
+
listener(...args);
|
|
968
|
+
};
|
|
969
|
+
addListener(event, wrapped, true);
|
|
970
|
+
return logger;
|
|
971
|
+
},
|
|
972
|
+
|
|
973
|
+
rawListeners: (event: string | symbol) => [...(listeners.get(event) ?? [])],
|
|
974
|
+
|
|
975
|
+
off: (event: string | symbol, listener: EventListener) => {
|
|
976
|
+
removeEventListener(event, listener);
|
|
977
|
+
return logger;
|
|
978
|
+
},
|
|
979
|
+
|
|
980
|
+
removeAllListeners: (event?: string | symbol) => {
|
|
981
|
+
if (event === undefined) {
|
|
982
|
+
listeners.clear();
|
|
983
|
+
levelListeners.length = 0;
|
|
984
|
+
return logger;
|
|
985
|
+
}
|
|
986
|
+
listeners.delete(event);
|
|
987
|
+
if (event === 'level-change') {
|
|
988
|
+
levelListeners.length = 0;
|
|
989
|
+
}
|
|
990
|
+
return logger;
|
|
991
|
+
},
|
|
992
|
+
|
|
993
|
+
removeListener: (
|
|
994
|
+
event: string | symbol,
|
|
995
|
+
listener: EventListener,
|
|
996
|
+
) => {
|
|
997
|
+
removeEventListener(event, listener);
|
|
998
|
+
return logger;
|
|
999
|
+
},
|
|
1000
|
+
|
|
1001
|
+
setMaxListeners: (n: number) => {
|
|
1002
|
+
maxListeners = n;
|
|
1003
|
+
return logger;
|
|
1004
|
+
},
|
|
179
1005
|
};
|
|
1006
|
+
|
|
1007
|
+
for (const levelName of Object.keys(customLevels)) {
|
|
1008
|
+
logger[levelName] = makeLogMethod(levelName);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
return logger;
|
|
180
1012
|
}
|
|
181
1013
|
|
|
182
1014
|
/**
|