@bymax-one/nest-logger 1.0.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.
@@ -0,0 +1,953 @@
1
+ import * as _nestjs_common from '@nestjs/common';
2
+ import { ModuleMetadata, Type, DynamicModule, INestApplication, LoggerService, OnApplicationShutdown, ExceptionFilter, ArgumentsHost, NestInterceptor, ExecutionContext, CallHandler, MiddlewareConsumer, NestMiddleware, CustomDecorator } from '@nestjs/common';
3
+ import { Logger } from 'pino';
4
+ import { Observable } from 'rxjs';
5
+ import { Request, Response, NextFunction } from 'express';
6
+
7
+ /**
8
+ * Pino log levels (mirrored to NestJS-compatible names).
9
+ *
10
+ * Numeric mapping (Pino):
11
+ * - `fatal` 60
12
+ * - `error` 50
13
+ * - `warn` 40
14
+ * - `info` 30
15
+ * - `debug` 20
16
+ * - `trace` 10
17
+ *
18
+ * @example
19
+ * const level: LogLevel = 'info'
20
+ */
21
+ type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
22
+
23
+ /**
24
+ * Pluggable destination contract for the logger.
25
+ *
26
+ * Layer: server/interfaces — implemented by every concrete sink
27
+ * (`DefaultStdoutDestination`, future HTTP / file / queue destinations).
28
+ * The library owns the JSON serialization; implementations only forward
29
+ * the already-encoded payload.
30
+ */
31
+
32
+ /**
33
+ * Pluggable destination for log entries.
34
+ *
35
+ * Implementations receive the final JSON-serialized payload (with trailing
36
+ * newline) and write it wherever they want — file, network, queue, etc.
37
+ *
38
+ * Implementations MUST be non-blocking and tolerant to errors. A destination
39
+ * that throws or hangs MUST NOT break the application — the library's
40
+ * `DestinationRegistry` catches errors and emits a meta-log
41
+ * (`LOGGER_DESTINATION_WRITE_FAILED`).
42
+ *
43
+ * @example
44
+ * class FileDestination implements ILogDestination {
45
+ * readonly name = 'file'
46
+ * write(payload: string): void {
47
+ * fs.appendFileSync('/var/log/app.log', payload)
48
+ * }
49
+ * }
50
+ */
51
+ interface ILogDestination {
52
+ /** Unique identifier — used in error logs and registry lookups. */
53
+ readonly name: string;
54
+ /**
55
+ * Minimum log level this destination accepts. Entries below this level
56
+ * are not written. Undefined means "accept everything".
57
+ */
58
+ readonly minLevel?: LogLevel;
59
+ /**
60
+ * Write a single log entry. Payload is the already-serialized JSON string
61
+ * (UTF-8, terminated with `\n`).
62
+ *
63
+ * Implementations may:
64
+ * - Write synchronously (e.g., `process.stdout`)
65
+ * - Buffer and flush periodically (e.g., HTTP batching)
66
+ * - Return a Promise for async I/O
67
+ *
68
+ * Errors thrown here are caught by the library; the destination may be
69
+ * temporarily skipped if it consistently fails.
70
+ *
71
+ * @param payload — Newline-terminated JSON entry, UTF-8 encoded.
72
+ */
73
+ write(payload: string): void | Promise<void>;
74
+ /**
75
+ * Optional lifecycle hook — called once during NestJS module init.
76
+ * Use for opening connections, allocating buffers, scheduling flushers.
77
+ */
78
+ onInit?(): void | Promise<void>;
79
+ /**
80
+ * Optional lifecycle hook — called during NestJS `onApplicationShutdown`.
81
+ * MUST flush any pending writes and close resources.
82
+ *
83
+ * The library awaits this — graceful shutdown blocks until all destinations
84
+ * return.
85
+ */
86
+ onShutdown?(): void | Promise<void>;
87
+ }
88
+
89
+ /**
90
+ * Service metadata propagated to every log entry.
91
+ *
92
+ * Aligned with OpenTelemetry semantic conventions for service identity:
93
+ * {@link https://opentelemetry.io/docs/specs/semconv/resource/#service}
94
+ */
95
+ interface ServiceMetadata {
96
+ /** OTel attribute `service.name` — stable identifier of the running service. */
97
+ readonly name: string;
98
+ /** OTel attribute `service.version` — semver, commit SHA, or build tag. */
99
+ readonly version: string;
100
+ }
101
+
102
+ /**
103
+ * Public module-options interfaces for `BymaxLoggerModule`.
104
+ *
105
+ * Layer: server/interfaces — the contracts consumed by `forRoot` /
106
+ * `forRootAsync` plus the nested `HttpOptions` / `OtelOptions` shapes and
107
+ * the async factory pair. The synchronous and asynchronous variants are
108
+ * intentionally separate types (not a union) so the dynamic-module helper
109
+ * can branch on shape without runtime discrimination.
110
+ */
111
+
112
+ /**
113
+ * HTTP module configuration — used by the optional HTTP interceptor and filter.
114
+ *
115
+ * Every field is optional; defaults are applied by `applyDefaults`.
116
+ */
117
+ interface HttpOptions {
118
+ /** Mount the HTTP interceptor + filter. Default: false. */
119
+ isEnabled?: boolean;
120
+ /** Capture unhandled HTTP exceptions and emit `HTTP_EXCEPTION_UNHANDLED`. Default: true. */
121
+ shouldCaptureExceptions?: boolean;
122
+ /** Generate `requestId` when the request header is absent. Default: true. */
123
+ shouldGenerateRequestId?: boolean;
124
+ /**
125
+ * Paths that bypass HTTP logging (health checks, metrics). Defaults:
126
+ * `/health`, `/metrics`.
127
+ *
128
+ * SECURITY: each pattern is `.test()`-ed against the (attacker-controllable)
129
+ * request path on every request. Supply only anchored, linear-time regexes
130
+ * (e.g. `/^\/health$/`); a pattern with catastrophic backtracking would turn a
131
+ * crafted URL into a ReDoS vector. The defaults are anchored and safe.
132
+ */
133
+ excludePaths?: readonly RegExp[];
134
+ /** Header name carrying the tenant identifier. Default: `x-tenant-id`. */
135
+ tenantIdHeader?: string;
136
+ }
137
+ /**
138
+ * OpenTelemetry integration tuning — applied by `TraceContextMixin` when the
139
+ * `@opentelemetry/api` peer dependency is installed.
140
+ */
141
+ interface OtelOptions {
142
+ /** Inject the active span's trace/span IDs into every log entry. Default: true. */
143
+ shouldAutoInjectTraceContext?: boolean;
144
+ /**
145
+ * Shortcut that derives sensible defaults for `traceIdField` / `spanIdField` /
146
+ * `traceFlagsField` based on a target casing.
147
+ *
148
+ * - `'camelCase'` (default) → `traceId` / `spanId` / `traceFlags`
149
+ * - `'snake_case'` → `trace_id` / `span_id` / `trace_flags`
150
+ * (OTel Logs Data Model wire format)
151
+ *
152
+ * Individual `*Field` overrides ALWAYS win over the shortcut.
153
+ */
154
+ fieldFormat?: 'camelCase' | 'snake_case';
155
+ /** Override the field name for the trace ID. */
156
+ traceIdField?: string;
157
+ /** Override the field name for the span ID. */
158
+ spanIdField?: string;
159
+ /** Override the field name for the trace flags. */
160
+ traceFlagsField?: string;
161
+ }
162
+ /**
163
+ * Synchronous configuration for `BymaxLoggerModule.forRoot()`.
164
+ */
165
+ interface BymaxLoggerModuleOptions {
166
+ /** Mandatory service metadata — emitted on every log entry. */
167
+ service: ServiceMetadata;
168
+ /** Minimum level emitted. Default: `info` in production, `debug` otherwise. */
169
+ level?: LogLevel;
170
+ /** Register the module as `@Global()`. Default: true. */
171
+ isGlobal?: boolean;
172
+ /**
173
+ * @deprecated Not read by the module — a module cannot call `app.useLogger()`
174
+ * itself (it never sees the `INestApplication`). Call
175
+ * `BymaxLoggerModule.useNestLogger(app)` in `main.ts` instead. Default: true.
176
+ */
177
+ shouldUseAsNestLogger?: boolean;
178
+ /** Additional Pino redact paths — merged with `DEFAULT_REDACT_PATHS`. */
179
+ redactPaths?: readonly string[];
180
+ /** Censor string written in place of redacted values. Default: `[REDACTED]`. */
181
+ redactCensor?: string;
182
+ /** Disable default redact paths (use with caution). Default: false. */
183
+ shouldDisableDefaultRedact?: boolean;
184
+ /** Custom destinations beyond `DefaultStdoutDestination`. */
185
+ destinations?: readonly ILogDestination[];
186
+ /**
187
+ * @deprecated Currently inert — a `true` value does not change output on its
188
+ * own. For pretty local logs add `new PrettyDevDestination()` to
189
+ * `destinations` (and install the optional `pino-pretty` peer dep).
190
+ * Default: `NODE_ENV !== 'production'`.
191
+ */
192
+ isPretty?: boolean;
193
+ /** HTTP module configuration. */
194
+ http?: HttpOptions;
195
+ /** OpenTelemetry integration tuning. */
196
+ otel?: OtelOptions;
197
+ /** Maximum size in bytes per log entry. Default: 65536. */
198
+ maxEntrySizeBytes?: number;
199
+ /** Custom Pino serializers, merged with defaults. */
200
+ serializers?: Record<string, (input: unknown) => unknown>;
201
+ /** Custom timestamp function. Default: `() => new Date().toISOString()`. */
202
+ timestamp?: () => string;
203
+ }
204
+ /**
205
+ * Fully-resolved options after `applyDefaults` runs.
206
+ *
207
+ * Every optional field is filled, and the nested `http` / `otel` bags are
208
+ * deep-required because {@link applyDefaults} populates each of their fields.
209
+ * This is the precise shape every runtime layer (pino factory, services,
210
+ * middleware) consumes — distinct from the loosely-typed
211
+ * `BymaxLoggerModuleOptions` the consumer supplies.
212
+ */
213
+ type ResolvedBymaxLoggerModuleOptions = Readonly<Required<Omit<BymaxLoggerModuleOptions, 'http' | 'otel'>> & {
214
+ http: Required<HttpOptions>;
215
+ otel: Required<OtelOptions>;
216
+ }>;
217
+ /**
218
+ * Factory interface used by `useExisting` / `useClass` async wiring.
219
+ *
220
+ * Implement this interface when the module options depend on providers that are
221
+ * already in the DI graph (e.g., a `ConfigService`). Pass the class to
222
+ * `forRootAsync({ useClass: MyFactory })` or the token to `useExisting`.
223
+ */
224
+ interface BymaxLoggerModuleOptionsFactory {
225
+ /**
226
+ * Produce the synchronous or asynchronous module options.
227
+ *
228
+ * @returns Resolved or promised `BymaxLoggerModuleOptions`.
229
+ * @throws Any error thrown here propagates as a module-initialisation failure.
230
+ */
231
+ createLoggerOptions(): BymaxLoggerModuleOptions | Promise<BymaxLoggerModuleOptions>;
232
+ }
233
+ /**
234
+ * Asynchronous configuration for `BymaxLoggerModule.forRootAsync()`.
235
+ *
236
+ * Standard NestJS dynamic-module async-options shape — pick `imports` from
237
+ * `ModuleMetadata` to allow re-using the consumer's providers in the factory.
238
+ */
239
+ interface BymaxLoggerModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
240
+ /**
241
+ * Factory producing the module options. The `unknown[]` parameter shape
242
+ * matches the heterogeneous `inject` array — NestJS resolves each token
243
+ * independently and passes the resolved providers as positional args, so a
244
+ * tighter generic would force every consumer to widen the signature with
245
+ * `as` casts at the call site.
246
+ */
247
+ useFactory?: (...args: unknown[]) => BymaxLoggerModuleOptions | Promise<BymaxLoggerModuleOptions>;
248
+ /** Provider tokens NestJS must resolve and forward to `useFactory`. */
249
+ inject?: readonly (string | symbol | Type<unknown>)[];
250
+ /** Reuse an existing factory provider already registered in the DI graph. */
251
+ useExisting?: Type<BymaxLoggerModuleOptionsFactory>;
252
+ /** Instantiate a factory class and call its `createLoggerOptions()`. */
253
+ useClass?: Type<BymaxLoggerModuleOptionsFactory>;
254
+ }
255
+
256
+ /** Extra (non-option) flags accepted by `forRoot` / `forRootAsync`. */
257
+ interface BymaxLoggerModuleExtras {
258
+ /** Register the module globally. Default: `true`. */
259
+ isGlobal?: boolean;
260
+ }
261
+ /**
262
+ * `BymaxLoggerModuleBase` — base class providing the generated `forRoot` /
263
+ * `forRootAsync` implementations.
264
+ *
265
+ * `BUILDER_OPTIONS_TOKEN` — internal DI token for the raw (pre-defaulted)
266
+ * consumer options; not intended for injection outside this module.
267
+ *
268
+ * `OPTIONS_TYPE` / `ASYNC_OPTIONS_TYPE` — `typeof`-only sentinels used as
269
+ * parameter types in the static factory overrides; never instantiated at
270
+ * runtime.
271
+ */
272
+ declare const BymaxLoggerModuleBase: _nestjs_common.ConfigurableModuleCls<BymaxLoggerModuleOptions, "forRoot", "createLoggerOptions", BymaxLoggerModuleExtras>;
273
+ declare const OPTIONS_TYPE: BymaxLoggerModuleOptions & Partial<BymaxLoggerModuleExtras>;
274
+ declare const ASYNC_OPTIONS_TYPE: _nestjs_common.ConfigurableModuleAsyncOptions<BymaxLoggerModuleOptions, "createLoggerOptions"> & Partial<BymaxLoggerModuleExtras>;
275
+
276
+ declare class BymaxLoggerModule extends BymaxLoggerModuleBase {
277
+ /**
278
+ * Synchronous registration.
279
+ *
280
+ * @param options - Logger options (plus the `isGlobal` extra).
281
+ * @returns The configured `DynamicModule`.
282
+ * @throws Error When the options fail validation.
283
+ * @example
284
+ * BymaxLoggerModule.forRoot({ service: { name: 'api', version: '1.0.0' } })
285
+ */
286
+ static forRoot(options: typeof OPTIONS_TYPE): DynamicModule;
287
+ /**
288
+ * Asynchronous registration. Options are resolved lazily via the consumer's
289
+ * factory (`useFactory` + `inject` + `imports`, `useClass`, or `useExisting`);
290
+ * the Pino instance and the bootstrap log are produced only after they
291
+ * resolve, never at module-decoration time.
292
+ *
293
+ * Note: the destination-registry `onModuleInit` lifecycle hook is managed by
294
+ * `DestinationRegistry` itself, not by this factory. The destinations array is
295
+ * registered under `LOGGER_DESTINATIONS_TOKEN`; no built-in destination defines
296
+ * an `onInit` hook, so nothing is lost.
297
+ *
298
+ * HTTP access logging IS supported on this path: the global interceptor slot
299
+ * is always registered and gated at runtime against the resolved
300
+ * `http.isEnabled` (see {@link asyncHttpInterceptorProvider}). The catch-all
301
+ * exception FILTER is intentionally NOT auto-wired here — registering a global
302
+ * `@Catch()` filter from async config would either interfere with a consumer's
303
+ * own filters (when disabled) or require unsafe re-throwing. Async consumers who
304
+ * want it register `HttpExceptionFilter` themselves:
305
+ * `{ provide: APP_FILTER, useClass: HttpExceptionFilter }`.
306
+ *
307
+ * @param options - Async options (factory + inject + imports, or class).
308
+ * @returns The configured `DynamicModule`.
309
+ */
310
+ static forRootAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule;
311
+ /**
312
+ * Replace NestJS's internal logger with this library's `PinoLoggerService` and
313
+ * flush any logs buffered since `NestFactory.create(..., { bufferLogs: true })`.
314
+ *
315
+ * Call once in `main.ts` AFTER creating the app and BEFORE `app.listen(...)`.
316
+ * The module cannot do this itself — a module never sees the
317
+ * `INestApplication` instance — so this static helper bridges the gap.
318
+ *
319
+ * @param app - The Nest application returned by `NestFactory.create()`.
320
+ * @throws Error When `BymaxLoggerModule` was not imported (no `PinoLoggerService`
321
+ * in the container) — a clear message instead of a cryptic DI failure. The
322
+ * original DI error is attached as the error `cause` so the underlying
323
+ * failure is preserved for debugging.
324
+ * @example
325
+ * const app = await NestFactory.create(AppModule, { bufferLogs: true })
326
+ * BymaxLoggerModule.useNestLogger(app)
327
+ * await app.listen(3000)
328
+ */
329
+ static useNestLogger(app: INestApplication): void;
330
+ }
331
+
332
+ /**
333
+ * Pino-backed implementation of the NestJS `LoggerService` contract plus the
334
+ * structured `MODULE_ACTION_RESULT` API.
335
+ *
336
+ * For a per-class context label, inject a CHILD logger already bound to that
337
+ * context via `@InjectLogger(MyService.name)` — do NOT call `setContext()` per
338
+ * class: this is a singleton, so a per-class `setContext()` clobbers the label
339
+ * for every other holder (see {@link PinoLoggerService.setContext}).
340
+ *
341
+ * @example
342
+ * constructor(
343
+ * @InjectLogger(UserService.name) private readonly logger: PinoLoggerService,
344
+ * ) {}
345
+ * // ...
346
+ * this.logger.info('USER_CREATED', 'New user registered', userId, { plan: 'pro' })
347
+ */
348
+ declare class PinoLoggerService implements LoggerService, OnApplicationShutdown {
349
+ private readonly pino;
350
+ private context?;
351
+ constructor(pino: Logger);
352
+ /**
353
+ * NestJS `log` (info level). Last string parameter is treated as the context.
354
+ *
355
+ * @param message - The message to log.
356
+ * @param optionalParams - NestJS trailing params (context as last string).
357
+ */
358
+ log(message: unknown, ...optionalParams: unknown[]): void;
359
+ /**
360
+ * NestJS `error`. When `message` is an `Error`, routes to the structured
361
+ * error path (serializing `err`). Otherwise follows the NestJS variadic
362
+ * contract `error(message, stack?, context?)`, reading the stack and context
363
+ * positionally so a trailing stack is never mistaken for the context.
364
+ *
365
+ * @param message - The message or `Error` to log.
366
+ * @param optionalParams - NestJS trailing params (`stack` at index 0, `context` at index 1).
367
+ */
368
+ error(message: unknown, ...optionalParams: unknown[]): void;
369
+ /**
370
+ * NestJS `warn` (warn level).
371
+ *
372
+ * @param message - The message to log.
373
+ * @param optionalParams - NestJS trailing params (context as last string).
374
+ */
375
+ warn(message: unknown, ...optionalParams: unknown[]): void;
376
+ /**
377
+ * NestJS `debug` (debug level).
378
+ *
379
+ * @param message - The message to log.
380
+ * @param optionalParams - NestJS trailing params (context as last string).
381
+ */
382
+ debug(message: unknown, ...optionalParams: unknown[]): void;
383
+ /**
384
+ * NestJS `verbose` (mapped to Pino `trace`).
385
+ *
386
+ * @param message - The message to log.
387
+ * @param optionalParams - NestJS trailing params (context as last string).
388
+ */
389
+ verbose(message: unknown, ...optionalParams: unknown[]): void;
390
+ /**
391
+ * NestJS `fatal` (fatal level). Declared non-optional here: Pino supports
392
+ * level 60 natively, and widening the optional `LoggerService.fatal?` to a
393
+ * required method stays type-compatible.
394
+ *
395
+ * @param message - The message to log.
396
+ * @param optionalParams - NestJS trailing params (context as last string).
397
+ */
398
+ fatal(message: unknown, ...optionalParams: unknown[]): void;
399
+ /**
400
+ * Emit a structured info log.
401
+ *
402
+ * @param logKey - Convention key, e.g. `USER_CREATED`.
403
+ * @param message - Human-readable message.
404
+ * @param userId - Optional acting user identifier.
405
+ * @param metadata - Optional extra structured fields.
406
+ * @example
407
+ * logger.info('USER_CREATED', 'New user registered', userId, { plan: 'pro' })
408
+ */
409
+ info(logKey: string, message: string, userId?: string, metadata?: Record<string, unknown>): void;
410
+ /**
411
+ * Emit a structured warning log.
412
+ *
413
+ * @param logKey - Convention key.
414
+ * @param message - Human-readable message.
415
+ * @param userId - Optional acting user identifier.
416
+ * @param metadata - Optional extra structured fields.
417
+ */
418
+ warnStructured(logKey: string, message: string, userId?: string, metadata?: Record<string, unknown>): void;
419
+ /**
420
+ * Emit a structured error log with a serialized `err` object.
421
+ *
422
+ * @param logKey - Convention key.
423
+ * @param error - The error to serialize (`name` / `message` / `stack`).
424
+ * @param userId - Optional acting user identifier.
425
+ * @param metadata - Optional extra structured fields.
426
+ */
427
+ errorStructured(logKey: string, error: Error, userId?: string, metadata?: Record<string, unknown>): void;
428
+ /**
429
+ * Set the global context label applied to subsequent logs from THIS instance.
430
+ *
431
+ * WARNING: `PinoLoggerService` is a singleton. Calling this per class clobbers
432
+ * the label for every other holder, and calling it at request time is a
433
+ * cross-request race. For a per-class label use `@InjectLogger(MyClass.name)`
434
+ * (which binds a child logger instead of mutating shared state); reserve
435
+ * `setContext()` for a single global / bootstrap label.
436
+ *
437
+ * @param context - The context label.
438
+ */
439
+ setContext(context: string): void;
440
+ /**
441
+ * Access the underlying Pino instance for advanced use cases.
442
+ *
443
+ * @returns The wrapped Pino logger.
444
+ */
445
+ getRawLogger(): Logger;
446
+ /**
447
+ * Create a child logger with additional permanent bindings, inheriting the
448
+ * current context label.
449
+ *
450
+ * @param bindings - Fields merged into every entry of the child logger.
451
+ * @returns A new `PinoLoggerService` wrapping the Pino child.
452
+ */
453
+ child(bindings: Record<string, unknown>): PinoLoggerService;
454
+ /**
455
+ * NestJS shutdown hook. Destination flushing is handled by `DestinationRegistry`;
456
+ * this service has no additional teardown work.
457
+ */
458
+ onApplicationShutdown(): void;
459
+ /**
460
+ * Serialize an Error into a plain, log-safe object matching Pino's built-in
461
+ * `err` serializer shape (`type`, `message`, `stack`).
462
+ *
463
+ * Reads `error.name` (NOT `error.constructor.name`) for `type`: native
464
+ * subclasses set `name` to their class name, and a sanitized plain-object error
465
+ * (from `sanitizeError`) carries the real class name on `name` — whereas its
466
+ * `constructor.name` would be the unhelpful `'Object'`, breaking `err.type`
467
+ * dashboards for every exception logged through `HttpExceptionFilter`.
468
+ */
469
+ private serializeError;
470
+ /** Resolve the context: last string param wins, else the instance context. */
471
+ private resolveContext;
472
+ /** Emit a structured payload at info or warn level. */
473
+ private emitStructured;
474
+ /** Emit a NestJS-style variadic log, dispatching by level without computed indexing. */
475
+ private emitNestStyle;
476
+ }
477
+
478
+ /**
479
+ * Per-request context propagated via AsyncLocalStorage.
480
+ *
481
+ * Consumer code may extend with arbitrary keys via
482
+ * `LogContextService.set(key, value)`; the index signature permits the
483
+ * extension at the type level.
484
+ */
485
+ interface LogContext$1 {
486
+ /** Correlation ID — typically from `x-request-id` header or generated. */
487
+ requestId?: string;
488
+ /** Multi-tenant identifier — read from `x-tenant-id` header or auth claim. */
489
+ tenantId?: string;
490
+ /** Authenticated user ID. */
491
+ userId?: string;
492
+ /** OpenTelemetry trace ID (32 hex chars). Injected by `TraceContextMixin`. */
493
+ traceId?: string;
494
+ /** OpenTelemetry span ID (16 hex chars). */
495
+ spanId?: string;
496
+ /** Free-form extensions added by application code. */
497
+ [key: string]: unknown;
498
+ }
499
+
500
+ /**
501
+ * Injectable wrapper around `AsyncLocalStorage<LogContext>`.
502
+ *
503
+ * A single instance is shared per module (NestJS singleton scope) so the
504
+ * code that opens a scope and the mixin that reads it operate on the same
505
+ * storage.
506
+ *
507
+ * @example
508
+ * logContext.run({ requestId: 'r_1' }, async () => {
509
+ * logContext.set('userId', 'u_1')
510
+ * await handler() // every log inside carries requestId + userId
511
+ * })
512
+ */
513
+ declare class LogContextService {
514
+ private readonly als;
515
+ /**
516
+ * Run `callback` inside a fresh context scope. Every log emitted
517
+ * synchronously or asynchronously within the callback inherits `context`.
518
+ *
519
+ * @typeParam T - Return type of the callback.
520
+ * @param context - Initial context bag for the scope.
521
+ * @param callback - Function executed within the scope.
522
+ * @returns Whatever `callback` returns.
523
+ * @example
524
+ * logContext.run({ requestId: 'r_1', tenantId: 't_1' }, async () => {
525
+ * await userService.create(dto) // logs carry requestId + tenantId
526
+ * })
527
+ */
528
+ run<T>(context: LogContext$1, callback: () => T): T;
529
+ /**
530
+ * Read the active context, or `undefined` when called outside any scope.
531
+ *
532
+ * @returns The active `LogContext`, or `undefined`.
533
+ */
534
+ getStore(): LogContext$1 | undefined;
535
+ /**
536
+ * Add or override a single field on the active context.
537
+ *
538
+ * @param key - Field name to set.
539
+ * @param value - Field value.
540
+ * @throws Error When called outside a `run()` scope, or with a prototype-polluting key.
541
+ */
542
+ set(key: string, value: unknown): void;
543
+ /**
544
+ * Read a single field from the active context.
545
+ *
546
+ * @typeParam T - Expected value type (caller-asserted).
547
+ * @param key - Field name to read.
548
+ * @returns The value cast to `T`, or `undefined` when unset / out of scope.
549
+ */
550
+ get<T = unknown>(key: string): T | undefined;
551
+ /**
552
+ * Strip prototype-polluting keys from a caller-supplied context so an own
553
+ * `__proto__` / `constructor` / `prototype` key (e.g. from `JSON.parse` of an
554
+ * untrusted payload) can never reach the store — and, via the trace mixin's
555
+ * `Object.assign`, the emitted log entry. Mirrors the guard `set()` enforces,
556
+ * but drops the key rather than throwing: a logging concern must never crash
557
+ * the request that produced it.
558
+ *
559
+ * @param context - The raw context bag passed to `run()`.
560
+ * @returns A shallow copy with any forbidden keys removed.
561
+ */
562
+ private sanitizeContext;
563
+ }
564
+
565
+ /**
566
+ * Canonical destination that writes log payloads to `process.stdout`.
567
+ *
568
+ * `process.stdout.write` is buffered by Node.js, which makes it safe to call
569
+ * on the hot path without blocking the event loop. The Pino producer is the
570
+ * back-pressure boundary — this destination is intentionally a thin sink.
571
+ */
572
+ declare class DefaultStdoutDestination implements ILogDestination {
573
+ readonly name = "stdout-json";
574
+ readonly minLevel?: LogLevel;
575
+ /**
576
+ * @param opts.minLevel — Optional minimum level filter. Entries below this
577
+ * level are dropped by the destination registry before reaching `write`.
578
+ * Omit (or pass `undefined`) to accept every level.
579
+ */
580
+ constructor(opts?: {
581
+ minLevel?: LogLevel;
582
+ });
583
+ /**
584
+ * Write a serialized JSON log entry (terminated with `\n`) to `stdout`.
585
+ */
586
+ write(payload: string): void;
587
+ }
588
+
589
+ /**
590
+ * Pretty-print destination backed by `pino-pretty`.
591
+ *
592
+ * Lifecycle contract (driven by `DestinationRegistry`):
593
+ * 1. `onInit()` resolves `pino-pretty` and builds the transform (rendering to
594
+ * `process.stdout`).
595
+ * 2. `write()` forwards each serialized line to the transform.
596
+ * 3. `onShutdown()` flushes and closes the transform.
597
+ *
598
+ * @example
599
+ * BymaxLoggerModule.forRoot({
600
+ * service: { name: 'api', version: '1.0.0' },
601
+ * destinations: [new PrettyDevDestination()]
602
+ * })
603
+ */
604
+ declare class PrettyDevDestination implements ILogDestination {
605
+ readonly name = "pretty-dev";
606
+ readonly minLevel?: LogLevel;
607
+ /**
608
+ * The `pino-pretty` transform stream. Undefined until {@link onInit} succeeds.
609
+ */
610
+ private stream?;
611
+ /**
612
+ * Set when {@link onInit} fails (e.g. `pino-pretty` absent). Distinguishes a
613
+ * permanently-failed destination — whose writes are DROPPED, since the registry
614
+ * dropped it from the active set — from the transient pre-init window, where
615
+ * {@link write} falls back to raw stdout so a bootstrap log is not lost.
616
+ */
617
+ private initFailed;
618
+ /**
619
+ * @param opts.minLevel — Optional minimum level filter applied by the
620
+ * destination registry's multi-stream wiring. Omit to accept every level.
621
+ */
622
+ constructor(opts?: {
623
+ minLevel?: LogLevel;
624
+ });
625
+ /**
626
+ * Lazily resolve `pino-pretty` and build the colorized transform that renders
627
+ * each entry to `process.stdout`.
628
+ *
629
+ * @throws Error When `pino-pretty` is not installed — with an actionable
630
+ * message telling the consumer to install it or drop this destination.
631
+ */
632
+ onInit(): Promise<void>;
633
+ /**
634
+ * Write a serialized NDJSON entry to the pretty transform.
635
+ *
636
+ * Three states:
637
+ * - initialized → forward to the pino-pretty transform;
638
+ * - init failed → DROP (the destination was removed from the active set, so
639
+ * it must not duplicate raw NDJSON to stdout);
640
+ * - pre-init (transient window before {@link onInit}, e.g. a bootstrap log)
641
+ * → fall back to raw stdout so the entry is not lost.
642
+ *
643
+ * @param payload — Newline-terminated JSON entry, UTF-8 encoded.
644
+ */
645
+ write(payload: string): void;
646
+ /**
647
+ * Flush and close the pretty transform, resolving once the stream has ended.
648
+ * A no-op when {@link onInit} never ran.
649
+ */
650
+ onShutdown(): Promise<void>;
651
+ }
652
+
653
+ /**
654
+ * Logs and renders any exception that escapes the request pipeline.
655
+ */
656
+ declare class HttpExceptionFilter implements ExceptionFilter {
657
+ private readonly logger;
658
+ /**
659
+ * @param logger - The structured logger exception entries are written to.
660
+ * Injected by token explicitly: the package is bundled without
661
+ * `emitDecoratorMetadata`, so implicit class-type injection would not resolve.
662
+ */
663
+ constructor(logger: PinoLoggerService);
664
+ /**
665
+ * Log the exception at the status-appropriate level and write the JSON
666
+ * response.
667
+ *
668
+ * @param exception - The thrown value (anything).
669
+ * @param host - The arguments host (switched to HTTP).
670
+ */
671
+ catch(exception: unknown, host: ArgumentsHost): void;
672
+ }
673
+
674
+ /**
675
+ * Logs the full HTTP request lifecycle through a Pino-backed logger.
676
+ */
677
+ declare class HttpLoggingInterceptor implements NestInterceptor {
678
+ private readonly logger;
679
+ /** Path patterns that bypass HTTP logging entirely (health checks, metrics). */
680
+ private readonly excludePaths;
681
+ /**
682
+ * @param logger - The structured logger the lifecycle entries are written to.
683
+ * Injected by token explicitly: the package is bundled without
684
+ * `emitDecoratorMetadata`, so implicit class-type injection would not resolve.
685
+ * @param options - Resolved module options (read for `http.excludePaths`).
686
+ * Injected by token for the same bundling reason.
687
+ */
688
+ constructor(logger: PinoLoggerService, options: ResolvedBymaxLoggerModuleOptions);
689
+ /**
690
+ * Whether a request path bypasses HTTP logging (matches any `excludePaths`).
691
+ *
692
+ * @param path - The query-stripped request path.
693
+ * @returns `true` when the path matches a configured exclude pattern.
694
+ */
695
+ private isExcluded;
696
+ /**
697
+ * Emit the START entry, then attach success/redirect/error logging to the
698
+ * downstream observable while always re-propagating any thrown exception.
699
+ *
700
+ * @param context - The current execution context (HTTP).
701
+ * @param next - The downstream call handler.
702
+ * @returns The (unmodified) response stream.
703
+ */
704
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
705
+ /**
706
+ * Log a non-throwing completion: SUCCESS for 2xx, REDIRECT for 3xx, nothing
707
+ * for any other status (those are surfaced via the error path instead).
708
+ *
709
+ * @param statusCode - The final response status code.
710
+ * @param method - The HTTP method.
711
+ * @param url - The normalized request URL.
712
+ * @param userId - The acting user id, when known.
713
+ * @param duration - The elapsed time in milliseconds.
714
+ */
715
+ private logSuccess;
716
+ /**
717
+ * Log a thrown completion: SERVER_ERROR (error level, with stack) for 5xx,
718
+ * CLIENT_ERROR (warn level) for an `HttpException` below 500. A non-`HttpException`
719
+ * always maps to 500, so it is reported as a server error.
720
+ *
721
+ * @param err - The thrown value (anything).
722
+ * @param method - The HTTP method.
723
+ * @param url - The normalized request URL.
724
+ * @param userId - The acting user id, when known.
725
+ * @param duration - The elapsed time in milliseconds.
726
+ */
727
+ private logError;
728
+ }
729
+
730
+ /**
731
+ * `applyRequestIdMiddleware` consumer helper.
732
+ *
733
+ * Layer: server/middlewares — NestJS requires middleware to be wired in a
734
+ * module's `configure(consumer)` hook, which the library cannot do on the
735
+ * consumer's behalf. This helper encapsulates the one-liner so consumers do not
736
+ * import the middleware class directly.
737
+ */
738
+
739
+ /**
740
+ * Register {@link RequestIdMiddleware} on the given consumer.
741
+ *
742
+ * @param consumer - The `MiddlewareConsumer` from the module's `configure` hook.
743
+ * @param routes - Route pattern(s) to apply the middleware to. Defaults to all.
744
+ * @example
745
+ * import { applyRequestIdMiddleware } from '@bymax-one/nest-logger'
746
+ *
747
+ * @Module({ imports: [BymaxLoggerModule.forRoot({ service, http: { isEnabled: true } })] })
748
+ * export class AppModule implements NestModule {
749
+ * configure(consumer: MiddlewareConsumer): void {
750
+ * applyRequestIdMiddleware(consumer)
751
+ * }
752
+ * }
753
+ */
754
+ declare function applyRequestIdMiddleware(consumer: MiddlewareConsumer, routes?: string): void;
755
+
756
+ /**
757
+ * Reads (or generates) the correlation id and starts the per-request log
758
+ * context scope.
759
+ *
760
+ * @example
761
+ * // Wire it in the consumer's AppModule:
762
+ * export class AppModule implements NestModule {
763
+ * configure(consumer: MiddlewareConsumer): void {
764
+ * applyRequestIdMiddleware(consumer)
765
+ * }
766
+ * }
767
+ */
768
+ declare class RequestIdMiddleware implements NestMiddleware {
769
+ private readonly logContext;
770
+ /** Header name carrying the tenant id, resolved from module options. */
771
+ private readonly tenantIdHeader;
772
+ /** Whether to mint a `requestId` when the inbound header is absent. */
773
+ private readonly shouldGenerateRequestId;
774
+ /**
775
+ * @param logContext - The AsyncLocalStorage-backed context service. Injected by
776
+ * token explicitly: the package is bundled without `emitDecoratorMetadata`,
777
+ * so implicit class-type injection would not resolve.
778
+ * @param options - Resolved module options (read for `http.tenantIdHeader` and
779
+ * `http.shouldGenerateRequestId`).
780
+ */
781
+ constructor(logContext: LogContextService, options: ResolvedBymaxLoggerModuleOptions);
782
+ /**
783
+ * Echo or mint the `x-request-id`, expose it on the response, and run the
784
+ * remainder of the request inside a fresh log-context scope.
785
+ *
786
+ * @param req - The incoming Express request.
787
+ * @param res - The outgoing Express response.
788
+ * @param next - The next handler in the chain.
789
+ */
790
+ use(req: Request, res: Response, next: NextFunction): void;
791
+ }
792
+
793
+ /**
794
+ * Inject a logger into a constructor parameter.
795
+ *
796
+ * With a `context` the injected logger is a child bound to that context (every
797
+ * log carries it); without one, the shared `PinoLoggerService` is injected.
798
+ *
799
+ * @param context - Optional context label (typically the host class name).
800
+ * @returns A parameter decorator wiring the injection (and recording the label).
801
+ * @example
802
+ * @Injectable()
803
+ * export class UsersService {
804
+ * constructor(@InjectLogger('UsersService') private readonly logger: PinoLoggerService) {}
805
+ * }
806
+ * // every `this.logger.*` entry now carries context: 'UsersService'
807
+ */
808
+ declare function InjectLogger(context?: string): ParameterDecorator;
809
+
810
+ /** Reflect-metadata key under which the class context label is stored. */
811
+ declare const LOG_CONTEXT_METADATA_KEY = "bymax_logger:log_context";
812
+ /**
813
+ * Set the log-context label for a class. All loggers injected within the class
814
+ * are intended to adopt this label (auto-application requires a wired interceptor).
815
+ *
816
+ * @param name - The context label (typically the class name).
817
+ * @returns A NestJS metadata decorator carrying the label.
818
+ * @example
819
+ * @LogContext('PaymentsService')
820
+ * @Injectable()
821
+ * export class PaymentsService {}
822
+ */
823
+ declare const LogContext: (name: string) => CustomDecorator<string>;
824
+
825
+ /**
826
+ * Measure and log a method's execution time.
827
+ *
828
+ * @param thresholdMs - Duration (ms) above which the call is logged as slow.
829
+ * Defaults to {@link DEFAULT_THRESHOLD_MS} (1000).
830
+ * @returns A method decorator that wraps the original implementation.
831
+ * @remarks The wrapped method always returns a `Promise` (see the file header):
832
+ * apply only to async methods or callers that can await the result.
833
+ * @example
834
+ * class ReportService {
835
+ * constructor(@InjectLogger() private readonly logger: PinoLoggerService) {}
836
+ *
837
+ * @LogPerformance(500) // warn if it takes > 500ms
838
+ * async generateReport(): Promise<void> {}
839
+ * }
840
+ */
841
+ declare function LogPerformance(thresholdMs?: number): MethodDecorator;
842
+
843
+ /**
844
+ * Dependency-injection tokens used by `BymaxLoggerModule`.
845
+ *
846
+ * Symbols (rather than strings) are used to guarantee uniqueness across the
847
+ * entire NestJS DI graph — two consumers cannot collide on the same token by
848
+ * accident, and accidental string typos cannot resolve a foreign provider.
849
+ *
850
+ * This mirrors the pattern established by `@bymax-one/nest-auth` for every
851
+ * shared Bymax NestJS library.
852
+ */
853
+ /** Injection token for the resolved `BymaxLoggerModuleOptions` snapshot. */
854
+ declare const LOGGER_OPTIONS_TOKEN: unique symbol;
855
+ /** Injection token for the underlying Pino instance (used by services and mixins). */
856
+ declare const LOGGER_PINO_INSTANCE_TOKEN: unique symbol;
857
+ /** Injection token for the resolved destinations array (read-only at runtime). */
858
+ declare const LOGGER_DESTINATIONS_TOKEN: unique symbol;
859
+ /** Injection token for the per-request AsyncLocalStorage-backed log context. */
860
+ declare const LOG_CONTEXT_TOKEN: unique symbol;
861
+
862
+ /**
863
+ * Canonical list of redact paths merged into Pino's `redact.paths` option by
864
+ * default. Expected length: 27 common fields × 4 depths + 5 absolute = **113**.
865
+ */
866
+ declare const DEFAULT_REDACT_PATHS: readonly string[];
867
+
868
+ /**
869
+ * Shape of a single log entry as serialized to JSON output.
870
+ *
871
+ * Used by destinations and downstream parsers (Datadog, Loki, Elastic).
872
+ * The trailing index signature `[key: string]: unknown` covers arbitrary
873
+ * structured fields injected by application logs.
874
+ */
875
+ interface LogEntry {
876
+ /** Pino numeric level (30 = info, 50 = error, etc.). */
877
+ level: number;
878
+ /** ISO 8601 UTC timestamp string OR epoch milliseconds (numeric). */
879
+ time: string | number;
880
+ /** Human-readable message. */
881
+ msg: string;
882
+ /** Convention key — `MODULE_ACTION_RESULT` format. */
883
+ logKey?: string;
884
+ /** Service metadata snapshot — see {@link ServiceMetadata}. */
885
+ service?: ServiceMetadata;
886
+ /** Optional NestJS context (typically the emitting class name). */
887
+ context?: string;
888
+ /** Optional correlation IDs propagated via AsyncLocalStorage. */
889
+ requestId?: string;
890
+ tenantId?: string;
891
+ userId?: string;
892
+ /** Optional trace context injected when OpenTelemetry SDK is active. */
893
+ traceId?: string;
894
+ spanId?: string;
895
+ /** Arbitrary additional fields produced by application logs. */
896
+ [key: string]: unknown;
897
+ }
898
+
899
+ /**
900
+ * Regex enforcing the canonical Bymax log-key convention.
901
+ *
902
+ * The convention is `MODULE_ACTION_RESULT` (e.g., `USER_CREATED`,
903
+ * `AUTH_LOGIN_SUCCESS`), but the regex accepts **two or more** uppercase words —
904
+ * keys in {@link RESERVED_LOG_KEYS} legitimately use four words
905
+ * (e.g. `HTTP_REQUEST_CLIENT_ERROR`, `LOGGER_DESTINATION_WRITE_FAILED`).
906
+ *
907
+ * Pattern: two mandatory uppercase-prefixed groups separated by `_`, plus one
908
+ * optional third group. Within each group, `[A-Z0-9_]+` allows digits and
909
+ * underscores after the leading uppercase letter, which enables additional
910
+ * semantic words inside a group (e.g., `HTTP_REQUEST_CLIENT` is the first group
911
+ * in `HTTP_REQUEST_CLIENT_ERROR`). As a side-effect, strings with a double
912
+ * underscore (`HELLO__WORLD`) also pass; this is an accepted edge case.
913
+ *
914
+ * Enforcement rules:
915
+ * - At least 2 uppercase-prefixed words (`[A-Z][A-Z0-9_]+`)
916
+ * - Each pattern group starts with `A-Z`; no lowercase letters permitted
917
+ * - No leading or trailing underscores at the top level
918
+ *
919
+ * @example
920
+ * LOG_KEYS_CONVENTION_REGEX.test('USER_CREATED') // true
921
+ * LOG_KEYS_CONVENTION_REGEX.test('AUTH_LOGIN_SUCCESS') // true
922
+ * LOG_KEYS_CONVENTION_REGEX.test('HTTP_REQUEST_CLIENT_ERROR') // true (4 words)
923
+ * LOG_KEYS_CONVENTION_REGEX.test('login_success') // false (lowercase)
924
+ * LOG_KEYS_CONVENTION_REGEX.test('LOGIN') // false (single word)
925
+ */
926
+ declare const LOG_KEYS_CONVENTION_REGEX: RegExp;
927
+
928
+ /**
929
+ * Log keys reserved by `@bymax-one/nest-logger` for its own structured events.
930
+ *
931
+ * Consumer apps SHOULD NOT use these names for application-level events
932
+ * to avoid collision in log aggregation queries (Datadog, Loki, Elastic).
933
+ */
934
+ declare const RESERVED_LOG_KEYS: Readonly<{
935
+ readonly LOGGER_BOOTSTRAP_OK: "LOGGER_BOOTSTRAP_OK";
936
+ readonly LOGGER_BOOTSTRAP_WARNING: "LOGGER_BOOTSTRAP_WARNING";
937
+ readonly LOGGER_SHUTDOWN_OK: "LOGGER_SHUTDOWN_OK";
938
+ readonly HTTP_REQUEST_START: "HTTP_REQUEST_START";
939
+ readonly HTTP_REQUEST_SUCCESS: "HTTP_REQUEST_SUCCESS";
940
+ readonly HTTP_REQUEST_REDIRECT: "HTTP_REQUEST_REDIRECT";
941
+ readonly HTTP_REQUEST_CLIENT_ERROR: "HTTP_REQUEST_CLIENT_ERROR";
942
+ readonly HTTP_REQUEST_SERVER_ERROR: "HTTP_REQUEST_SERVER_ERROR";
943
+ readonly HTTP_REQUEST_COMPLETED: "HTTP_REQUEST_COMPLETED";
944
+ readonly HTTP_EXCEPTION_HANDLED: "HTTP_EXCEPTION_HANDLED";
945
+ readonly HTTP_EXCEPTION_UNHANDLED: "HTTP_EXCEPTION_UNHANDLED";
946
+ readonly METHOD_EXECUTION: "METHOD_EXECUTION";
947
+ readonly METHOD_SLOW_EXECUTION: "METHOD_SLOW_EXECUTION";
948
+ readonly LOGGER_DESTINATION_INIT_FAILED: "LOGGER_DESTINATION_INIT_FAILED";
949
+ readonly LOGGER_DESTINATION_WRITE_FAILED: "LOGGER_DESTINATION_WRITE_FAILED";
950
+ readonly LOGGER_ENTRY_TRUNCATED: "LOGGER_ENTRY_TRUNCATED";
951
+ }>;
952
+
953
+ export { BymaxLoggerModule, type BymaxLoggerModuleAsyncOptions, type BymaxLoggerModuleOptions, type BymaxLoggerModuleOptionsFactory, DEFAULT_REDACT_PATHS, DefaultStdoutDestination, HttpExceptionFilter, HttpLoggingInterceptor, type HttpOptions, type ILogDestination, InjectLogger, LOGGER_DESTINATIONS_TOKEN, LOGGER_OPTIONS_TOKEN, LOGGER_PINO_INSTANCE_TOKEN, LOG_CONTEXT_METADATA_KEY, LOG_CONTEXT_TOKEN, LOG_KEYS_CONVENTION_REGEX, LogContext, type LogContext$1 as LogContextBag, LogContextService, type LogEntry, type LogLevel, LogPerformance, type OtelOptions, PinoLoggerService, PrettyDevDestination, RESERVED_LOG_KEYS, RequestIdMiddleware, type ResolvedBymaxLoggerModuleOptions, type ServiceMetadata, applyRequestIdMiddleware };