@bymax-one/nest-logger 1.3.1 → 1.4.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.
@@ -878,8 +878,21 @@ declare class LogContextService {
878
878
  * Run `callback` inside a fresh context scope. Every log emitted
879
879
  * synchronously or asynchronously within the callback inherits `context`.
880
880
  *
881
+ * **A nested `run()` REPLACES the enclosing context, it does not extend it.**
882
+ * The scope starts from `context` alone, so a field an outer scope had set —
883
+ * a `tenantId` resolved at the edge, say — is absent for the whole inner
884
+ * scope: nothing throws, `get()` simply returns `undefined`, and the field is
885
+ * missing from every entry the callback emits. Reach for {@link runMerged}
886
+ * when the inner scope should extend the outer one.
887
+ *
888
+ * Replacement is the deliberate default because the opposite leaks: a
889
+ * background job started inside a request scope would silently inherit the
890
+ * caller's `userId` and attribute its entries to a user who never triggered
891
+ * it. A missing field is visibly missing; an inherited one is wrong while
892
+ * looking right.
893
+ *
881
894
  * @typeParam T - Return type of the callback.
882
- * @param context - Initial context bag for the scope.
895
+ * @param context - Initial context bag for the scope. Becomes the WHOLE scope.
883
896
  * @param callback - Function executed within the scope.
884
897
  * @returns Whatever `callback` returns.
885
898
  * @example
@@ -888,6 +901,32 @@ declare class LogContextService {
888
901
  * })
889
902
  */
890
903
  run<T>(context: LogContext$1, callback: () => T): T;
