@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.
- package/CHANGELOG.md +161 -1
- package/README.md +86 -6
- package/dist/server/index.cjs +214 -15
- package/dist/server/index.d.cts +218 -3
- package/dist/server/index.d.ts +218 -3
- package/dist/server/index.mjs +210 -12
- package/package.json +1 -1
package/dist/server/index.d.cts
CHANGED
|
@@ -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
|
|
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 };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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
|
|
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 };
|