@bymax-one/nest-logger 1.3.0 → 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.
@@ -260,6 +260,21 @@ interface HttpOptions {
260
260
  * Paths that bypass HTTP logging (health checks, metrics). Defaults:
261
261
  * `/health`, `/metrics`.
262
262
  *
263
+ * The defaults do NOT cover `@bymax-one/nest-core`, and that is the common case
264
+ * worth knowing before you rely on them. It serves liveness and readiness at
265
+ * `/health/live` and `/health/ready`, and no bare `/health` at all, so
266
+ * `/^\/health$/` excludes a route that does not exist while both probes are
267
+ * logged. Measured on one deployment: 8272 of 8274 HTTP entries were the
268
+ * liveness probe. Set `excludePaths` yourself when wiring the two together —
269
+ * `[/^\/health\/(live|ready)$/, /^\/metrics$/]` matches the stock layout.
270
+ *
271
+ * The defaults are not widened to include those subpaths because both prefixes
272
+ * are CONFIGURABLE there (`DEFAULT_HEALTH_PATH`, `DEFAULT_METRICS_PATH`): any
273
+ * default naming specific subpaths would be less wrong rather than correct, and
274
+ * a prefix pattern would silently swallow a consumer's own `/health/*` route
275
+ * that they did want logged. Excluding a path is a decision about which requests
276
+ * disappear from the record, so it stays explicit.
277
+ *
263
278
  * SECURITY: each pattern is `.test()`-ed against the (attacker-controllable)
264
279
  * request path on every request. Supply only anchored, linear-time regexes
265
280
  * (e.g. `/^\/health$/`); a pattern with catastrophic backtracking would turn a
@@ -863,8 +878,21 @@ declare class LogContextService {
863
878
  * Run `callback` inside a fresh context scope. Every log emitted
864
879
  * synchronously or asynchronously within the callback inherits `context`.
865
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
+ *
866
894
  * @typeParam T - Return type of the callback.
867
- * @param context - Initial context bag for the scope.
895
+ * @param context - Initial context bag for the scope. Becomes the WHOLE scope.
868
896
  * @param callback - Function executed within the scope.
869
897
  * @returns Whatever `callback` returns.
870
898
  * @example
@@ -873,6 +901,32 @@ declare class LogContextService {
873
901
  * })
874
902
  */
875
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;
876
930
  /**
877
931
  * Read the active context, or `undefined` when called outside any scope.
878
932
  *
@@ -1279,6 +1333,76 @@ declare class HttpLoggingInterceptor implements NestInterceptor {
1279
1333
  private logError;
1280
1334
  }
1281
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
+
1282
1406
  /**
1283
1407
  * `applyRequestIdMiddleware` consumer helper.
1284
1408
  *
@@ -1404,6 +1528,47 @@ interface LoggableResponse {
1404
1528
  /** Continuation passed to a middleware; invoked with no arguments here. */
1405
1529
  type NextHandler = () => void;
1406
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
+
1407
1572
  /**
1408
1573
  * Reads (or generates) the correlation id and starts the per-request log
1409
1574
  * context scope.
@@ -1432,13 +1597,78 @@ declare class RequestIdMiddleware implements NestMiddleware {
1432
1597
  constructor(logContext: LogContextService, options: ResolvedBymaxLoggerModuleOptions);
1433
1598
  /**
1434
1599
  * Echo or mint the `x-request-id`, expose it on the response, and run the
1435
- * 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.
1436
1620
  *
1437
1621
  * @param req - The incoming request (Express satisfies the contract).
1438
1622
  * @param res - The outgoing response (Express satisfies the contract).
1439
1623
  * @param next - The next handler in the chain.
1440
1624
  */
1441
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;
1442
1672
  }
1443
1673
 
1444
1674
  /**
@@ -1635,4 +1865,4 @@ interface LogEntry {
1635
1865
  */
1636
1866
  declare const LOG_KEYS_CONVENTION_REGEX: RegExp;
1637
1867
 