904
+ /**
905
+ * Run `callback` inside a scope that EXTENDS the enclosing one: the active
906
+ * context's fields are carried in, and `context` overrides them key by key.
907
+ *
908
+ * The explicit counterpart to {@link run}'s replacement semantics, for the
909
+ * case where an inner scope is genuinely a refinement of the outer — adding a
910
+ * `userId` once authentication resolved it, without having to restate the
911
+ * `requestId` the middleware already set.
912
+ *
913
+ * Outside any scope there is nothing to merge and this behaves exactly like
914
+ * {@link run}. Writes inside the new scope never reach the enclosing one: the
915
+ * merge produces a fresh object, so the outer context is intact after the
916
+ * callback returns.
917
+ *
918
+ * @typeParam T - Return type of the callback.
919
+ * @param context - Fields to add or override on top of the active context.
920
+ * @param callback - Function executed within the merged scope.
921
+ * @returns Whatever `callback` returns.
922
+ * @example
923
+ * logContext.run({ requestId: 'r_1' }, () => {
924
+ * logContext.runMerged({ userId: 'u_1' }, () => {
925
+ * logContext.get('requestId') // 'r_1' — carried in from the outer scope
926
+ * })
927
+ * })
928
+ */
929
+ runMerged<T>(context: LogContext$1, callback: () => T): T;
891
930
  /**
892
931
  * Read the active context, or `undefined` when called outside any scope.
893
932
  *
@@ -1294,6 +1333,76 @@ declare class HttpLoggingInterceptor implements NestInterceptor {
1294
1333
  private logError;
1295
1334
  }
1296
1335
 
1336
+ /**
1337
+ * `applyAccessLog` consumer helper — the access log mounted AHEAD of the body
1338
+ * parser, from `main.ts`.
1339
+ *
1340
+ * Layer: server/middlewares — the bootstrap-time counterpart to
1341
+ * {@link applyRequestIdMiddleware}. Both mount the same two middlewares in the
1342
+ * same order; they differ only in WHERE the mount lands in the Express stack,
1343
+ * and that difference decides an entire class of requests.
1344
+ *
1345
+ * NestJS registers the body parser one line before it registers module
1346
+ * middleware (`@nestjs/core/nest-application.js`: `registerParserMiddleware()`
1347
+ * then `registerModules()`), and Express dispatches in registration order. So
1348
+ * anything wired through `configure(consumer)` sits downstream of the parser,
1349
+ * and a body the parser rejects — `next(err)` skips every remaining non-error
1350
+ * handler — reaches no module middleware, no guard, no interceptor and no
1351
+ * handler. Measured: a `POST` with truncated JSON produced no access log at all.
1352
+ *
1353
+ * `INestApplication.use()` delegates straight to the HTTP adapter, so a handler
1354
+ * mounted here lands in the Express stack at call time — the same reason `helmet`
1355
+ * and `cookie-parser` are mounted this way.
1356
+ *
1357
+ * The boundary is **`init()`**, not `listen()`. `listen()` merely triggers `init()`
1358
+ * when it has not run yet, so "before `listen()`" is true only for the usual
1359
+ * bootstrap that never calls `init()` itself. A serverless handler or a test that
1360
+ * does `await app.init()` and mounts afterwards is already behind the parser.
1361
+ *
1362
+ * {@link HttpAccessLogMiddleware} needed no new logic to work there: it emits
1363
+ * from the response's `'close'` event, so it never depends on a route matching,
1364
+ * a handler running or a filter catching anything.
1365
+ *
1366
+ * Full rationale, and the table of what each wiring emits today, is in the
1367
+ * README under "Requests rejected before routing".
1368
+ */
1369
+
1370
+ /**
1371
+ * Mount the correlation scope and the HTTP access log ahead of the body parser.
1372
+ *
1373
+ * Call AFTER `NestFactory.create()` and BEFORE the application initializes —
1374
+ * that is, before `app.init()`, or before `app.listen()` in the usual bootstrap
1375
+ * that never calls `init()` directly.
1376
+ *
1377
+ * **`init()` is the real deadline, and `listen()` is only where most bootstraps
1378
+ * meet it.** A serverless entry point or an integration test typically does
1379
+ * `await app.init()` and then wires things up; mounting there still mounts, but
1380
+ * BEHIND the parser, which forfeits the entire reason to use this helper — the
1381
+ * parser-rejected request goes unlogged exactly as before, with no error to say
1382
+ * so. The mount is not rejected when it is late because NestJS exposes no
1383
+ * supported signal for "already initialized" — `isInitialized` is private, and
1384
+ * reaching into the adapter's router to guess would break on a NestJS patch
1385
+ * release while claiming to be a safety net.
1386
+ *
1387
+ * The ordering is the one thing to get right here, so it is worth a glance at
1388
+ * the call site rather than trust: mount it immediately after `NestFactory.create()`.
1389
+ *
1390
+ * Requires `http.isEnabled` for the access log itself; with HTTP logging off the
1391
+ * correlation scope is still opened (matching `applyRequestIdMiddleware`) and no
1392
+ * access entries are written.
1393
+ *
1394
+ * @param app - The Nest application returned by `NestFactory.create()`.
1395
+ * @throws Error When `BymaxLoggerModule` was not imported.
1396
+ * @example
1397
+ * import { BymaxLoggerModule, applyAccessLog } from '@bymax-one/nest-logger'
1398
+ *
1399
+ * const app = await NestFactory.create(AppModule, { bufferLogs: true })
1400
+ * BymaxLoggerModule.useNestLogger(app)
1401
+ * applyAccessLog(app) // before init(): listen() triggers it if you have not
1402
+ * await app.listen(3000)
1403
+ */
1404
+ declare function applyAccessLog(app: INestApplication): void;
1405
+
1297
1406
  /**
1298
1407
  * `applyRequestIdMiddleware` consumer helper.
1299
1408
  *
@@ -1419,6 +1528,47 @@ interface LoggableResponse {
1419
1528
  /** Continuation passed to a middleware; invoked with no arguments here. */
1420
1529
  type NextHandler = () => void;
1421
1530
 
