@bymax-one/nest-logger 1.4.6 → 1.4.8
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 +65 -1
- package/README.md +3 -1
- package/dist/server/index.cjs +56 -16
- package/dist/server/index.d.cts +0 -60
- package/dist/server/index.d.ts +0 -60
- package/dist/server/index.mjs +56 -16
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,68 @@ heading here.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [1.4.8] - 2026-11-01
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **A logging call no longer throws because of what it was handed.** `error`, `warn`, `log`, `fatal`
|
|
19
|
+
and the other NestJS variadic methods threw out of the call when an `Error` had a `name` or `message`
|
|
20
|
+
getter that throws, and when the message had no text form (a null-prototype object, a throwing
|
|
21
|
+
`toString`). In `catch (e) { logger.error(e) }` that second throw replaced the original error and
|
|
22
|
+
nothing was logged. The call now returns and the entry is written: an unreadable `Error` arrives as
|
|
23
|
+
the same `err` node `errorStructured` produces, with `err.type: 'SanitizeFailed'`, and a message with
|
|
24
|
+
no text form is logged as `Unreadable log message`.
|
|
25
|
+
|
|
26
|
+
#### Apply to a derived backend
|
|
27
|
+
|
|
28
|
+
Nothing to change in code. What can differ at run time:
|
|
29
|
+
|
|
30
|
+
- Entries that were never written now appear. A host that handled the throw (a `try` around the logging
|
|
31
|
+
call, or an error handler that reacted to it) no longer sees it; one that relied on the throw to detect
|
|
32
|
+
a hostile input should look for `err.type: 'SanitizeFailed'` (an `Error`) or the message
|
|
33
|
+
`Unreadable log message` (a message) instead.
|
|
34
|
+
|
|
35
|
+
### Tests
|
|
36
|
+
|
|
37
|
+
- Each variadic position with an unreadable `name` or `message`, and each message with no text form on
|
|
38
|
+
`error`, `warn` and `log`, has a failing-first spec through the real Pino pipeline, with a control that
|
|
39
|
+
a readable error is logged as before.
|
|
40
|
+
|
|
41
|
+
## [1.4.7] - 2026-11-01
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **`errorStructured` keeps its `logKey` when the `Error` cannot be read.** A hostile `Error` (a Proxy whose
|
|
46
|
+
`ownKeys` throws, an own getter that throws, a `stack` or `message` getter that throws) made the whole
|
|
47
|
+
record fail the name redaction, so the entry arrived as `LOGGER_REDACTION_FAILED` and lost the caller's
|
|
48
|
+
`logKey`, `userId` and metadata. The `Error` node is now isolated: when it cannot be walked it is replaced
|
|
49
|
+
by its sanitized form and walked again, so the name redaction still applies to whatever can be read. An
|
|
50
|
+
unreadable `stack` or `cause` no longer degrades the node to `SanitizeFailed`; it keeps `name` and
|
|
51
|
+
`message` and carries `_errorFieldsIncomplete: true`. A hostile value that is not an `Error` still fails
|
|
52
|
+
the whole record, as before.
|
|
53
|
+
- **A stale comment in `HttpExceptionFilter` is corrected.** It said the cause chain was dropped at
|
|
54
|
+
`errorStructured`; the entry keeps the cause chain and `AggregateError.errors`, and the response body
|
|
55
|
+
that rides along as an own field is name-redacted. A new spec pins both.
|
|
56
|
+
|
|
57
|
+
#### Apply to a derived backend
|
|
58
|
+
|
|
59
|
+
Nothing to change in code. What can differ at run time:
|
|
60
|
+
|
|
61
|
+
- An alert on `LOGGER_REDACTION_FAILED` that fired for an unreadable `Error` stops firing for it. The event
|
|
62
|
+
now arrives under its own `logKey`, with the `err` node carrying `_errorFieldsIncomplete: true`. To find
|
|
63
|
+
these entries going forward, filter on `err._errorFieldsIncomplete: true` or `err.type: 'SanitizeFailed'`. `LOGGER_REDACTION_FAILED` is
|
|
64
|
+
left for a record that really cannot be read, such as hostile metadata.
|
|
65
|
+
- An error whose `stack` or `cause` getter throws used to reach the log as `{ type: 'SanitizeFailed' }`; it
|
|
66
|
+
now keeps its `type` and `message` and carries the marker. Code that matched `SanitizeFailed` for that
|
|
67
|
+
case should match the marker instead.
|
|
68
|
+
|
|
69
|
+
### Tests
|
|
70
|
+
|
|
71
|
+
- An `Error` that cannot be read has a failing-first spec at the redactor and through the real Pino
|
|
72
|
+
pipeline, including a readable `password` next to a throwing getter and a hostile `message` getter, with
|
|
73
|
+
no clear secret in the line. Two existing cases that pinned the old degradation (`SanitizeFailed` for a
|
|
74
|
+
throwing `stack` getter, the record-level envelope for a hostile `Error`) now pin the new behavior.
|
|
75
|
+
|
|
14
76
|
## [1.4.6] - 2026-11-01
|
|
15
77
|
|
|
16
78
|
### Fixed
|
|
@@ -2276,7 +2338,9 @@ published `dist/` is identical — no runtime behaviour changes for consumers.
|
|
|
2276
2338
|
- Professional CI suite: `ci.yml`, `bench.yml`, `codeql.yml`, `scorecard.yml`,
|
|
2277
2339
|
`release.yml`, Dependabot, and issue templates
|
|
2278
2340
|
|
|
2279
|
-
[Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.4.
|
|
2341
|
+
[Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.4.8...HEAD
|
|
2342
|
+
[1.4.8]: https://github.com/bymaxone/nest-logger/compare/v1.4.7...v1.4.8
|
|
2343
|
+
[1.4.7]: https://github.com/bymaxone/nest-logger/compare/v1.4.6...v1.4.7
|
|
2280
2344
|
[1.4.6]: https://github.com/bymaxone/nest-logger/compare/v1.4.5...v1.4.6
|
|
2281
2345
|
[1.4.5]: https://github.com/bymaxone/nest-logger/compare/v1.4.4...v1.4.5
|
|
2282
2346
|
[1.4.4]: https://github.com/bymaxone/nest-logger/compare/v1.4.3...v1.4.4
|
package/README.md
CHANGED
|
@@ -729,6 +729,8 @@ Pino's standard serializer did.
|
|
|
729
729
|
|
|
730
730
|
When those properties cannot be read (a Proxy whose `ownKeys` throws, or a getter that throws), none of them is copied and the error node carries `_errorFieldsIncomplete: true`, so an entry without its `code` is never mistaken for a complete one.
|
|
731
731
|
|
|
732
|
+
An `Error` that cannot be read at all does not cost the record its `logKey`, `userId` or metadata: the node is replaced by its sanitized form, which keeps `name` and `message` and carries the same marker unless `name` or `message` itself is unreadable, in which case it is `{ type: 'SanitizeFailed' }`. Filter on `err._errorFieldsIncomplete: true` or `SanitizeFailed` to find these entries.
|
|
733
|
+
|
|
732
734
|
### 7. Context propagation with `LogContextService`
|
|
733
735
|
|
|
734
736
|
```typescript
|
|
@@ -1599,7 +1601,7 @@ The `PrettyDevDestination` errors are thrown inside `onInit`, which the registry
|
|
|
1599
1601
|
| `LOGGER_OTEL_API_BROKEN` | NDJSON line on `stderr`, once | `@opentelemetry/api` is installed but throws while loading, or exports no usable `trace` namespace | Reinstall it, or turn `otel.shouldAutoInjectTraceContext` off; until then `traceId` and `spanId` are absent |
|
|
1600
1602
|
| `LOGGER_BOOTSTRAP_WARNING` | log entry with `reason: 'OTEL_API_UNAVAILABLE'` | `otel.shouldAutoInjectTraceContext` is on and `@opentelemetry/api` cannot be resolved | Install `@opentelemetry/api`, or turn the option off; until then `traceId` and `spanId` are absent |
|
|
1601
1603
|
| `LOGGER_ENTRY_TRUNCATED` | `_logKey` in the envelope | a serialized field exceeds `maxEntrySizeBytes` | Reduce the payload or raise the ceiling |
|
|
1602
|
-
| `LOGGER_REDACTION_FAILED` | `_logKey` in the envelope, with `_redactionFailed: true` | a record cannot be walked by the redactor (a throwing getter, a hostile proxy)
|
|
1604
|
+
| `LOGGER_REDACTION_FAILED` | `_logKey` in the envelope, with `_redactionFailed: true` | a record cannot be walked by the redactor (a throwing getter, a hostile proxy in the metadata) | The record is dropped whole, never partly emitted; the message and ambient context stay. Find the value that broke the walk |
|
|
1603
1605
|
|
|
1604
1606
|
---
|
|
1605
1607
|
|
package/dist/server/index.cjs
CHANGED
|
@@ -386,25 +386,41 @@ function sanitizeError(err, options) {
|
|
|
386
386
|
return toSanitizedError(err, /* @__PURE__ */ new WeakSet(), 0, maxCauseDepth);
|
|
387
387
|
}
|
|
388
388
|
function isErrorLike(value) {
|
|
389
|
+
try {
|
|
390
|
+
return readsAsError(value);
|
|
391
|
+
} catch {
|
|
392
|
+
return isErrorInstance(value);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function readsAsError(value) {
|
|
389
396
|
if (typeof value !== "object" || value === null) {
|
|
390
397
|
return false;
|
|
391
398
|
}
|
|
392
399
|
const candidate = value;
|
|
393
400
|
return typeof candidate.name === "string" && typeof candidate.message === "string";
|
|
394
401
|
}
|
|
402
|
+
function isErrorInstance(value) {
|
|
403
|
+
try {
|
|
404
|
+
return value instanceof Error;
|
|
405
|
+
} catch {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
395
409
|
function toSanitizedError(value, seen, depth2, maxDepth) {
|
|
396
410
|
try {
|
|
397
|
-
if (!
|
|
411
|
+
if (!readsAsError(value)) {
|
|
398
412
|
return { name: "UnknownError", message: String(value) };
|
|
399
413
|
}
|
|
400
414
|
seen.add(value);
|
|
401
415
|
const result = { name: value.name, message: value.message };
|
|
402
|
-
|
|
403
|
-
|
|
416
|
+
const stack = readOptional(() => value.stack, result);
|
|
417
|
+
if (stack !== void 0) {
|
|
418
|
+
result.stack = scrubStack(stack);
|
|
404
419
|
}
|
|
405
420
|
const isAtDepthLimit = depth2 >= maxDepth;
|
|
406
|
-
|
|
407
|
-
|
|
421
|
+
const cause = readOptional(() => value.cause, result);
|
|
422
|
+
if (cause !== void 0) {
|
|
423
|
+
result.cause = isAtDepthLimit ? createDepthMarker() : toSanitizedChild(cause, seen, depth2 + 1, maxDepth);
|
|
408
424
|
}
|
|
409
425
|
const aggregated = readAggregatedErrors(value);
|
|
410
426
|
if (aggregated !== void 0) {
|
|
@@ -431,7 +447,18 @@ function assignOwnErrorFields(target, source) {
|
|
|
431
447
|
}
|
|
432
448
|
}
|
|
433
449
|
} catch {
|
|
434
|
-
|
|
450
|
+
markIncomplete(target);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function markIncomplete(target) {
|
|
454
|
+
Reflect.defineProperty(target, "_errorFieldsIncomplete", { value: true, enumerable: true });
|
|
455
|
+
}
|
|
456
|
+
function readOptional(read, node) {
|
|
457
|
+
try {
|
|
458
|
+
return read();
|
|
459
|
+
} catch {
|
|
460
|
+
markIncomplete(node);
|
|
461
|
+
return void 0;
|
|
435
462
|
}
|
|
436
463
|
}
|
|
437
464
|
function toSanitizedChild(value, seen, depth2, maxDepth) {
|
|
@@ -487,6 +514,13 @@ function safeErrorMessage(error) {
|
|
|
487
514
|
return "Unreadable error message";
|
|
488
515
|
}
|
|
489
516
|
}
|
|
517
|
+
function safeLogMessage(message) {
|
|
518
|
+
try {
|
|
519
|
+
return toSingleLineMessage(typeof message === "string" ? message : String(message));
|
|
520
|
+
} catch {
|
|
521
|
+
return "Unreadable log message";
|
|
522
|
+
}
|
|
523
|
+
}
|
|
490
524
|
function findErrorParam(optionalParams) {
|
|
491
525
|
return optionalParams.find((param) => isErrorLike(param));
|
|
492
526
|
}
|
|
@@ -558,10 +592,7 @@ exports.PinoLoggerService = class PinoLoggerService {
|
|
|
558
592
|
assignIfDefined(payload, "context", context);
|
|
559
593
|
assignIfDefined(payload, "stack", stack);
|
|
560
594
|
assignIfDefined(payload, "err", findErrorParam(optionalParams));
|
|
561
|
-
this.pino.error(
|
|
562
|
-
payload,
|
|
563
|
-
toSingleLineMessage(typeof message === "string" ? message : String(message))
|
|
564
|
-
);
|
|
595
|
+
this.pino.error(payload, safeLogMessage(message));
|
|
565
596
|
}
|
|
566
597
|
/**
|
|
567
598
|
* NestJS `warn` (warn level).
|
|
@@ -718,7 +749,7 @@ exports.PinoLoggerService = class PinoLoggerService {
|
|
|
718
749
|
assignIfDefined(payload, "context", this.resolveContext(optionalParams));
|
|
719
750
|
const leading = isErrorLike(message) ? message : void 0;
|
|
720
751
|
assignIfDefined(payload, "err", leading ?? findErrorParam(optionalParams));
|
|
721
|
-
const msg = leading === void 0 ?
|
|
752
|
+
const msg = leading === void 0 ? safeLogMessage(message) : safeErrorMessage(leading);
|
|
722
753
|
switch (level) {
|
|
723
754
|
case "info":
|
|
724
755
|
this.pino.info(payload, msg);
|
|
@@ -1485,6 +1516,19 @@ function walk(value, sensitive, censor, depth2, ancestors, honorToJson = true) {
|
|
|
1485
1516
|
if (ancestors.has(value)) {
|
|
1486
1517
|
return REDACT_CIRCULAR;
|
|
1487
1518
|
}
|
|
1519
|
+
if (value instanceof Error) {
|
|
1520
|
+
return walkError(value, sensitive, censor, depth2, ancestors, honorToJson);
|
|
1521
|
+
}
|
|
1522
|
+
return walkObject(value, sensitive, censor, depth2, ancestors, honorToJson);
|
|
1523
|
+
}
|
|
1524
|
+
function walkError(error, sensitive, censor, depth2, ancestors, honorToJson) {
|
|
1525
|
+
try {
|
|
1526
|
+
return walkObject(error, sensitive, censor, depth2, ancestors, honorToJson);
|
|
1527
|
+
} catch {
|
|
1528
|
+
return walk(sanitizeError(error), sensitive, censor, depth2, ancestors, honorToJson);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
function walkObject(value, sensitive, censor, depth2, ancestors, honorToJson) {
|
|
1488
1532
|
const resolvedToJson = resolveToJson(value);
|
|
1489
1533
|
if (!resolvedToJson.fromAccessor && resolvedToJson.method === BUFFER_TO_JSON) {
|
|
1490
1534
|
return value;
|
|
@@ -3192,11 +3236,7 @@ exports.PinoUnexpectedErrorSink = __decorateClass([
|
|
|
3192
3236
|
__decorateParam(0, common.Inject(exports.PinoLoggerService))
|
|
3193
3237
|
], exports.PinoUnexpectedErrorSink);
|
|
3194
3238
|
function toLoggableError(value) {
|
|
3195
|
-
|
|
3196
|
-
return isErrorLike(value) ? value : sanitizeError(value);
|
|
3197
|
-
} catch {
|
|
3198
|
-
return sanitizeError(value);
|
|
3199
|
-
}
|
|
3239
|
+
return isErrorLike(value) ? value : sanitizeError(value);
|
|
3200
3240
|
}
|
|
3201
3241
|
var INJECT_LOGGER_CONTEXT_METADATA_KEY = "bymax_logger:context";
|
|
3202
3242
|
function InjectLogger(context) {
|
package/dist/server/index.d.cts
CHANGED
|
@@ -618,66 +618,6 @@ declare const RESERVED_LOG_KEYS_NOT_EMITTED: Readonly<{
|
|
|
618
618
|
*/
|
|
619
619
|
type ReservedLogKey = (typeof RESERVED_LOG_KEYS)[keyof typeof RESERVED_LOG_KEYS];
|
|
620
620
|
|
|
621
|
-
/**
|
|
622
|
-
* Name-based recursive redaction.
|
|
623
|
-
*
|
|
624
|
-
* Layer: server/utils — the engine behind the library's DEFAULT PII protection.
|
|
625
|
-
* It walks a log record once and censors every value whose KEY NAME is in the
|
|
626
|
-
* sensitive set, to a bounded depth (see {@link REDACT_MAX_TRAVERSAL_DEPTH}).
|
|
627
|
-
*
|
|
628
|
-
* Why this replaced path matching. The previous default compiled 140
|
|
629
|
-
* `fast-redact` paths, 108 of them multi-level wildcards (`*.password`,
|
|
630
|
-
* `*.*.password`, …). `fast-redact` walks every key at every listed level on
|
|
631
|
-
* every log call, so the cost grew with the PATH COUNT, not with the payload:
|
|
632
|
-
* measured at ~107 µs per entry (≈9.3 k logs/s) against ~0.9 µs with redaction
|
|
633
|
-
* off. A single traversal is O(nodes) — the same order Pino's serializer already
|
|
634
|
-
* pays. The shipped configuration measures 462 k logs/s against 9.3 k, for
|
|
635
|
-
* byte-identical output on every payload the old paths covered.
|
|
636
|
-
*
|
|
637
|
-
* It is also STRICTLY SAFER than the paths it replaces:
|
|
638
|
-
* - depth 100 instead of four, and past it the value is DROPPED rather than
|
|
639
|
-
* passed through, so the ceiling cannot become a leak the way the old one
|
|
640
|
-
* was (anything below the wildcard list's fourth level was emitted in clear);
|
|
641
|
-
* - a sensitive key is caught wherever it appears, so a headers bag logged as
|
|
642
|
-
* `{ headers: { authorization } }` is covered, not only the exact
|
|
643
|
-
* `req.headers.authorization` shape the absolute paths pinned.
|
|
644
|
-
*
|
|
645
|
-
* Three value kinds need special handling, and getting any of them wrong is a
|
|
646
|
-
* leak rather than a cosmetic issue:
|
|
647
|
-
* - `Error` — copied through its prototype and descriptors, so it stays an
|
|
648
|
-
* `Error` (Pino's `err` serializer keys off the instance, and `message` /
|
|
649
|
-
* `stack` are own but NON-enumerable). Skipping errors entirely left an
|
|
650
|
-
* error under any key WITHOUT a serializer serializing its own enumerable
|
|
651
|
-
* properties in clear.
|
|
652
|
-
* - anything carrying `toJSON` (`Date`, `Decimal`, Luxon, Prisma types) — the
|
|
653
|
-
* method decides the serialized form, so the walk inspects its OUTPUT.
|
|
654
|
-
* Skipping these let a `toJSON` SYNTHESIZE a secret straight past both hooks.
|
|
655
|
-
* - `ArrayBuffer` views (`Buffer`, typed arrays) — indexed byte containers,
|
|
656
|
-
* returned untouched.
|
|
657
|
-
*
|
|
658
|
-
* What this file cannot reach: bindings passed to `child()`. Pino pre-serializes
|
|
659
|
-
* them into the instance's `chindings` fragment before any formatter runs, so
|
|
660
|
-
* `PinoLoggerService.child()` applies this redactor itself.
|
|
661
|
-
*
|
|
662
|
-
* Contract: SNAPSHOT and NEVER-THROW. Nothing the caller passed is mutated; the
|
|
663
|
-
* result is a fresh structure built from values read exactly ONCE. That is a
|
|
664
|
-
* deliberate trade against an earlier copy-on-write design which returned a
|
|
665
|
-
* clean subtree by reference: doing so left every accessor in it to be evaluated
|
|
666
|
-
* a SECOND time by `JSON.stringify`, and a stateful getter (or `toJSON`) can
|
|
667
|
-
* answer clean to the walk and `{ password }` to the serializer. Inspection
|
|
668
|
-
* cannot close that window — only reading once and pinning the result can. It
|
|
669
|
-
* costs ~26 % of the entry throughput, measured, and the walk is still ~36x
|
|
670
|
-
* faster than the path expansion it replaced.
|
|
671
|
-
*
|
|
672
|
-
* Any internal failure — a throwing getter, a hostile proxy, a value the walk
|
|
673
|
-
* cannot READ — degrades the whole record to a marked, secret-free envelope rather
|
|
674
|
-
* than crashing the request that produced it. Writing the censor is not among those
|
|
675
|
-
* failures: every copy is built so the write cannot fail, which is why a frozen
|
|
676
|
-
* error keeps its fields instead of costing the record.
|
|
677
|
-
*
|
|
678
|
-
* @see {@link createNameRedactor}
|
|
679
|
-
*/
|
|
680
|
-
|
|
681
621
|
/**
|
|
682
622
|
* A configured redactor.
|
|
683
623
|
*
|
package/dist/server/index.d.ts
CHANGED
|
@@ -618,66 +618,6 @@ declare const RESERVED_LOG_KEYS_NOT_EMITTED: Readonly<{
|
|
|
618
618
|
*/
|
|
619
619
|
type ReservedLogKey = (typeof RESERVED_LOG_KEYS)[keyof typeof RESERVED_LOG_KEYS];
|
|
620
620
|
|
|
621
|
-
/**
|
|
622
|
-
* Name-based recursive redaction.
|
|
623
|
-
*
|
|
624
|
-
* Layer: server/utils — the engine behind the library's DEFAULT PII protection.
|
|
625
|
-
* It walks a log record once and censors every value whose KEY NAME is in the
|
|
626
|
-
* sensitive set, to a bounded depth (see {@link REDACT_MAX_TRAVERSAL_DEPTH}).
|
|
627
|
-
*
|
|
628
|
-
* Why this replaced path matching. The previous default compiled 140
|
|
629
|
-
* `fast-redact` paths, 108 of them multi-level wildcards (`*.password`,
|
|
630
|
-
* `*.*.password`, …). `fast-redact` walks every key at every listed level on
|
|
631
|
-
* every log call, so the cost grew with the PATH COUNT, not with the payload:
|
|
632
|
-
* measured at ~107 µs per entry (≈9.3 k logs/s) against ~0.9 µs with redaction
|
|
633
|
-
* off. A single traversal is O(nodes) — the same order Pino's serializer already
|
|
634
|
-
* pays. The shipped configuration measures 462 k logs/s against 9.3 k, for
|
|
635
|
-
* byte-identical output on every payload the old paths covered.
|
|
636
|
-
*
|
|
637
|
-
* It is also STRICTLY SAFER than the paths it replaces:
|
|
638
|
-
* - depth 100 instead of four, and past it the value is DROPPED rather than
|
|
639
|
-
* passed through, so the ceiling cannot become a leak the way the old one
|
|
640
|
-
* was (anything below the wildcard list's fourth level was emitted in clear);
|
|
641
|
-
* - a sensitive key is caught wherever it appears, so a headers bag logged as
|
|
642
|
-
* `{ headers: { authorization } }` is covered, not only the exact
|
|
643
|
-
* `req.headers.authorization` shape the absolute paths pinned.
|
|
644
|
-
*
|
|
645
|
-
* Three value kinds need special handling, and getting any of them wrong is a
|
|
646
|
-
* leak rather than a cosmetic issue:
|
|
647
|
-
* - `Error` — copied through its prototype and descriptors, so it stays an
|
|
648
|
-
* `Error` (Pino's `err` serializer keys off the instance, and `message` /
|
|
649
|
-
* `stack` are own but NON-enumerable). Skipping errors entirely left an
|
|
650
|
-
* error under any key WITHOUT a serializer serializing its own enumerable
|
|
651
|
-
* properties in clear.
|
|
652
|
-
* - anything carrying `toJSON` (`Date`, `Decimal`, Luxon, Prisma types) — the
|
|
653
|
-
* method decides the serialized form, so the walk inspects its OUTPUT.
|
|
654
|
-
* Skipping these let a `toJSON` SYNTHESIZE a secret straight past both hooks.
|
|
655
|
-
* - `ArrayBuffer` views (`Buffer`, typed arrays) — indexed byte containers,
|
|
656
|
-
* returned untouched.
|
|
657
|
-
*
|
|
658
|
-
* What this file cannot reach: bindings passed to `child()`. Pino pre-serializes
|
|
659
|
-
* them into the instance's `chindings` fragment before any formatter runs, so
|
|
660
|
-
* `PinoLoggerService.child()` applies this redactor itself.
|
|
661
|
-
*
|
|
662
|
-
* Contract: SNAPSHOT and NEVER-THROW. Nothing the caller passed is mutated; the
|
|
663
|
-
* result is a fresh structure built from values read exactly ONCE. That is a
|
|
664
|
-
* deliberate trade against an earlier copy-on-write design which returned a
|
|
665
|
-
* clean subtree by reference: doing so left every accessor in it to be evaluated
|
|
666
|
-
* a SECOND time by `JSON.stringify`, and a stateful getter (or `toJSON`) can
|
|
667
|
-
* answer clean to the walk and `{ password }` to the serializer. Inspection
|
|
668
|
-
* cannot close that window — only reading once and pinning the result can. It
|
|
669
|
-
* costs ~26 % of the entry throughput, measured, and the walk is still ~36x
|
|
670
|
-
* faster than the path expansion it replaced.
|
|
671
|
-
*
|
|
672
|
-
* Any internal failure — a throwing getter, a hostile proxy, a value the walk
|
|
673
|
-
* cannot READ — degrades the whole record to a marked, secret-free envelope rather
|
|
674
|
-
* than crashing the request that produced it. Writing the censor is not among those
|
|
675
|
-
* failures: every copy is built so the write cannot fail, which is why a frozen
|
|
676
|
-
* error keeps its fields instead of costing the record.
|
|
677
|
-
*
|
|
678
|
-
* @see {@link createNameRedactor}
|
|
679
|
-
*/
|
|
680
|
-
|
|
681
621
|
/**
|
|
682
622
|
* A configured redactor.
|
|
683
623
|
*
|
package/dist/server/index.mjs
CHANGED
|
@@ -383,25 +383,41 @@ function sanitizeError(err, options) {
|
|
|
383
383
|
return toSanitizedError(err, /* @__PURE__ */ new WeakSet(), 0, maxCauseDepth);
|
|
384
384
|
}
|
|
385
385
|
function isErrorLike(value) {
|
|
386
|
+
try {
|
|
387
|
+
return readsAsError(value);
|
|
388
|
+
} catch {
|
|
389
|
+
return isErrorInstance(value);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function readsAsError(value) {
|
|
386
393
|
if (typeof value !== "object" || value === null) {
|
|
387
394
|
return false;
|
|
388
395
|
}
|
|
389
396
|
const candidate = value;
|
|
390
397
|
return typeof candidate.name === "string" && typeof candidate.message === "string";
|
|
391
398
|
}
|
|
399
|
+
function isErrorInstance(value) {
|
|
400
|
+
try {
|
|
401
|
+
return value instanceof Error;
|
|
402
|
+
} catch {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
392
406
|
function toSanitizedError(value, seen, depth2, maxDepth) {
|
|
393
407
|
try {
|
|
394
|
-
if (!
|
|
408
|
+
if (!readsAsError(value)) {
|
|
395
409
|
return { name: "UnknownError", message: String(value) };
|
|
396
410
|
}
|
|
397
411
|
seen.add(value);
|
|
398
412
|
const result = { name: value.name, message: value.message };
|
|
399
|
-
|
|
400
|
-
|
|
413
|
+
const stack = readOptional(() => value.stack, result);
|
|
414
|
+
if (stack !== void 0) {
|
|
415
|
+
result.stack = scrubStack(stack);
|
|
401
416
|
}
|
|
402
417
|
const isAtDepthLimit = depth2 >= maxDepth;
|
|
403
|
-
|
|
404
|
-
|
|
418
|
+
const cause = readOptional(() => value.cause, result);
|
|
419
|
+
if (cause !== void 0) {
|
|
420
|
+
result.cause = isAtDepthLimit ? createDepthMarker() : toSanitizedChild(cause, seen, depth2 + 1, maxDepth);
|
|
405
421
|
}
|
|
406
422
|
const aggregated = readAggregatedErrors(value);
|
|
407
423
|
if (aggregated !== void 0) {
|
|
@@ -428,7 +444,18 @@ function assignOwnErrorFields(target, source) {
|
|
|
428
444
|
}
|
|
429
445
|
}
|
|
430
446
|
} catch {
|
|
431
|
-
|
|
447
|
+
markIncomplete(target);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function markIncomplete(target) {
|
|
451
|
+
Reflect.defineProperty(target, "_errorFieldsIncomplete", { value: true, enumerable: true });
|
|
452
|
+
}
|
|
453
|
+
function readOptional(read, node) {
|
|
454
|
+
try {
|
|
455
|
+
return read();
|
|
456
|
+
} catch {
|
|
457
|
+
markIncomplete(node);
|
|
458
|
+
return void 0;
|
|
432
459
|
}
|
|
433
460
|
}
|
|
434
461
|
function toSanitizedChild(value, seen, depth2, maxDepth) {
|
|
@@ -484,6 +511,13 @@ function safeErrorMessage(error) {
|
|
|
484
511
|
return "Unreadable error message";
|
|
485
512
|
}
|
|
486
513
|
}
|
|
514
|
+
function safeLogMessage(message) {
|
|
515
|
+
try {
|
|
516
|
+
return toSingleLineMessage(typeof message === "string" ? message : String(message));
|
|
517
|
+
} catch {
|
|
518
|
+
return "Unreadable log message";
|
|
519
|
+
}
|
|
520
|
+
}
|
|
487
521
|
function findErrorParam(optionalParams) {
|
|
488
522
|
return optionalParams.find((param) => isErrorLike(param));
|
|
489
523
|
}
|
|
@@ -555,10 +589,7 @@ var PinoLoggerService = class {
|
|
|
555
589
|
assignIfDefined(payload, "context", context);
|
|
556
590
|
assignIfDefined(payload, "stack", stack);
|
|
557
591
|
assignIfDefined(payload, "err", findErrorParam(optionalParams));
|
|
558
|
-
this.pino.error(
|
|
559
|
-
payload,
|
|
560
|
-
toSingleLineMessage(typeof message === "string" ? message : String(message))
|
|
561
|
-
);
|
|
592
|
+
this.pino.error(payload, safeLogMessage(message));
|
|
562
593
|
}
|
|
563
594
|
/**
|
|
564
595
|
* NestJS `warn` (warn level).
|
|
@@ -715,7 +746,7 @@ var PinoLoggerService = class {
|
|
|
715
746
|
assignIfDefined(payload, "context", this.resolveContext(optionalParams));
|
|
716
747
|
const leading = isErrorLike(message) ? message : void 0;
|
|
717
748
|
assignIfDefined(payload, "err", leading ?? findErrorParam(optionalParams));
|
|
718
|
-
const msg = leading === void 0 ?
|
|
749
|
+
const msg = leading === void 0 ? safeLogMessage(message) : safeErrorMessage(leading);
|
|
719
750
|
switch (level) {
|
|
720
751
|
case "info":
|
|
721
752
|
this.pino.info(payload, msg);
|
|
@@ -1482,6 +1513,19 @@ function walk(value, sensitive, censor, depth2, ancestors, honorToJson = true) {
|
|
|
1482
1513
|
if (ancestors.has(value)) {
|
|
1483
1514
|
return REDACT_CIRCULAR;
|
|
1484
1515
|
}
|
|
1516
|
+
if (value instanceof Error) {
|
|
1517
|
+
return walkError(value, sensitive, censor, depth2, ancestors, honorToJson);
|
|
1518
|
+
}
|
|
1519
|
+
return walkObject(value, sensitive, censor, depth2, ancestors, honorToJson);
|
|
1520
|
+
}
|
|
1521
|
+
function walkError(error, sensitive, censor, depth2, ancestors, honorToJson) {
|
|
1522
|
+
try {
|
|
1523
|
+
return walkObject(error, sensitive, censor, depth2, ancestors, honorToJson);
|
|
1524
|
+
} catch {
|
|
1525
|
+
return walk(sanitizeError(error), sensitive, censor, depth2, ancestors, honorToJson);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
function walkObject(value, sensitive, censor, depth2, ancestors, honorToJson) {
|
|
1485
1529
|
const resolvedToJson = resolveToJson(value);
|
|
1486
1530
|
if (!resolvedToJson.fromAccessor && resolvedToJson.method === BUFFER_TO_JSON) {
|
|
1487
1531
|
return value;
|
|
@@ -3189,11 +3233,7 @@ PinoUnexpectedErrorSink = __decorateClass([
|
|
|
3189
3233
|
__decorateParam(0, Inject(PinoLoggerService))
|
|
3190
3234
|
], PinoUnexpectedErrorSink);
|
|
3191
3235
|
function toLoggableError(value) {
|
|
3192
|
-
|
|
3193
|
-
return isErrorLike(value) ? value : sanitizeError(value);
|
|
3194
|
-
} catch {
|
|
3195
|
-
return sanitizeError(value);
|
|
3196
|
-
}
|
|
3236
|
+
return isErrorLike(value) ? value : sanitizeError(value);
|
|
3197
3237
|
}
|
|
3198
3238
|
var INJECT_LOGGER_CONTEXT_METADATA_KEY = "bymax_logger:context";
|
|
3199
3239
|
function InjectLogger(context) {
|
package/package.json
CHANGED