autotel-edge 3.17.0 → 3.17.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/LICENSE +191 -21
- package/README.md +2 -2
- package/dist/{config-CEF9DMGT.js → config-CFlfvmQ1.js} +2 -2
- package/dist/{config-CEF9DMGT.js.map → config-CFlfvmQ1.js.map} +1 -1
- package/dist/events.js +13 -9
- package/dist/events.js.map +1 -1
- package/dist/{execution-logger-CoxSsBmU.js → execution-logger-CSW2AyVi.js} +7 -4
- package/dist/{execution-logger-CoxSsBmU.js.map → execution-logger-CSW2AyVi.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/logger.js +7 -3
- package/dist/logger.js.map +1 -1
- package/package.json +5 -6
- package/src/api/logger.test.ts +0 -1616
- package/src/api/logger.ts +0 -1038
- package/src/api/redact.test.ts +0 -432
- package/src/api/redact.ts +0 -402
- package/src/compose.ts +0 -243
- package/src/composition.test.ts +0 -130
- package/src/composition.ts +0 -132
- package/src/core/buffer.ts +0 -17
- package/src/core/config.test.ts +0 -413
- package/src/core/config.ts +0 -169
- package/src/core/context.ts +0 -245
- package/src/core/exporter.ts +0 -99
- package/src/core/native-bridge.test.ts +0 -176
- package/src/core/native-bridge.ts +0 -278
- package/src/core/provider.context.test.ts +0 -55
- package/src/core/provider.ts +0 -46
- package/src/core/span.ts +0 -222
- package/src/core/spanprocessor.test.ts +0 -521
- package/src/core/spanprocessor.ts +0 -232
- package/src/core/trace-context.ts +0 -95
- package/src/core/tracer.test.ts +0 -123
- package/src/core/tracer.ts +0 -216
- package/src/events/index.test.ts +0 -242
- package/src/events/index.ts +0 -338
- package/src/events.ts +0 -6
- package/src/execution-logger.test.ts +0 -368
- package/src/execution-logger.ts +0 -410
- package/src/functional.test.ts +0 -827
- package/src/functional.ts +0 -1002
- package/src/index.ts +0 -140
- package/src/logger.ts +0 -35
- package/src/middleware-toolkit.test.ts +0 -71
- package/src/middleware-toolkit.ts +0 -94
- package/src/native-routing.test.ts +0 -98
- package/src/parse-error.test.ts +0 -70
- package/src/parse-error.ts +0 -110
- package/src/plugin-runner.test.ts +0 -100
- package/src/plugin-runner.ts +0 -215
- package/src/sampling/index.test.ts +0 -297
- package/src/sampling/index.ts +0 -276
- package/src/sampling.ts +0 -6
- package/src/testing/index.ts +0 -9
- package/src/testing.ts +0 -6
- package/src/types.ts +0 -325
package/src/api/logger.ts
DELETED
|
@@ -1,1038 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pino-compatible structured logger for edge environments.
|
|
3
|
-
*
|
|
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
|
-
*
|
|
11
|
-
* The implementation stays dependency-light and console/write based so it
|
|
12
|
-
* works in Cloudflare Workers and similar edge runtimes.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
trace,
|
|
17
|
-
context as api_context,
|
|
18
|
-
createContextKey,
|
|
19
|
-
} from '@opentelemetry/api';
|
|
20
|
-
import { createRedactor, type RedactorConfig } from './redact';
|
|
21
|
-
|
|
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
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Context key for storing active log level (enables per-request log levels)
|
|
124
|
-
*/
|
|
125
|
-
const LOG_LEVEL_KEY = createContextKey('autotel-edge-log-level');
|
|
126
|
-
|
|
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;
|
|
173
|
-
}
|
|
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
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Get the active log level from context (if set)
|
|
199
|
-
* Falls back to undefined if no log level is set in context
|
|
200
|
-
*/
|
|
201
|
-
export function getActiveLogLevel(): LogLevel | undefined {
|
|
202
|
-
return api_context.active().getValue(LOG_LEVEL_KEY) as LogLevel | undefined;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Run a function with a specific log level
|
|
207
|
-
* The log level is stored in OpenTelemetry context and applies to all logger calls within the callback
|
|
208
|
-
*
|
|
209
|
-
* This works in edge runtimes (uses OTel context, not Node.js AsyncLocalStorage)
|
|
210
|
-
*
|
|
211
|
-
* @example
|
|
212
|
-
* ```typescript
|
|
213
|
-
* // Enable debug logging for a specific request
|
|
214
|
-
* runWithLogLevel('debug', () => {
|
|
215
|
-
* log.debug('This will be logged')
|
|
216
|
-
* processRequest()
|
|
217
|
-
* })
|
|
218
|
-
*
|
|
219
|
-
* // Disable logging temporarily
|
|
220
|
-
* runWithLogLevel('silent', () => {
|
|
221
|
-
* log.info('This will NOT be logged')
|
|
222
|
-
* })
|
|
223
|
-
* ```
|
|
224
|
-
*/
|
|
225
|
-
export function runWithLogLevel<T>(level: LogLevel, callback: () => T): T {
|
|
226
|
-
const ctx = api_context.active().setValue(LOG_LEVEL_KEY, level);
|
|
227
|
-
return api_context.with(ctx, callback);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Get current trace context from active span
|
|
232
|
-
*/
|
|
233
|
-
function getTraceContext(): {
|
|
234
|
-
traceId: string;
|
|
235
|
-
spanId: string;
|
|
236
|
-
correlationId: string;
|
|
237
|
-
} | null {
|
|
238
|
-
const span = trace.getActiveSpan();
|
|
239
|
-
if (!span) return null;
|
|
240
|
-
|
|
241
|
-
const ctx = span.spanContext();
|
|
242
|
-
return {
|
|
243
|
-
traceId: ctx.traceId,
|
|
244
|
-
spanId: ctx.spanId,
|
|
245
|
-
correlationId: ctx.traceId.slice(0, 16), // First 16 chars for grouping
|
|
246
|
-
};
|
|
247
|
-
}
|
|
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
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Create a lightweight structured logger
|
|
429
|
-
*
|
|
430
|
-
* @param service - Service name for logging
|
|
431
|
-
* @param options - Optional configuration
|
|
432
|
-
*
|
|
433
|
-
* @example
|
|
434
|
-
* ```typescript
|
|
435
|
-
* const log = createEdgeLogger('user-service')
|
|
436
|
-
*
|
|
437
|
-
* log.info({ email: 'test@example.com' }, 'Creating user')
|
|
438
|
-
* // Output: {"level":"info","service":"user-service","msg":"Creating user",
|
|
439
|
-
* // "email":"test@example.com","traceId":"...","spanId":"..."}
|
|
440
|
-
*
|
|
441
|
-
* // Dynamic log level control per-request
|
|
442
|
-
* runWithLogLevel('debug', () => {
|
|
443
|
-
* log.debug('This will be logged even if logger was created with level: "info"')
|
|
444
|
-
* })
|
|
445
|
-
* ```
|
|
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
|
-
|
|
493
|
-
export function createEdgeLogger(
|
|
494
|
-
service: string,
|
|
495
|
-
options?: EdgeLoggerOptions,
|
|
496
|
-
): EdgeLogger {
|
|
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;
|
|
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
|
-
// Default serializer for the error key, so `log.error({ err }, msg)` keeps the
|
|
522
|
-
// message/stack instead of JSON-stringifying an Error to `{}`. Mirrors the
|
|
523
|
-
// first-arg Error shape (toErrorAttrs). Overridable via options.serializers.
|
|
524
|
-
if (!(errorKey in serializers)) {
|
|
525
|
-
serializers[errorKey] = (value: unknown) =>
|
|
526
|
-
value instanceof Error
|
|
527
|
-
? { message: value.message, type: value.name, stack: value.stack }
|
|
528
|
-
: value;
|
|
529
|
-
}
|
|
530
|
-
const bindingsFormatter = options?.formatters?.bindings;
|
|
531
|
-
const levelFormatter = options?.formatters?.level;
|
|
532
|
-
const logFormatter = options?.formatters?.log;
|
|
533
|
-
const mixin = options?.mixin;
|
|
534
|
-
const levelComparison = options?.levelComparison;
|
|
535
|
-
const mixinMergeStrategy =
|
|
536
|
-
options?.mixinMergeStrategy ?? ((mergeObject: object, mixinObject: object) => ({ ...mergeObject, ...mixinObject }));
|
|
537
|
-
let enabled = options?.enabled ?? true;
|
|
538
|
-
const loggerName = options?.name;
|
|
539
|
-
const base = options?.base;
|
|
540
|
-
const timestamp = options?.timestamp ?? true;
|
|
541
|
-
const safe = options?.safe ?? true;
|
|
542
|
-
const crlf = options?.crlf ?? false;
|
|
543
|
-
const depthLimit = options?.depthLimit ?? 5;
|
|
544
|
-
const edgeLimitOpt = options?.edgeLimit ?? 100;
|
|
545
|
-
const hooks = options?.hooks;
|
|
546
|
-
const writeFn = options?.write;
|
|
547
|
-
const transmit = options?.transmit;
|
|
548
|
-
const bindingsChain: LogAttrs[] = options?._bindingsChain ?? [];
|
|
549
|
-
|
|
550
|
-
const compareLevels = (
|
|
551
|
-
current: number,
|
|
552
|
-
expected: number,
|
|
553
|
-
comparison: 'ASC' | 'DESC' | ((current: number, expected: number) => boolean) | undefined,
|
|
554
|
-
) => {
|
|
555
|
-
if (typeof comparison === 'function') return comparison(current, expected);
|
|
556
|
-
if (comparison === 'DESC') return current <= expected;
|
|
557
|
-
return current >= expected;
|
|
558
|
-
};
|
|
559
|
-
|
|
560
|
-
const addListener = (
|
|
561
|
-
event: string | symbol,
|
|
562
|
-
listener: EventListener,
|
|
563
|
-
prepend = false,
|
|
564
|
-
) => {
|
|
565
|
-
const activeListeners = listeners.get(event) ?? [];
|
|
566
|
-
if (prepend) {
|
|
567
|
-
activeListeners.unshift(listener);
|
|
568
|
-
} else {
|
|
569
|
-
activeListeners.push(listener);
|
|
570
|
-
}
|
|
571
|
-
listeners.set(event, activeListeners);
|
|
572
|
-
if (event === 'level-change') {
|
|
573
|
-
levelListeners.length = 0;
|
|
574
|
-
levelListeners.push(
|
|
575
|
-
...(listeners.get(event) as LevelChangeEventListener[] | undefined ?? []),
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
};
|
|
579
|
-
|
|
580
|
-
const notifyLevelChange = (
|
|
581
|
-
nextLevel: LevelWithSilentOrString,
|
|
582
|
-
previousLevel: LevelWithSilentOrString,
|
|
583
|
-
instance: EdgeLogger,
|
|
584
|
-
) => {
|
|
585
|
-
const nextValue = levelValues[nextLevel] ?? Infinity;
|
|
586
|
-
const previousValue = levelValues[previousLevel] ?? Infinity;
|
|
587
|
-
const snapshot = [...levelListeners];
|
|
588
|
-
for (const listener of snapshot) {
|
|
589
|
-
listener(nextLevel, nextValue, previousLevel, previousValue, instance);
|
|
590
|
-
}
|
|
591
|
-
};
|
|
592
|
-
|
|
593
|
-
const removeEventListener = (event: string | symbol, listener: EventListener) => {
|
|
594
|
-
const activeListeners = listeners.get(event);
|
|
595
|
-
if (!activeListeners) {
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
const index = activeListeners.indexOf(listener);
|
|
599
|
-
if (index !== -1) {
|
|
600
|
-
activeListeners.splice(index, 1);
|
|
601
|
-
}
|
|
602
|
-
if (activeListeners.length === 0) {
|
|
603
|
-
listeners.delete(event);
|
|
604
|
-
} else {
|
|
605
|
-
listeners.set(event, activeListeners);
|
|
606
|
-
}
|
|
607
|
-
if (event === 'level-change') {
|
|
608
|
-
levelListeners.length = 0;
|
|
609
|
-
levelListeners.push(
|
|
610
|
-
...((listeners.get(event) as LevelChangeEventListener[] | undefined) ??
|
|
611
|
-
[]),
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
};
|
|
615
|
-
|
|
616
|
-
const shouldLog = (level: LevelWithSilentOrString): boolean => {
|
|
617
|
-
if (!enabled) return false;
|
|
618
|
-
|
|
619
|
-
// Priority: context level > options level > 'info' default
|
|
620
|
-
const activeLevel = getActiveLogLevel() ?? currentLevel;
|
|
621
|
-
|
|
622
|
-
// 'silent' means suppress all logging
|
|
623
|
-
if (activeLevel === 'silent') return false;
|
|
624
|
-
const currentValue = levelValues[activeLevel];
|
|
625
|
-
const expectedValue = levelValues[level];
|
|
626
|
-
if (currentValue === undefined || expectedValue === undefined) return false;
|
|
627
|
-
|
|
628
|
-
return compareLevels(
|
|
629
|
-
expectedValue,
|
|
630
|
-
currentValue,
|
|
631
|
-
levelComparison,
|
|
632
|
-
);
|
|
633
|
-
};
|
|
634
|
-
|
|
635
|
-
const stringify = (obj: unknown): string => {
|
|
636
|
-
if (safe) {
|
|
637
|
-
return safeStringify(obj, depthLimit, edgeLimitOpt);
|
|
638
|
-
}
|
|
639
|
-
return JSON.stringify(obj);
|
|
640
|
-
};
|
|
641
|
-
|
|
642
|
-
const getTimestamp = (): string | undefined => {
|
|
643
|
-
if (timestamp === false) return undefined;
|
|
644
|
-
if (typeof timestamp === 'function') return timestamp();
|
|
645
|
-
return new Date().toISOString();
|
|
646
|
-
};
|
|
647
|
-
|
|
648
|
-
const writeOutput = (level: LevelWithSilentOrString, logObject: Record<string, any>) => {
|
|
649
|
-
if (writeFn) {
|
|
650
|
-
if (typeof writeFn === 'function') {
|
|
651
|
-
writeFn(logObject);
|
|
652
|
-
} else if (typeof writeFn === 'object' && writeFn[level]) {
|
|
653
|
-
writeFn[level](logObject);
|
|
654
|
-
} else {
|
|
655
|
-
console.log(stringify(logObject));
|
|
656
|
-
}
|
|
657
|
-
} else if (pretty) {
|
|
658
|
-
return; // pretty mode handles its own output
|
|
659
|
-
} else {
|
|
660
|
-
const json = stringify(logObject);
|
|
661
|
-
console.log(crlf ? json + '\r\n' : json);
|
|
662
|
-
}
|
|
663
|
-
};
|
|
664
|
-
|
|
665
|
-
const applySerializers = (obj: Record<string, any>): Record<string, any> => {
|
|
666
|
-
if (Object.keys(serializers).length === 0) return obj;
|
|
667
|
-
return Object.fromEntries(
|
|
668
|
-
Object.entries(obj).map(([key, value]) => [
|
|
669
|
-
key,
|
|
670
|
-
key in serializers ? serializers[key](value) : value,
|
|
671
|
-
]),
|
|
672
|
-
);
|
|
673
|
-
};
|
|
674
|
-
|
|
675
|
-
const emitTransmit = (
|
|
676
|
-
level: LevelWithSilentOrString,
|
|
677
|
-
rawArgs: unknown[],
|
|
678
|
-
) => {
|
|
679
|
-
if (!transmit) return;
|
|
680
|
-
if (transmit.level) {
|
|
681
|
-
const transmitValue = levelValues[transmit.level];
|
|
682
|
-
const logValue = levelValues[level];
|
|
683
|
-
if (transmitValue !== undefined && logValue !== undefined && logValue < transmitValue) {
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
// Apply serializers then redaction to messages
|
|
688
|
-
const processedMessages = rawArgs.map((m) => {
|
|
689
|
-
if (typeof m !== 'object' || m === null) return m;
|
|
690
|
-
const serialized = applySerializers(m as Record<string, any>);
|
|
691
|
-
return redactor ? redactor(serialized) : serialized;
|
|
692
|
-
});
|
|
693
|
-
// Apply serializers then redaction to bindings
|
|
694
|
-
const processedBindings = bindingsChain.map((b) => {
|
|
695
|
-
const serialized = applySerializers({ ...b });
|
|
696
|
-
return redactor ? (redactor(serialized) as LogAttrs) : serialized;
|
|
697
|
-
});
|
|
698
|
-
const logEvent: LogEvent = {
|
|
699
|
-
ts: Date.now(),
|
|
700
|
-
messages: processedMessages,
|
|
701
|
-
bindings: processedBindings,
|
|
702
|
-
level: { label: level, value: levelValues[level] },
|
|
703
|
-
};
|
|
704
|
-
transmit.send(level, logEvent);
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
const log = (
|
|
708
|
-
level: LevelWithSilentOrString,
|
|
709
|
-
msg: string,
|
|
710
|
-
attrs?: Record<string, any>,
|
|
711
|
-
rawArgs?: unknown[],
|
|
712
|
-
) => {
|
|
713
|
-
if (!shouldLog(level)) return;
|
|
714
|
-
|
|
715
|
-
const ctx = getTraceContext();
|
|
716
|
-
const message = msgPrefix ? `${msgPrefix}${msg}` : msg;
|
|
717
|
-
const baseBindings = bindingsFormatter
|
|
718
|
-
? (bindingsFormatter({ ...currentBindings }) as LogAttrs)
|
|
719
|
-
: { ...currentBindings };
|
|
720
|
-
const serializedAttrs = attrs
|
|
721
|
-
? Object.fromEntries(
|
|
722
|
-
Object.entries(attrs).map(([key, value]) => [
|
|
723
|
-
key,
|
|
724
|
-
key in serializers ? serializers[key](value) : value,
|
|
725
|
-
]),
|
|
726
|
-
)
|
|
727
|
-
: undefined;
|
|
728
|
-
const mergeObject = nestedKey
|
|
729
|
-
? ({ [nestedKey]: serializedAttrs ?? {} } as Record<string, unknown>)
|
|
730
|
-
: (serializedAttrs ?? {});
|
|
731
|
-
const mixinObject =
|
|
732
|
-
mixin?.(mergeObject, levelValues[level], logger) ?? {};
|
|
733
|
-
const mergedLogObject = mixinMergeStrategy(mergeObject, mixinObject) as Record<
|
|
734
|
-
string,
|
|
735
|
-
unknown
|
|
736
|
-
>;
|
|
737
|
-
const formattedLogObject = logFormatter
|
|
738
|
-
? logFormatter(mergedLogObject)
|
|
739
|
-
: mergedLogObject;
|
|
740
|
-
const formattedLevel = levelFormatter
|
|
741
|
-
? levelFormatter(level, levelValues[level])
|
|
742
|
-
: {
|
|
743
|
-
level: logger.useLevelLabels ? level : levelValues[level],
|
|
744
|
-
};
|
|
745
|
-
const ts = getTimestamp();
|
|
746
|
-
const logEntry: Record<string, any> = {
|
|
747
|
-
...formattedLevel,
|
|
748
|
-
...(loggerName === undefined ? {} : { name: loggerName }),
|
|
749
|
-
service,
|
|
750
|
-
...(message === '' ? {} : { [messageKey]: message }),
|
|
751
|
-
...(base === null || base === undefined ? {} : base),
|
|
752
|
-
...baseBindings,
|
|
753
|
-
...formattedLogObject,
|
|
754
|
-
...ctx, // Auto-inject traceId, spanId, correlationId
|
|
755
|
-
...(ts === undefined ? {} : { timestamp: ts }),
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
if (serializedAttrs && !nestedKey && errorKey !== 'error' && 'error' in logEntry) {
|
|
759
|
-
logEntry[errorKey] = logEntry.error;
|
|
760
|
-
delete logEntry.error;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
if (pretty && !writeFn) {
|
|
764
|
-
// Pretty print for development
|
|
765
|
-
const color = ANSI_COLORS[level] ?? ANSI_COLORS.info;
|
|
766
|
-
const symbol = LEVEL_SYMBOLS[level] ?? '●';
|
|
767
|
-
const time = formatPrettyTimestamp();
|
|
768
|
-
const traceInfo = ctx
|
|
769
|
-
? ` ${ANSI_DIM}[${ctx.traceId.slice(0, 8)}/${ctx.spanId.slice(0, 8)}]${ANSI_RESET}`
|
|
770
|
-
: '';
|
|
771
|
-
const prettyAttrs = {
|
|
772
|
-
...baseBindings,
|
|
773
|
-
...formattedLogObject,
|
|
774
|
-
};
|
|
775
|
-
const safeAttrs = redactor
|
|
776
|
-
? (redactor(prettyAttrs) as LogAttrs)
|
|
777
|
-
: prettyAttrs;
|
|
778
|
-
const attrsStr = formatPrettyAttrs(safeAttrs);
|
|
779
|
-
console.log(
|
|
780
|
-
`${ANSI_DIM}${time}${ANSI_RESET} ${color}${ANSI_BOLD}${symbol} ${level.toUpperCase()}${ANSI_RESET}${traceInfo} ${ANSI_DIM}(${service})${ANSI_RESET} ${message}${attrsStr}`,
|
|
781
|
-
);
|
|
782
|
-
} else {
|
|
783
|
-
// Structured JSON for production (or custom write)
|
|
784
|
-
const safeEntry = redactor ? (redactor(logEntry) as LogAttrs) : logEntry;
|
|
785
|
-
writeOutput(level, safeEntry);
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
emitTransmit(level, rawArgs ?? []);
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
const makeLogMethod = (levelName: string): LogFn => {
|
|
792
|
-
return ((...args: unknown[]) => {
|
|
793
|
-
if (hooks?.logMethod) {
|
|
794
|
-
const method = ((...methodArgs: unknown[]) => {
|
|
795
|
-
const { msg, attrs } = parseLogArgs(methodArgs);
|
|
796
|
-
log(levelName, msg, attrs, methodArgs);
|
|
797
|
-
}) as LogFn;
|
|
798
|
-
hooks.logMethod.call(logger, args as Parameters<LogFn>, method, levelValues[levelName]);
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
const { msg, attrs } = parseLogArgs(args);
|
|
802
|
-
log(levelName, msg, attrs, args);
|
|
803
|
-
}) as LogFn;
|
|
804
|
-
};
|
|
805
|
-
|
|
806
|
-
const logger: EdgeLogger = {
|
|
807
|
-
version: LOGGER_VERSION,
|
|
808
|
-
|
|
809
|
-
get name() {
|
|
810
|
-
return loggerName;
|
|
811
|
-
},
|
|
812
|
-
|
|
813
|
-
levels: {
|
|
814
|
-
values: levelValues,
|
|
815
|
-
labels: Object.fromEntries(
|
|
816
|
-
Object.entries(levelValues).map(([label, value]) => [value, label]),
|
|
817
|
-
),
|
|
818
|
-
},
|
|
819
|
-
useLevelLabels: options?.useLevelLabels ?? false,
|
|
820
|
-
customLevels: { ...customLevels },
|
|
821
|
-
useOnlyCustomLevels: options?.useOnlyCustomLevels ?? false,
|
|
822
|
-
|
|
823
|
-
get level() {
|
|
824
|
-
return currentLevel;
|
|
825
|
-
},
|
|
826
|
-
|
|
827
|
-
set level(level: LevelWithSilentOrString) {
|
|
828
|
-
const previousLevel = currentLevel;
|
|
829
|
-
currentLevel = level;
|
|
830
|
-
notifyLevelChange(level, previousLevel, logger);
|
|
831
|
-
},
|
|
832
|
-
|
|
833
|
-
get levelVal() {
|
|
834
|
-
return levelValues[currentLevel] ?? Infinity;
|
|
835
|
-
},
|
|
836
|
-
|
|
837
|
-
get msgPrefix() {
|
|
838
|
-
return msgPrefix;
|
|
839
|
-
},
|
|
840
|
-
|
|
841
|
-
onChild: onChildCallback,
|
|
842
|
-
get enabled() {
|
|
843
|
-
return enabled;
|
|
844
|
-
},
|
|
845
|
-
|
|
846
|
-
set enabled(value: boolean) {
|
|
847
|
-
enabled = value;
|
|
848
|
-
},
|
|
849
|
-
|
|
850
|
-
info: makeLogMethod('info'),
|
|
851
|
-
|
|
852
|
-
error: makeLogMethod('error'),
|
|
853
|
-
|
|
854
|
-
warn: makeLogMethod('warn'),
|
|
855
|
-
|
|
856
|
-
debug: makeLogMethod('debug'),
|
|
857
|
-
|
|
858
|
-
trace: makeLogMethod('trace'),
|
|
859
|
-
|
|
860
|
-
fatal: makeLogMethod('fatal'),
|
|
861
|
-
|
|
862
|
-
silent: (() => {}) as LogFn,
|
|
863
|
-
|
|
864
|
-
child: (bindings: LogAttrs, childOptions?: ChildLoggerOptions) => {
|
|
865
|
-
const childLogger = createEdgeLogger(service, {
|
|
866
|
-
level: childOptions?.level ?? currentLevel,
|
|
867
|
-
pretty,
|
|
868
|
-
bindings: { ...currentBindings, ...bindings },
|
|
869
|
-
redact: childOptions?.redact ?? options?.redact,
|
|
870
|
-
msgPrefix:
|
|
871
|
-
childOptions?.msgPrefix === undefined
|
|
872
|
-
? msgPrefix
|
|
873
|
-
: `${msgPrefix ?? ''}${childOptions.msgPrefix}`,
|
|
874
|
-
useLevelLabels: logger.useLevelLabels,
|
|
875
|
-
onChild: onChildCallback,
|
|
876
|
-
enabled,
|
|
877
|
-
messageKey,
|
|
878
|
-
errorKey,
|
|
879
|
-
nestedKey,
|
|
880
|
-
serializers: { ...serializers, ...childOptions?.serializers },
|
|
881
|
-
formatters: {
|
|
882
|
-
level: childOptions?.formatters?.level ?? levelFormatter,
|
|
883
|
-
bindings: childOptions?.formatters?.bindings ?? bindingsFormatter,
|
|
884
|
-
log: childOptions?.formatters?.log ?? logFormatter,
|
|
885
|
-
},
|
|
886
|
-
mixin,
|
|
887
|
-
mixinMergeStrategy,
|
|
888
|
-
customLevels,
|
|
889
|
-
useOnlyCustomLevels: options?.useOnlyCustomLevels,
|
|
890
|
-
levelComparison: options?.levelComparison,
|
|
891
|
-
name: loggerName,
|
|
892
|
-
base,
|
|
893
|
-
timestamp,
|
|
894
|
-
safe,
|
|
895
|
-
crlf,
|
|
896
|
-
depthLimit,
|
|
897
|
-
edgeLimit: edgeLimitOpt,
|
|
898
|
-
hooks,
|
|
899
|
-
write: writeFn,
|
|
900
|
-
transmit,
|
|
901
|
-
_bindingsChain: [...bindingsChain, bindings],
|
|
902
|
-
});
|
|
903
|
-
onChildCallback(childLogger);
|
|
904
|
-
return childLogger;
|
|
905
|
-
},
|
|
906
|
-
|
|
907
|
-
isLevelEnabled: (level: LevelWithSilentOrString) => shouldLog(level),
|
|
908
|
-
|
|
909
|
-
bindings: () => ({ ...currentBindings }),
|
|
910
|
-
|
|
911
|
-
setBindings: (bindings: LogAttrs) => {
|
|
912
|
-
for (const [key, value] of Object.entries(bindings)) {
|
|
913
|
-
if (!(key in currentBindings)) {
|
|
914
|
-
currentBindings[key] = value;
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
},
|
|
918
|
-
|
|
919
|
-
flush: (cb?: (err?: Error) => void) => {
|
|
920
|
-
cb?.();
|
|
921
|
-
},
|
|
922
|
-
|
|
923
|
-
emit: (event: string | symbol, ...args: any[]) => {
|
|
924
|
-
const activeListeners = listeners.get(event);
|
|
925
|
-
if (!activeListeners || activeListeners.length === 0) {
|
|
926
|
-
return false;
|
|
927
|
-
}
|
|
928
|
-
const snapshot = [...activeListeners];
|
|
929
|
-
for (const listener of snapshot) {
|
|
930
|
-
listener(...args);
|
|
931
|
-
}
|
|
932
|
-
return true;
|
|
933
|
-
},
|
|
934
|
-
|
|
935
|
-
eventNames: () => [...listeners.keys()],
|
|
936
|
-
|
|
937
|
-
getMaxListeners: () => maxListeners,
|
|
938
|
-
|
|
939
|
-
listenerCount: (event: string | symbol) => listeners.get(event)?.length ?? 0,
|
|
940
|
-
|
|
941
|
-
listeners: (event: string | symbol) => [...(listeners.get(event) ?? [])],
|
|
942
|
-
|
|
943
|
-
on: (event: string | symbol, listener: EventListener) => {
|
|
944
|
-
addListener(event, listener);
|
|
945
|
-
return logger;
|
|
946
|
-
},
|
|
947
|
-
|
|
948
|
-
addListener: (event: string | symbol, listener: EventListener) => {
|
|
949
|
-
addListener(event, listener);
|
|
950
|
-
return logger;
|
|
951
|
-
},
|
|
952
|
-
|
|
953
|
-
once: (event: string | symbol, listener: EventListener) => {
|
|
954
|
-
const wrapped: EventListener = (...args: any[]) => {
|
|
955
|
-
removeEventListener(event, wrapped);
|
|
956
|
-
listener(...args);
|
|
957
|
-
};
|
|
958
|
-
addListener(event, wrapped);
|
|
959
|
-
return logger;
|
|
960
|
-
},
|
|
961
|
-
|
|
962
|
-
prependListener: (
|
|
963
|
-
event: string | symbol,
|
|
964
|
-
listener: EventListener,
|
|
965
|
-
) => {
|
|
966
|
-
addListener(event, listener, true);
|
|
967
|
-
return logger;
|
|
968
|
-
},
|
|
969
|
-
|
|
970
|
-
prependOnceListener: (
|
|
971
|
-
event: string | symbol,
|
|
972
|
-
listener: EventListener,
|
|
973
|
-
) => {
|
|
974
|
-
const wrapped: EventListener = (...args: any[]) => {
|
|
975
|
-
removeEventListener(event, wrapped);
|
|
976
|
-
listener(...args);
|
|
977
|
-
};
|
|
978
|
-
addListener(event, wrapped, true);
|
|
979
|
-
return logger;
|
|
980
|
-
},
|
|
981
|
-
|
|
982
|
-
rawListeners: (event: string | symbol) => [...(listeners.get(event) ?? [])],
|
|
983
|
-
|
|
984
|
-
off: (event: string | symbol, listener: EventListener) => {
|
|
985
|
-
removeEventListener(event, listener);
|
|
986
|
-
return logger;
|
|
987
|
-
},
|
|
988
|
-
|
|
989
|
-
removeAllListeners: (event?: string | symbol) => {
|
|
990
|
-
if (event === undefined) {
|
|
991
|
-
listeners.clear();
|
|
992
|
-
levelListeners.length = 0;
|
|
993
|
-
return logger;
|
|
994
|
-
}
|
|
995
|
-
listeners.delete(event);
|
|
996
|
-
if (event === 'level-change') {
|
|
997
|
-
levelListeners.length = 0;
|
|
998
|
-
}
|
|
999
|
-
return logger;
|
|
1000
|
-
},
|
|
1001
|
-
|
|
1002
|
-
removeListener: (
|
|
1003
|
-
event: string | symbol,
|
|
1004
|
-
listener: EventListener,
|
|
1005
|
-
) => {
|
|
1006
|
-
removeEventListener(event, listener);
|
|
1007
|
-
return logger;
|
|
1008
|
-
},
|
|
1009
|
-
|
|
1010
|
-
setMaxListeners: (n: number) => {
|
|
1011
|
-
maxListeners = n;
|
|
1012
|
-
return logger;
|
|
1013
|
-
},
|
|
1014
|
-
};
|
|
1015
|
-
|
|
1016
|
-
for (const levelName of Object.keys(customLevels)) {
|
|
1017
|
-
logger[levelName] = makeLogMethod(levelName);
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
return logger;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
/**
|
|
1024
|
-
* Helper to get trace context (useful for BYOL - Bring Your Own Logger)
|
|
1025
|
-
*
|
|
1026
|
-
* @example
|
|
1027
|
-
* ```typescript
|
|
1028
|
-
* import bunyan from 'bunyan'
|
|
1029
|
-
* import { getEdgeTraceContext } from 'autotel-edge/api/logger'
|
|
1030
|
-
*
|
|
1031
|
-
* const bunyanLogger = bunyan.createLogger({ name: 'myapp' })
|
|
1032
|
-
* const ctx = getEdgeTraceContext()
|
|
1033
|
-
* bunyanLogger.info({ ...ctx, email: 'test@example.com' }, 'Creating user')
|
|
1034
|
-
* ```
|
|
1035
|
-
*/
|
|
1036
|
-
export function getEdgeTraceContext() {
|
|
1037
|
-
return getTraceContext();
|
|
1038
|
-
}
|