1531
+ /**
1532
+ * Records the HTTP access log for every request, including the ones no
1533
+ * interceptor can observe.
1534
+ */
1535
+ declare class HttpAccessLogMiddleware implements NestMiddleware {
1536
+ private readonly logger;
1537
+ private readonly logContext;
1538
+ /** Whether HTTP logging is on at all (`http.isEnabled`). */
1539
+ private readonly isEnabled;
1540
+ /** Path patterns that bypass HTTP logging entirely (health checks, metrics). */
1541
+ private readonly excludePaths;
1542
+ /**
1543
+ * @param logger - The structured logger the access entries are written to.
1544
+ * Injected by token explicitly: the package is bundled without
1545
+ * `emitDecoratorMetadata`, so implicit class-type injection would not resolve.
1546
+ * @param options - Resolved module options (read for `http.excludePaths`).
1547
+ */
1548
+ constructor(logger: PinoLoggerService, logContext: LogContextService, options: ResolvedBymaxLoggerModuleOptions);
1549
+ /**
1550
+ * Emit START, arm the terminal entry on `'close'`, and continue the chain.
1551
+ *
1552
+ * @param req - The incoming request (Express satisfies the contract).
1553
+ * @param res - The outgoing response (Express satisfies the contract).
1554
+ * @param next - The next handler in the chain.
1555
+ */
1556
+ use(req: LoggableRequest, res: LoggableResponse, next: NextHandler): void;
1557
+ /**
1558
+ * Emit the terminal entry for a closed request.
1559
+ *
1560
+ * `userId` is read HERE rather than at START because authentication runs in a
1561
+ * guard, downstream of this middleware — at START there is no principal yet.
1562
+ *
1563
+ * @param req - The request, carrying any error the interceptor recorded.
1564
+ * @param res - The closed response.
1565
+ * @param method - The HTTP method.
1566
+ * @param url - The normalized request URL.
1567
+ * @param duration - Elapsed milliseconds.
1568
+ */
1569
+ private logTerminal;
1570
+ }
1571
+
1422
1572
  /**
1423
1573
  * Reads (or generates) the correlation id and starts the per-request log
1424
1574
  * context scope.
@@ -1447,13 +1597,78 @@ declare class RequestIdMiddleware implements NestMiddleware {
1447
1597
  constructor(logContext: LogContextService, options: ResolvedBymaxLoggerModuleOptions);
1448
1598
  /**
1449
1599
  * Echo or mint the `x-request-id`, expose it on the response, and run the
1450
- * remainder of the request inside a fresh log-context scope.
1600
+ * remainder of the request inside its own log-context scope.
1601
+ *
1602
+ * Idempotent: mounting this middleware twice on the same request mints one id,
1603
+ * not two. The request gets its OWN store, started from the enclosing scope's
1604
+ * correlation fields — see {@link INHERITABLE_CONTEXT_KEYS} — and nothing
1605
+ * else, because no other field can be known to belong to this request.
1606
+ *
1607
+ * An enclosing scope's `requestId` is never adopted and never echoed: adoption
1608
+ * reads a value this middleware recorded on the REQUEST, which a scope opened
1609
+ * elsewhere does not have. What the two generation modes do with one, measured
1610
+ * on two requests sharing a long-lived enclosing scope:
1611
+ *
1612
+ * - `shouldGenerateRequestId: true` (default) — each request mints its own
1613
+ * id, the two requests differ, and the enclosing value reaches neither the
1614
+ * header nor the entries.
1615
+ * - `shouldGenerateRequestId: false` with no inbound header — nothing is
1616
+ * minted, so the request scope inherits the enclosing `requestId` and every
1617
+ * request inside that scope logs under it. No header is exposed. That is
1618
+ * the consumer's own scope being inherited, not correlation being reused,
1619
+ * and it is the configuration that says the gateway owns the id.
1451
1620
  *
1452
1621
  * @param req - The incoming request (Express satisfies the contract).
1453
1622
  * @param res - The outgoing response (Express satisfies the contract).
1454
1623
  * @param next - The next handler in the chain.
1455
1624
  */
1456
1625
  use(req: LoggableRequest, res: LoggableResponse, next: NextHandler): void;