1638
- 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 };
@@ -260,6 +260,21 @@ interface HttpOptions {
260
260
  * Paths that bypass HTTP logging (health checks, metrics). Defaults:
261
261
  * `/health`, `/metrics`.
262
262
  *
263
+ * The defaults do NOT cover `@bymax-one/nest-core`, and that is the common case
264
+ * worth knowing before you rely on them. It serves liveness and readiness at
265
+ * `/health/live` and `/health/ready`, and no bare `/health` at all, so
266
+ * `/^\/health$/` excludes a route that does not exist while both probes are
267
+ * logged. Measured on one deployment: 8272 of 8274 HTTP entries were the
268
+ * liveness probe. Set `excludePaths` yourself when wiring the two together —
269
+ * `[/^\/health\/(live|ready)$/, /^\/metrics$/]` matches the stock layout.
270
+ *
271
+ * The defaults are not widened to include those subpaths because both prefixes
272
+ * are CONFIGURABLE there (`DEFAULT_HEALTH_PATH`, `DEFAULT_METRICS_PATH`): any
273
+ * default naming specific subpaths would be less wrong rather than correct, and
274
+ * a prefix pattern would silently swallow a consumer's own `/health/*` route
275
+ * that they did want logged. Excluding a path is a decision about which requests
276
+ * disappear from the record, so it stays explicit.
277
+ *
263
278
  * SECURITY: each pattern is `.test()`-ed against the (attacker-controllable)
264
279
  * request path on every request. Supply only anchored, linear-time regexes
265
280
  * (e.g. `/^\/health$/`); a pattern with catastrophic backtracking would turn a
@@ -863,8 +878,21 @@ declare class LogContextService {
863
878
  * Run `callback` inside a fresh context scope. Every log emitted
864
879
  * synchronously or asynchronously within the callback inherits `context`.
865
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
+ *
866
894
  * @typeParam T - Return type of the callback.
867
- * @param context - Initial context bag for the scope.
895
+ * @param context - Initial context bag for the scope. Becomes the WHOLE scope.
868
896
  * @param callback - Function executed within the scope.
869
897
  * @returns Whatever `callback` returns.
870
898
  * @example
@@ -873,6 +901,32 @@ declare class LogContextService {
873
901
  * })
874
902
  */
875
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;
876
930
  /**
877
931
  * Read the active context, or `undefined` when called outside any scope.
878
932
  *
@@ -1279,6 +1333,76 @@ declare class HttpLoggingInterceptor implements NestInterceptor {
1279
1333
  private logError;
1280
1334
  }
1281
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
+
1282
1406
  /**
1283
1407
  * `applyRequestIdMiddleware` consumer helper.
1284
1408
  *
@@ -1404,6 +1528,47 @@ interface LoggableResponse {
1404
1528
  /** Continuation passed to a middleware; invoked with no arguments here. */
1405
1529
  type NextHandler = () => void;
1406
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
+
1407
1572
  /**
1408
1573
  * Reads (or generates) the correlation id and starts the per-request log
1409
1574
  * context scope.
@@ -1432,13 +1597,78 @@ declare class RequestIdMiddleware implements NestMiddleware {
1432
1597
  constructor(logContext: LogContextService, options: ResolvedBymaxLoggerModuleOptions);
1433
1598
  /**
1434
1599
  * Echo or mint the `x-request-id`, expose it on the response, and run the
1435
- * 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.
1436
1620
  *
1437
1621
  * @param req - The incoming request (Express satisfies the contract).
1438
1622
  * @param res - The outgoing response (Express satisfies the contract).
1439
1623
  * @param next - The next handler in the chain.
1440
1624
  */
1441
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;
1442
1672
  }
1443
1673
 
1444
1674
  /**
@@ -1635,4 +1865,4 @@ interface LogEntry {
1635
1865
  */
1636
1866
  declare const LOG_KEYS_CONVENTION_REGEX: RegExp;
1637
1867
 
1638
- 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 };