@bymax-one/nest-logger 1.0.7 → 1.1.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 +37 -1
- package/README.md +7 -7
- package/dist/server/index.cjs +5 -3
- package/dist/server/index.d.cts +21 -3
- package/dist/server/index.d.ts +21 -3
- package/dist/server/index.mjs +5 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,40 @@ heading here.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [1.1.0] - 2026-08-11
|
|
15
|
+
|
|
16
|
+
Coordinated ecosystem release aligning every `@bymax-one/*` package after the ioredis 6 /
|
|
17
|
+
bullmq 6 migration. This is a version-alignment-only release: the published `dist/` is
|
|
18
|
+
identical to `1.0.8`, and nothing changed but the version number.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- No changes since `1.0.8`; version advanced to keep the `@bymax-one/*` line aligned.
|
|
23
|
+
|
|
24
|
+
## [1.0.8] - 2026-08-10
|
|
25
|
+
|
|
26
|
+
Remediation of a local audit's redaction, attribution and correlation-id findings. No API
|
|
27
|
+
changed; the fixes are in the default redact set, the acting-user resolution and the docs.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **Root-level metadata is redacted.** The default redact set now lists each sensitive field at
|
|
32
|
+
depth 0 as well as depths 1–4, so a value spread into a log record's own root — as
|
|
33
|
+
`emitStructured` does with caller metadata — is scrubbed. The depth-1-and-deeper wildcards never
|
|
34
|
+
reached the root, so a field named `password` passed as metadata was written in clear.
|
|
35
|
+
- **Acting-user attribution reads the JWT subject first.** The HTTP logging interceptor and the
|
|
36
|
+
exception filter now resolve the acting user as `sub ?? id`, so a JWT principal (every
|
|
37
|
+
`@bymax-one/nest-auth` token) is attributed to its subject rather than an ORM `id` when a
|
|
38
|
+
principal carries both.
|
|
39
|
+
|
|
40
|
+
### Documentation
|
|
41
|
+
|
|
42
|
+
- The advertised default redact-path count is corrected from 113 to **140** (27 fields across
|
|
43
|
+
depths 0–4, plus 5 absolute header paths) across the README and the technical specification.
|
|
44
|
+
- The correlation-id charset comment now describes what the middleware actually does — echo
|
|
45
|
+
`x-request-id` and store `requestId` in the log context — instead of a non-existent
|
|
46
|
+
error-envelope `correlationId` field.
|
|
47
|
+
|
|
14
48
|
## [1.0.7] - 2026-08-07
|
|
15
49
|
|
|
16
50
|
**Documentation and tooling.** `dist/` differs from `1.0.6` only in the text of the comments
|
|
@@ -316,7 +350,9 @@ published `dist/` is identical — no runtime behaviour changes for consumers.
|
|
|
316
350
|
- Professional CI suite: `ci.yml`, `bench.yml`, `codeql.yml`, `scorecard.yml`,
|
|
317
351
|
`release.yml`, Dependabot, and issue templates
|
|
318
352
|
|
|
319
|
-
[Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.0
|
|
353
|
+
[Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.1.0...HEAD
|
|
354
|
+
[1.1.0]: https://github.com/bymaxone/nest-logger/compare/v1.0.8...v1.1.0
|
|
355
|
+
[1.0.8]: https://github.com/bymaxone/nest-logger/compare/v1.0.7...v1.0.8
|
|
320
356
|
[1.0.7]: https://github.com/bymaxone/nest-logger/compare/v1.0.6...v1.0.7
|
|
321
357
|
[1.0.6]: https://github.com/bymaxone/nest-logger/compare/v1.0.5...v1.0.6
|
|
322
358
|
[1.0.5]: https://github.com/bymaxone/nest-logger/compare/v1.0.4...v1.0.5
|
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ The library has **zero direct dependencies** — all packages arrive as peer dep
|
|
|
43
43
|
|
|
44
44
|
- **🎯 One module, the whole pipeline** — Logger service, HTTP interceptor, exception filter, context propagation, redaction, and destinations arrive in a single `forRoot()`. No gluing together `pino-http`, a redaction layer, and a transport by hand.
|
|
45
45
|
- **🔌 Your sinks, your rules** — The library defines `ILogDestination`. You implement it for Loki, Postgres, a rolling file, or anything else. No vendor lock-in, no hidden transport dependencies.
|
|
46
|
-
- **🔒 Redacted by default** —
|
|
46
|
+
- **🔒 Redacted by default** — 140 redact paths compile at bootstrap covering the standard set: passwords, tokens, PCI DSS card data, MFA secrets, and LGPD documents. Domain-specific field names are yours to add via `redactPaths`.
|
|
47
47
|
- **⚡ On the hot path, so it stays cheap** — Singleton providers, one composed Pino mixin, and a pre-compiled redactor. No `Scope.REQUEST`, no per-log regex matching.
|
|
48
48
|
- **🔭 Correlated when you need it** — When an OpenTelemetry span is active, `traceId`/`spanId`/`traceFlags` land in every entry. When the peer is absent, the mixin steps aside at zero cost.
|
|
49
49
|
|
|
@@ -65,7 +65,7 @@ pnpm add @bymax-one/nest-logger
|
|
|
65
65
|
|
|
66
66
|
### 🛡️ Security & Privacy
|
|
67
67
|
|
|
68
|
-
- ✅ **PII Redaction by Default** —
|
|
68
|
+
- ✅ **PII Redaction by Default** — 140 paths covering passwords, tokens, and generic secrets — powered by `fast-redact`
|
|
69
69
|
- ✅ **PCI DSS & MFA Coverage** — card data and MFA secrets redacted out of the box, with common HTTP auth headers
|
|
70
70
|
- ✅ **LGPD-Aware Paths** — CPF, CNPJ, and RG redacted by default for Brazilian workloads
|
|
71
71
|
- ✅ **Append-Only Redact List** — `DEFAULT_REDACT_PATHS` never shrinks without a major version; extend it via `redactPaths`
|
|
@@ -374,7 +374,7 @@ Full options reference for `BymaxLoggerModule.forRoot(options)`:
|
|
|
374
374
|
| `level` | `LogLevel` | `'info'` | Minimum log level. One of `fatal \| error \| warn \| info \| debug \| trace` |
|
|
375
375
|
| `isPretty` | `boolean` | `!isProduction` | ⚠️ Reserved — not auto-wired. For pretty output add `new PrettyDevDestination()` to `destinations` (needs `pino-pretty`) |
|
|
376
376
|
| `redactPaths` | `string[]` | `[]` | Additional `fast-redact` paths merged with the defaults |
|
|
377
|
-
| `shouldDisableDefaultRedact` | `boolean` | `false` | Skip the
|
|
377
|
+
| `shouldDisableDefaultRedact` | `boolean` | `false` | Skip the 140 default PII paths. ⚠️ Emits a bootstrap warning — document why |
|
|
378
378
|
| `redactCensor` | `string` | `'[REDACTED]'` | Replacement value written in place of every redacted field |
|
|
379
379
|
| `maxEntrySizeBytes` | `number` | `65536` | UTF-8 byte ceiling per serialized field (`err` + any custom serializer); over it the value becomes a truncation envelope |
|
|
380
380
|
| `destinations` | `ILogDestination[]` | `[]` | Additional sinks (Loki, Postgres, rolling file, …) alongside default stdout |
|
|
@@ -605,7 +605,7 @@ Application Service
|
|
|
605
605
|
└── OTel span → { traceId, spanId, traceFlags }
|
|
606
606
|
│
|
|
607
607
|
▼
|
|
608
|
-
fast-redact ← compiled redact function (
|
|
608
|
+
fast-redact ← compiled redact function (140 paths)
|
|
609
609
|
│
|
|
610
610
|
▼
|
|
611
611
|
┌─────────────────────────────┐
|
|
@@ -622,7 +622,7 @@ Application Service
|
|
|
622
622
|
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
623
623
|
| **🪶 Singleton Scope** | `AsyncLocalStorage` delivers per-request context at zero latency overhead — NestJS `Scope.REQUEST` adds ~5% on the injection graph, unacceptable on a logger that runs for every request |
|
|
624
624
|
| **🧬 One Composed Mixin** | ALS context and OTel trace context merge into a single Pino mixin with a deterministic order: ALS first, then OTel — an active span is the authoritative trace identity, so it wins on conflicts |
|
|
625
|
-
| **⚡ Compiled Redaction** |
|
|
625
|
+
| **⚡ Compiled Redaction** | 140 `fast-redact` paths compile once at module bootstrap into a specialized function; no per-log regex matching, under 3% throughput impact |
|
|
626
626
|
| **🔌 Interface-Driven Sinks** | `ILogDestination` is a contract — Loki, Postgres, rolling files, or anything else is a consumer implementation, never a dependency of this package |
|
|
627
627
|
| **🌳 Zero Runtime Deps** | `"dependencies": {}` — every package arrives as a peer dependency, so consumers pin exact versions and the supply-chain surface stays theirs |
|
|
628
628
|
|
|
@@ -634,7 +634,7 @@ A logger sees every payload the application handles, so the security posture is
|
|
|
634
634
|
|
|
635
635
|
### Redaction by default
|
|
636
636
|
|
|
637
|
-
The library ships **
|
|
637
|
+
The library ships **140 redact paths** compiled at initialization into a single `fast-redact` function (< 3% throughput impact). These cover:
|
|
638
638
|
|
|
639
639
|
| Category | Fields |
|
|
640
640
|
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -709,7 +709,7 @@ When integrating `@bymax-one/nest-logger` in production, verify each of the foll
|
|
|
709
709
|
|
|
710
710
|
| Layer | Implementation |
|
|
711
711
|
| ------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
712
|
-
| PII Redaction |
|
|
712
|
+
| PII Redaction | 140 `fast-redact` paths compiled once at bootstrap — no per-log regex matching |
|
|
713
713
|
| Credentials | `password`, `passwordHash`, `token`, `accessToken`, `refreshToken`, `apiKey`, `apiSecret`, `privateKey` |
|
|
714
714
|
| MFA Secrets | `mfaSecret`, `mfaRecoveryCodes`, `totpSecret` — redacted at wildcard depths 1–4 |
|
|
715
715
|
| PCI DSS | `cardNumber`, `cardCvv`, `cvv`, `cvc`, `cardExpiry` |
|
package/dist/server/index.cjs
CHANGED
|
@@ -520,7 +520,7 @@ exports.HttpExceptionFilter = class HttpExceptionFilter {
|
|
|
520
520
|
const ctx = host.switchToHttp();
|
|
521
521
|
const res = ctx.getResponse();
|
|
522
522
|
const req = ctx.getRequest();
|
|
523
|
-
const userId = req.user?.id;
|
|
523
|
+
const userId = req.user?.sub ?? req.user?.id;
|
|
524
524
|
const url = stripQueryString(req.url);
|
|
525
525
|
if (exception instanceof common.HttpException) {
|
|
526
526
|
const status2 = exception.getStatus();
|
|
@@ -605,7 +605,7 @@ exports.HttpLoggingInterceptor = class HttpLoggingInterceptor {
|
|
|
605
605
|
}
|
|
606
606
|
const rawUserAgent = req.headers["user-agent"];
|
|
607
607
|
const userAgent = typeof rawUserAgent === "string" ? rawUserAgent : UNKNOWN_USER_AGENT;
|
|
608
|
-
const userId = req.user?.id;
|
|
608
|
+
const userId = req.user?.sub ?? req.user?.id;
|
|
609
609
|
const normalizedUrl = normalizeUrl(url);
|
|
610
610
|
const start = Date.now();
|
|
611
611
|
this.logger.info(RESERVED_LOG_KEYS.HTTP_REQUEST_START, `${method} ${normalizedUrl}`, userId, {
|
|
@@ -820,6 +820,7 @@ var REDACT_ABSOLUTE_PATHS = [
|
|
|
820
820
|
'res.headers["set-cookie"]'
|
|
821
821
|
];
|
|
822
822
|
var DEFAULT_REDACT_PATHS = [
|
|
823
|
+
...REDACT_COMMON_FIELDS,
|
|
823
824
|
...REDACT_COMMON_FIELDS.flatMap((field) => depth(field)),
|
|
824
825
|
...REDACT_ABSOLUTE_PATHS
|
|
825
826
|
];
|
|
@@ -1344,8 +1345,9 @@ var PrettyDevDestination = class {
|
|
|
1344
1345
|
};
|
|
1345
1346
|
var REQUEST_ID_HEADER = "x-request-id";
|
|
1346
1347
|
var MAX_CORRELATION_ID_LENGTH = 256;
|
|
1348
|
+
var CORRELATION_ID_CHARSET = /^[A-Za-z0-9._:/+=-]+$/;
|
|
1347
1349
|
function isAcceptableHeaderValue(value) {
|
|
1348
|
-
return typeof value === "string" && value.length <= MAX_CORRELATION_ID_LENGTH;
|
|
1350
|
+
return typeof value === "string" && value.length <= MAX_CORRELATION_ID_LENGTH && CORRELATION_ID_CHARSET.test(value);
|
|
1349
1351
|
}
|
|
1350
1352
|
exports.RequestIdMiddleware = class RequestIdMiddleware {
|
|
1351
1353
|
/**
|
package/dist/server/index.d.cts
CHANGED
|
@@ -778,7 +778,11 @@ type IncomingHeaders = Record<string, string | string[] | undefined>;
|
|
|
778
778
|
* The inbound request members the logging layer reads.
|
|
779
779
|
*
|
|
780
780
|
* `user` is populated by upstream auth in the consumer app (a guard or
|
|
781
|
-
* middleware)
|
|
781
|
+
* middleware). The acting-user id is read as `sub` first, then `id`: a JWT principal
|
|
782
|
+
* (every `@bymax-one/nest-auth` token) names it `sub`, while an ORM-style principal
|
|
783
|
+
* names it `id`. The JWT subject wins when both are present, since it is the
|
|
784
|
+
* authenticated identity. Reading only `id` — as this once did — silently dropped
|
|
785
|
+
* the user field for every JWT-authenticated request.
|
|
782
786
|
*/
|
|
783
787
|
interface LoggableRequest {
|
|
784
788
|
/** Inbound headers — read for correlation ids and the user agent. */
|
|
@@ -789,9 +793,14 @@ interface LoggableRequest {
|
|
|
789
793
|
readonly url: string;
|
|
790
794
|
/** Client address when the adapter resolves one. */
|
|
791
795
|
readonly ip?: string | undefined;
|
|
792
|
-
/**
|
|
796
|
+
/**
|
|
797
|
+
* Authenticated principal, when upstream auth attached one. `id` is the
|
|
798
|
+
* ORM-style identifier; `sub` is the JWT subject claim. The logging layer reads
|
|
799
|
+
* whichever is present.
|
|
800
|
+
*/
|
|
793
801
|
readonly user?: {
|
|
794
802
|
readonly id?: string | undefined;
|
|
803
|
+
readonly sub?: string | undefined;
|
|
795
804
|
} | undefined;
|
|
796
805
|
}
|
|
797
806
|
/**
|
|
@@ -920,7 +929,16 @@ declare const LOG_CONTEXT_TOKEN: unique symbol;
|
|
|
920
929
|
|
|
921
930
|
/**
|
|
922
931
|
* Canonical list of redact paths merged into Pino's `redact.paths` option by
|
|
923
|
-
* default. Expected length: 27 common fields × 4 depths + 5
|
|
932
|
+
* default. Expected length: 27 root fields + 27 common fields × 4 depths + 5
|
|
933
|
+
* absolute = **140**.
|
|
934
|
+
*
|
|
935
|
+
* The bare field names come first, at depth 0 — the log record's own root.
|
|
936
|
+
* `emitStructured` spreads caller metadata there (`{ ...metadata, logKey, ... }`),
|
|
937
|
+
* so a field named `password` passed as metadata lands at the root, which the
|
|
938
|
+
* depth-1-and-deeper wildcards below never reach (`fast-redact`'s `*` matches a
|
|
939
|
+
* level, not the root). Without the bare name the library's own documented call
|
|
940
|
+
* `logger.info(key, msg, userId, { password })` would write the password in clear.
|
|
941
|
+
* Listing it makes the default set cover a sensitive field wherever it appears.
|
|
924
942
|
*/
|
|
925
943
|
declare const DEFAULT_REDACT_PATHS: readonly string[];
|
|
926
944
|
|
package/dist/server/index.d.ts
CHANGED
|
@@ -778,7 +778,11 @@ type IncomingHeaders = Record<string, string | string[] | undefined>;
|
|
|
778
778
|
* The inbound request members the logging layer reads.
|
|
779
779
|
*
|
|
780
780
|
* `user` is populated by upstream auth in the consumer app (a guard or
|
|
781
|
-
* middleware)
|
|
781
|
+
* middleware). The acting-user id is read as `sub` first, then `id`: a JWT principal
|
|
782
|
+
* (every `@bymax-one/nest-auth` token) names it `sub`, while an ORM-style principal
|
|
783
|
+
* names it `id`. The JWT subject wins when both are present, since it is the
|
|
784
|
+
* authenticated identity. Reading only `id` — as this once did — silently dropped
|
|
785
|
+
* the user field for every JWT-authenticated request.
|
|
782
786
|
*/
|
|
783
787
|
interface LoggableRequest {
|
|
784
788
|
/** Inbound headers — read for correlation ids and the user agent. */
|
|
@@ -789,9 +793,14 @@ interface LoggableRequest {
|
|
|
789
793
|
readonly url: string;
|
|
790
794
|
/** Client address when the adapter resolves one. */
|
|
791
795
|
readonly ip?: string | undefined;
|
|
792
|
-
/**
|
|
796
|
+
/**
|
|
797
|
+
* Authenticated principal, when upstream auth attached one. `id` is the
|
|
798
|
+
* ORM-style identifier; `sub` is the JWT subject claim. The logging layer reads
|
|
799
|
+
* whichever is present.
|
|
800
|
+
*/
|
|
793
801
|
readonly user?: {
|
|
794
802
|
readonly id?: string | undefined;
|
|
803
|
+
readonly sub?: string | undefined;
|
|
795
804
|
} | undefined;
|
|
796
805
|
}
|
|
797
806
|
/**
|
|
@@ -920,7 +929,16 @@ declare const LOG_CONTEXT_TOKEN: unique symbol;
|
|
|
920
929
|
|
|
921
930
|
/**
|
|
922
931
|
* Canonical list of redact paths merged into Pino's `redact.paths` option by
|
|
923
|
-
* default. Expected length: 27 common fields × 4 depths + 5
|
|
932
|
+
* default. Expected length: 27 root fields + 27 common fields × 4 depths + 5
|
|
933
|
+
* absolute = **140**.
|
|
934
|
+
*
|
|
935
|
+
* The bare field names come first, at depth 0 — the log record's own root.
|
|
936
|
+
* `emitStructured` spreads caller metadata there (`{ ...metadata, logKey, ... }`),
|
|
937
|
+
* so a field named `password` passed as metadata lands at the root, which the
|
|
938
|
+
* depth-1-and-deeper wildcards below never reach (`fast-redact`'s `*` matches a
|
|
939
|
+
* level, not the root). Without the bare name the library's own documented call
|
|
940
|
+
* `logger.info(key, msg, userId, { password })` would write the password in clear.
|
|
941
|
+
* Listing it makes the default set cover a sensitive field wherever it appears.
|
|
924
942
|
*/
|
|
925
943
|
declare const DEFAULT_REDACT_PATHS: readonly string[];
|
|
926
944
|
|
package/dist/server/index.mjs
CHANGED
|
@@ -514,7 +514,7 @@ var HttpExceptionFilter = class {
|
|
|
514
514
|
const ctx = host.switchToHttp();
|
|
515
515
|
const res = ctx.getResponse();
|
|
516
516
|
const req = ctx.getRequest();
|
|
517
|
-
const userId = req.user?.id;
|
|
517
|
+
const userId = req.user?.sub ?? req.user?.id;
|
|
518
518
|
const url = stripQueryString(req.url);
|
|
519
519
|
if (exception instanceof HttpException) {
|
|
520
520
|
const status2 = exception.getStatus();
|
|
@@ -599,7 +599,7 @@ var HttpLoggingInterceptor = class {
|
|
|
599
599
|
}
|
|
600
600
|
const rawUserAgent = req.headers["user-agent"];
|
|
601
601
|
const userAgent = typeof rawUserAgent === "string" ? rawUserAgent : UNKNOWN_USER_AGENT;
|
|
602
|
-
const userId = req.user?.id;
|
|
602
|
+
const userId = req.user?.sub ?? req.user?.id;
|
|
603
603
|
const normalizedUrl = normalizeUrl(url);
|
|
604
604
|
const start = Date.now();
|
|
605
605
|
this.logger.info(RESERVED_LOG_KEYS.HTTP_REQUEST_START, `${method} ${normalizedUrl}`, userId, {
|
|
@@ -814,6 +814,7 @@ var REDACT_ABSOLUTE_PATHS = [
|
|
|
814
814
|
'res.headers["set-cookie"]'
|
|
815
815
|
];
|
|
816
816
|
var DEFAULT_REDACT_PATHS = [
|
|
817
|
+
...REDACT_COMMON_FIELDS,
|
|
817
818
|
...REDACT_COMMON_FIELDS.flatMap((field) => depth(field)),
|
|
818
819
|
...REDACT_ABSOLUTE_PATHS
|
|
819
820
|
];
|
|
@@ -1338,8 +1339,9 @@ var PrettyDevDestination = class {
|
|
|
1338
1339
|
};
|
|
1339
1340
|
var REQUEST_ID_HEADER = "x-request-id";
|
|
1340
1341
|
var MAX_CORRELATION_ID_LENGTH = 256;
|
|
1342
|
+
var CORRELATION_ID_CHARSET = /^[A-Za-z0-9._:/+=-]+$/;
|
|
1341
1343
|
function isAcceptableHeaderValue(value) {
|
|
1342
|
-
return typeof value === "string" && value.length <= MAX_CORRELATION_ID_LENGTH;
|
|
1344
|
+
return typeof value === "string" && value.length <= MAX_CORRELATION_ID_LENGTH && CORRELATION_ID_CHARSET.test(value);
|
|
1343
1345
|
}
|
|
1344
1346
|
var RequestIdMiddleware = class {
|
|
1345
1347
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bymax-one/nest-logger",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Structured JSON logging for NestJS based on Pino 10, with optional OpenTelemetry correlation.",
|
|
5
5
|
"author": "Bymax One <support@bymax.one>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"lint": "eslint src",
|
|
64
64
|
"lint:fix": "eslint src --fix",
|
|
65
65
|
"mutation": "stryker run",
|
|
66
|
+
"mutation:full": "node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run",
|
|
66
67
|
"mutation:dry-run": "stryker run --dryRunOnly",
|
|
67
|
-
"mutation:incremental": "stryker run --incremental",
|
|
68
68
|
"prepare": "husky",
|
|
69
69
|
"prepublishOnly": "pnpm clean && pnpm typecheck && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm check:published",
|
|
70
70
|
"release": "npm publish --provenance --access public",
|