1626
+ /**
1627
+ * The enclosing scope's fields that a request scope may start from.
1628
+ *
1629
+ * Plain `run()` would discard everything the caller had set and a plain merge
1630
+ * would carry identity across requests; {@link INHERITABLE_CONTEXT_KEYS} is
1631
+ * the middle a request scope actually needs.
1632
+ *
1633
+ * @returns A fresh bag, never the enclosing store itself, so writes inside the
1634
+ * request never reach the scope that opened it.
1635
+ */
1636
+ private inheritableContext;
1637
+ /**
1638
+ * Second mount on a request that already carries a correlation id: echo the id
1639
+ * onto the response and fill in a tenant the scope does not have yet, without
1640
+ * opening another scope.
1641
+ *
1642
+ * Nothing is minted and nothing already set is overwritten, so a third mount —
1643
+ * or a thirtieth — changes nothing. The response header is written on every
1644
+ * pass because the enclosing scope may have been opened by consumer code that
1645
+ * never exposed it.
1646
+ *
1647
+ * @param requestId - The id already in scope, which wins.
1648
+ * @param req - The incoming request, read for the tenant header.
1649
+ * @param res - The outgoing response, where the effective id is exposed.
1650
+ */
1651
+ private adoptExistingScope;
1652
+ /**
1653
+ * The correlation id for this request: the inbound header when acceptable,
1654
+ * otherwise a fresh UUID — or nothing at all when `shouldGenerateRequestId` is
1655
+ * off and the caller sent none, which leaves correlation to the upstream
1656
+ * gateway.
1657
+ *
1658
+ * @param req - The incoming request.
1659
+ * @returns The id, or `undefined` when none is available and none is minted.
1660
+ */
1661
+ private resolveRequestId;
1662
+ /**
1663
+ * The tenant id carried by the configured header, when it is acceptable.
1664
+ *
1665
+ * Never minted: a tenant is an assertion about who the request belongs to, and
1666
+ * inventing one would be a fabricated claim rather than a correlation aid.
1667
+ *
1668
+ * @param req - The incoming request.
1669
+ * @returns The tenant id, or `undefined`.
1670
+ */
1671
+ private resolveTenantId;
1457
1672
  }
1458
1673
 
1459
1674
  /**
@@ -1650,4 +1865,4 @@ interface LogEntry {
1650
1865
  */
1651
1866
  declare const LOG_KEYS_CONVENTION_REGEX: RegExp;
1652
1867
 
1653
- export { BymaxLoggerModule, type BymaxLoggerModuleAsyncOptions, type BymaxLoggerModuleOptions, type BymaxLoggerModuleOptionsFactory, DEFAULT_REDACT_PATHS, DefaultStdoutDestination, type EmittedDeploymentResource, type EmittedServiceResource, HttpExceptionFilter, HttpLoggingInterceptor, type HttpOptions, type ILogDestination, type IncomingHeaders, 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 LoggableRequest, type LoggableResponse, type NextHandler, type OtelOptions, PINO_LEVEL_NAMES, PINO_LEVEL_NUMBERS, PinoLoggerService, PrettyDevDestination, type PrettyViewOptions, RESERVED_LOG_KEYS, RESERVED_LOG_KEYS_NOT_EMITTED, RequestIdMiddleware, type ReservedLogKey, type ResolvedBymaxLoggerModuleOptions, type ResolvedServiceMetadata, type ServiceMetadata, applyRequestIdMiddleware };
1868
+ export { BymaxLoggerModule, type BymaxLoggerModuleAsyncOptions, type BymaxLoggerModuleOptions, type BymaxLoggerModuleOptionsFactory, DEFAULT_REDACT_PATHS, DefaultStdoutDestination, type EmittedDeploymentResource, type EmittedServiceResource, HttpAccessLogMiddleware, HttpExceptionFilter, HttpLoggingInterceptor, type HttpOptions, type ILogDestination, type IncomingHeaders, 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 LoggableRequest, type LoggableResponse, type NextHandler, type OtelOptions, PINO_LEVEL_NAMES, PINO_LEVEL_NUMBERS, PinoLoggerService, PrettyDevDestination, type PrettyViewOptions, RESERVED_LOG_KEYS, RESERVED_LOG_KEYS_NOT_EMITTED, RequestIdMiddleware, type ReservedLogKey, type ResolvedBymaxLoggerModuleOptions, type ResolvedServiceMetadata, type ServiceMetadata, applyAccessLog, applyRequestIdMiddleware };
@@ -878,8 +878,21 @@ declare class LogContextService {
878
878
  * Run `callback` inside a fresh context scope. Every log emitted
879
879
  * synchronously or asynchronously within the callback inherits `context`.
880
880
  *
881
+ * **A nested `run()` REPLACES the enclosing context, it does not extend it.**
882
+ * The scope starts from `context` alone, so a field an outer scope had set —
883
+ * a `tenantId` resolved at the edge, say — is absent for the whole inner
884
+ * scope: nothing throws, `get()` simply returns `undefined`, and the field is
885
+ * missing from every entry the callback emits. Reach for {@link runMerged}
886
+ * when the inner scope should extend the outer one.
887
+ *
888
+ * Replacement is the deliberate default because the opposite leaks: a
889
+ * background job started inside a request scope would silently inherit the
890
+ * caller's `userId` and attribute its entries to a user who never triggered
891
+ * it. A missing field is visibly missing; an inherited one is wrong while
892
+ * looking right.
893
+ *
881
894
  * @typeParam T - Return type of the callback.
882
- * @param context - Initial context bag for the scope.
895
+ * @param context - Initial context bag for the scope. Becomes the WHOLE scope.
883
896
  * @param callback - Function executed within the scope.
884
897
  * @returns Whatever `callback` returns.
885
898
  * @example
@@ -888,6 +901,32 @@ declare class LogContextService {
888
901
  * })
889
902
  */
890
903
  run<T>(context: LogContext$1, callback: () => T): T;
904
+ /**
905
+ * Run `callback` inside a scope that EXTENDS the enclosing one: the active
906
+ * context's fields are carried in, and `context` overrides them key by key.
907
+ *
908
+ * The explicit counterpart to {@link run}'s replacement semantics, for the
909
+ * case where an inner scope is genuinely a refinement of the outer — adding a
910
+ * `userId` once authentication resolved it, without having to restate the
911
+ * `requestId` the middleware already set.
912
+ *
913
+ * Outside any scope there is nothing to merge and this behaves exactly like
914
+ * {@link run}. Writes inside the new scope never reach the enclosing one: the
915
+ * merge produces a fresh object, so the outer context is intact after the
916
+ * callback returns.
917
+ *
918
+ * @typeParam T - Return type of the callback.
919
+ * @param context - Fields to add or override on top of the active context.
920
+ * @param callback - Function executed within the merged scope.
921
+ * @returns Whatever `callback` returns.
922
+ * @example
923
+ * logContext.run({ requestId: 'r_1' }, () => {
924
+ * logContext.runMerged({ userId: 'u_1' }, () => {
925
+ * logContext.get('requestId') // 'r_1' — carried in from the outer scope
926
+ * })
927
+ * })
928
+ */
929
+ runMerged<T>(context: LogContext$1, callback: () => T): T;
891
930
  /**
892
931
  * Read the active context, or `undefined` when called outside any scope.
893
932
  *
@@ -1294,6 +1333,76 @@ declare class HttpLoggingInterceptor implements NestInterceptor {
1294
1333
  private logError;
1295
1334
  }
1296
1335
 
1336
+ /**
1337
+ * `applyAccessLog` consumer helper — the access log mounted AHEAD of the body
1338
+ * parser, from `main.ts`.
1339
+ *
1340
+ * Layer: server/middlewares — the bootstrap-time counterpart to
1341
+ * {@link applyRequestIdMiddleware}. Both mount the same two middlewares in the
1342
+ * same order; they differ only in WHERE the mount lands in the Express stack,
1343
+ * and that difference decides an entire class of requests.
1344
+ *
1345
+ * NestJS registers the body parser one line before it registers module
1346
+ * middleware (`@nestjs/core/nest-application.js`: `registerParserMiddleware()`
1347
+ * then `registerModules()`), and Express dispatches in registration order. So
1348
+ * anything wired through `configure(consumer)` sits downstream of the parser,
1349
+ * and a body the parser rejects — `next(err)` skips every remaining non-error
1350
+ * handler — reaches no module middleware, no guard, no interceptor and no
1351
+ * handler. Measured: a `POST` with truncated JSON produced no access log at all.
1352
+ *
1353
+ * `INestApplication.use()` delegates straight to the HTTP adapter, so a handler
1354
+ * mounted here lands in the Express stack at call time — the same reason `helmet`
1355
+ * and `cookie-parser` are mounted this way.
1356
+ *
1357
+ * The boundary is **`init()`**, not `listen()`. `listen()` merely triggers `init()`
1358
+ * when it has not run yet, so "before `listen()`" is true only for the usual
1359
+ * bootstrap that never calls `init()` itself. A serverless handler or a test that
1360
+ * does `await app.init()` and mounts afterwards is already behind the parser.
1361
+ *
1362
+ * {@link HttpAccessLogMiddleware} needed no new logic to work there: it emits
1363
+ * from the response's `'close'` event, so it never depends on a route matching,
1364
+ * a handler running or a filter catching anything.
1365
+ *
1366
+ * Full rationale, and the table of what each wiring emits today, is in the
1367
+ * README under "Requests rejected before routing".
1368
+ */
1369
+
1370
+ /**
1371
+ * Mount the correlation scope and the HTTP access log ahead of the body parser.
1372
+ *
1373
+ * Call AFTER `NestFactory.create()` and BEFORE the application initializes —
1374
+ * that is, before `app.init()`, or before `app.listen()` in the usual bootstrap
1375
+ * that never calls `init()` directly.
1376
+ *
1377
+ * **`init()` is the real deadline, and `listen()` is only where most bootstraps
1378
+ * meet it.** A serverless entry point or an integration test typically does
1379
+ * `await app.init()` and then wires things up; mounting there still mounts, but
1380
+ * BEHIND the parser, which forfeits the entire reason to use this helper — the
1381
+ * parser-rejected request goes unlogged exactly as before, with no error to say
1382
+ * so. The mount is not rejected when it is late because NestJS exposes no
1383
+ * supported signal for "already initialized" — `isInitialized` is private, and
1384
+ * reaching into the adapter's router to guess would break on a NestJS patch
1385
+ * release while claiming to be a safety net.
1386
+ *
1387
+ * The ordering is the one thing to get right here, so it is worth a glance at
1388
+ * the call site rather than trust: mount it immediately after `NestFactory.create()`.
1389
+ *
1390
+ * Requires `http.isEnabled` for the access log itself; with HTTP logging off the
1391
+ * correlation scope is still opened (matching `applyRequestIdMiddleware`) and no
1392
+ * access entries are written.
1393
+ *
1394
+ * @param app - The Nest application returned by `NestFactory.create()`.
1395
+ * @throws Error When `BymaxLoggerModule` was not imported.
1396
+ * @example
1397
+ * import { BymaxLoggerModule, applyAccessLog } from '@bymax-one/nest-logger'
1398
+ *
1399
+ * const app = await NestFactory.create(AppModule, { bufferLogs: true })
1400
+ * BymaxLoggerModule.useNestLogger(app)
1401
+ * applyAccessLog(app) // before init(): listen() triggers it if you have not
1402
+ * await app.listen(3000)
1403
+ */
1404
+ declare function applyAccessLog(app: INestApplication): void;
1405
+
1297
1406
  /**
1298
1407
  * `applyRequestIdMiddleware` consumer helper.
1299
1408
  *
@@ -1419,6 +1528,47 @@ interface LoggableResponse {
1419
1528
  /** Continuation passed to a middleware; invoked with no arguments here. */
1420
1529
  type NextHandler = () => void;
1421
1530
 
1531
+ /**
1532
+ * Records the HTTP access log for every request, including the ones no
1533
+ * interceptor can observe.
1534
+ */
1535
+ declare class HttpAccessLogMiddleware implements NestMiddleware {
1536
+ private readonly logger;
1537
+ private readonly logContext;
1538
+ /** Whether HTTP logging is on at all (`http.isEnabled`). */
1539
+ private readonly isEnabled;
1540
+ /** Path patterns that bypass HTTP logging entirely (health checks, metrics). */
1541
+ private readonly excludePaths;
1542
+ /**
1543
+ * @param logger - The structured logger the access entries are written to.
1544
+ * Injected by token explicitly: the package is bundled without
1545
+ * `emitDecoratorMetadata`, so implicit class-type injection would not resolve.
1546
+ * @param options - Resolved module options (read for `http.excludePaths`).
1547
+ */
1548
+ constructor(logger: PinoLoggerService, logContext: LogContextService, options: ResolvedBymaxLoggerModuleOptions);
1549
+ /**
1550
+ * Emit START, arm the terminal entry on `'close'`, and continue the chain.
1551
+ *
1552
+ * @param req - The incoming request (Express satisfies the contract).
1553
+ * @param res - The outgoing response (Express satisfies the contract).
1554
+ * @param next - The next handler in the chain.
1555
+ */
1556
+ use(req: LoggableRequest, res: LoggableResponse, next: NextHandler): void;
1557
+ /**
1558
+ * Emit the terminal entry for a closed request.
1559
+ *
1560
+ * `userId` is read HERE rather than at START because authentication runs in a
1561
+ * guard, downstream of this middleware — at START there is no principal yet.
1562
+ *
1563
+ * @param req - The request, carrying any error the interceptor recorded.
1564
+ * @param res - The closed response.
1565
+ * @param method - The HTTP method.
1566
+ * @param url - The normalized request URL.
1567
+ * @param duration - Elapsed milliseconds.
1568
+ */
1569
+ private logTerminal;
1570
+ }
1571
+
1422
1572
  /**
1423
1573
  * Reads (or generates) the correlation id and starts the per-request log
1424
1574
  * context scope.
@@ -1447,13 +1597,78 @@ declare class RequestIdMiddleware implements NestMiddleware {
1447
1597
  constructor(logContext: LogContextService, options: ResolvedBymaxLoggerModuleOptions);
1448
1598
  /**
1449
1599
  * Echo or mint the `x-request-id`, expose it on the response, and run the
1450
- * remainder of the request inside a fresh log-context scope.
1600
+ * remainder of the request inside its own log-context scope.
1601
+ *
1602
+ * Idempotent: mounting this middleware twice on the same request mints one id,
1603
+ * not two. The request gets its OWN store, started from the enclosing scope's
1604
+ * correlation fields — see {@link INHERITABLE_CONTEXT_KEYS} — and nothing
1605
+ * else, because no other field can be known to belong to this request.
1606
+ *
1607
+ * An enclosing scope's `requestId` is never adopted and never echoed: adoption
1608
+ * reads a value this middleware recorded on the REQUEST, which a scope opened
1609
+ * elsewhere does not have. What the two generation modes do with one, measured
1610
+ * on two requests sharing a long-lived enclosing scope:
1611
+ *
1612
+ * - `shouldGenerateRequestId: true` (default) — each request mints its own
1613
+ * id, the two requests differ, and the enclosing value reaches neither the
1614
+ * header nor the entries.
1615
+ * - `shouldGenerateRequestId: false` with no inbound header — nothing is
1616
+ * minted, so the request scope inherits the enclosing `requestId` and every
1617
+ * request inside that scope logs under it. No header is exposed. That is
1618
+ * the consumer's own scope being inherited, not correlation being reused,
1619
+ * and it is the configuration that says the gateway owns the id.
1451
1620
  *
1452
1621
  * @param req - The incoming request (Express satisfies the contract).
1453
1622
  * @param res - The outgoing response (Express satisfies the contract).
1454
1623
  * @param next - The next handler in the chain.
1455
1624
  */
1456
1625
  use(req: LoggableRequest, res: LoggableResponse, next: NextHandler): void;
1626
+ /**
1627
+ * The enclosing scope's fields that a request scope may start from.
1628
+ *
1629
+ * Plain `run()` would discard everything the caller had set and a plain merge
1630
+ * would carry identity across requests; {@link INHERITABLE_CONTEXT_KEYS} is
1631
+ * the middle a request scope actually needs.
1632
+ *
1633
+ * @returns A fresh bag, never the enclosing store itself, so writes inside the
1634
+ * request never reach the scope that opened it.
1635
+ */
1636
+ private inheritableContext;
1637
+ /**
1638
+ * Second mount on a request that already carries a correlation id: echo the id
1639
+ * onto the response and fill in a tenant the scope does not have yet, without
1640
+ * opening another scope.
1641
+ *
1642
+ * Nothing is minted and nothing already set is overwritten, so a third mount —
1643
+ * or a thirtieth — changes nothing. The response header is written on every
1644
+ * pass because the enclosing scope may have been opened by consumer code that
1645
+ * never exposed it.
1646
+ *
1647
+ * @param requestId - The id already in scope, which wins.
1648
+ * @param req - The incoming request, read for the tenant header.
1649
+ * @param res - The outgoing response, where the effective id is exposed.
1650
+ */
1651
+ private adoptExistingScope;
1652
+ /**
1653
+ * The correlation id for this request: the inbound header when acceptable,
1654
+ * otherwise a fresh UUID — or nothing at all when `shouldGenerateRequestId` is
1655
+ * off and the caller sent none, which leaves correlation to the upstream
1656
+ * gateway.
1657
+ *
1658
+ * @param req - The incoming request.
1659
+ * @returns The id, or `undefined` when none is available and none is minted.
1660
+ */
1661
+ private resolveRequestId;
1662
+ /**
1663
+ * The tenant id carried by the configured header, when it is acceptable.
1664
+ *
1665
+ * Never minted: a tenant is an assertion about who the request belongs to, and
1666
+ * inventing one would be a fabricated claim rather than a correlation aid.
1667
+ *
1668
+ * @param req - The incoming request.
1669
+ * @returns The tenant id, or `undefined`.
1670
+ */
1671
+ private resolveTenantId;
1457
1672
  }
1458
1673
 
1459
1674
  /**
@@ -1650,4 +1865,4 @@ interface LogEntry {
1650
1865
  */
1651
1866
  declare const LOG_KEYS_CONVENTION_REGEX: RegExp;
1652
1867
 
1653
- export { BymaxLoggerModule, type BymaxLoggerModuleAsyncOptions, type BymaxLoggerModuleOptions, type BymaxLoggerModuleOptionsFactory, DEFAULT_REDACT_PATHS, DefaultStdoutDestination, type EmittedDeploymentResource, type EmittedServiceResource, HttpExceptionFilter, HttpLoggingInterceptor, type HttpOptions, type ILogDestination, type IncomingHeaders, 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 LoggableRequest, type LoggableResponse, type NextHandler, type OtelOptions, PINO_LEVEL_NAMES, PINO_LEVEL_NUMBERS, PinoLoggerService, PrettyDevDestination, type PrettyViewOptions, RESERVED_LOG_KEYS, RESERVED_LOG_KEYS_NOT_EMITTED, RequestIdMiddleware, type ReservedLogKey, type ResolvedBymaxLoggerModuleOptions, type ResolvedServiceMetadata, type ServiceMetadata, applyRequestIdMiddleware };
1868
+ export { BymaxLoggerModule, type BymaxLoggerModuleAsyncOptions, type BymaxLoggerModuleOptions, type BymaxLoggerModuleOptionsFactory, DEFAULT_REDACT_PATHS, DefaultStdoutDestination, type EmittedDeploymentResource, type EmittedServiceResource, HttpAccessLogMiddleware, HttpExceptionFilter, HttpLoggingInterceptor, type HttpOptions, type ILogDestination, type IncomingHeaders, 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 LoggableRequest, type LoggableResponse, type NextHandler, type OtelOptions, PINO_LEVEL_NAMES, PINO_LEVEL_NUMBERS, PinoLoggerService, PrettyDevDestination, type PrettyViewOptions, RESERVED_LOG_KEYS, RESERVED_LOG_KEYS_NOT_EMITTED, RequestIdMiddleware, type ReservedLogKey, type ResolvedBymaxLoggerModuleOptions, type ResolvedServiceMetadata, type ServiceMetadata, applyAccessLog, applyRequestIdMiddleware };