@bymax-one/nest-logger 1.4.5 → 1.4.7
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 +95 -1
- package/README.md +18 -11
- package/dist/server/index.cjs +176 -69
- package/dist/server/index.d.cts +2 -60
- package/dist/server/index.d.ts +2 -60
- package/dist/server/index.mjs +176 -69
- package/dist/shared/index.cjs +3 -1
- package/dist/shared/index.d.cts +2 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.mjs +3 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,98 @@ heading here.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [1.4.7] - 2026-11-01
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **`errorStructured` keeps its `logKey` when the `Error` cannot be read.** A hostile `Error` (a Proxy whose
|
|
19
|
+
`ownKeys` throws, an own getter that throws, a `stack` or `message` getter that throws) made the whole
|
|
20
|
+
record fail the name redaction, so the entry arrived as `LOGGER_REDACTION_FAILED` and lost the caller's
|
|
21
|
+
`logKey`, `userId` and metadata. The `Error` node is now isolated: when it cannot be walked it is replaced
|
|
22
|
+
by its sanitized form and walked again, so the name redaction still applies to whatever can be read. An
|
|
23
|
+
unreadable `stack` or `cause` no longer degrades the node to `SanitizeFailed`; it keeps `name` and
|
|
24
|
+
`message` and carries `_errorFieldsIncomplete: true`. A hostile value that is not an `Error` still fails
|
|
25
|
+
the whole record, as before.
|
|
26
|
+
- **A stale comment in `HttpExceptionFilter` is corrected.** It said the cause chain was dropped at
|
|
27
|
+
`errorStructured`; the entry keeps the cause chain and `AggregateError.errors`, and the response body
|
|
28
|
+
that rides along as an own field is name-redacted. A new spec pins both.
|
|
29
|
+
|
|
30
|
+
#### Apply to a derived backend
|
|
31
|
+
|
|
32
|
+
Nothing to change in code. What can differ at run time:
|
|
33
|
+
|
|
34
|
+
- An alert on `LOGGER_REDACTION_FAILED` that fired for an unreadable `Error` stops firing for it. The event
|
|
35
|
+
now arrives under its own `logKey`, with the `err` node carrying `_errorFieldsIncomplete: true`. To find
|
|
36
|
+
these entries going forward, filter on `err._errorFieldsIncomplete: true` or `err.type: 'SanitizeFailed'`. `LOGGER_REDACTION_FAILED` is
|
|
37
|
+
left for a record that really cannot be read, such as hostile metadata.
|
|
38
|
+
- An error whose `stack` or `cause` getter throws used to reach the log as `{ type: 'SanitizeFailed' }`; it
|
|
39
|
+
now keeps its `type` and `message` and carries the marker. Code that matched `SanitizeFailed` for that
|
|
40
|
+
case should match the marker instead.
|
|
41
|
+
|
|
42
|
+
### Tests
|
|
43
|
+
|
|
44
|
+
- An `Error` that cannot be read has a failing-first spec at the redactor and through the real Pino
|
|
45
|
+
pipeline, including a readable `password` next to a throwing getter and a hostile `message` getter, with
|
|
46
|
+
no clear secret in the line. Two existing cases that pinned the old degradation (`SanitizeFailed` for a
|
|
47
|
+
throwing `stack` getter, the record-level envelope for a hostile `Error`) now pin the new behavior.
|
|
48
|
+
|
|
49
|
+
## [1.4.6] - 2026-11-01
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
Five places swallowed a failed side effect with no trace. Each now leaves one.
|
|
54
|
+
|
|
55
|
+
- **A destination failure whose cause cannot be read is no longer dropped.** When a sink's `onInit`,
|
|
56
|
+
`onRegistryReady` or `write` failed with a value that has no text form (a null-prototype object, a
|
|
57
|
+
throwing `toString`, an `Error` with throwing `name` or `message` getters), the `stderr` line was not
|
|
58
|
+
written at all and the sink stopped receiving entries unannounced. The line is now written with
|
|
59
|
+
`err: { type: 'UnknownError', message: 'unreadable value' }`.
|
|
60
|
+
- **`PinoUnexpectedErrorSink` announces a report it had to drop.** When logging the report threw, or
|
|
61
|
+
the report could not be read, the `HTTP_UNEXPECTED_ERROR` entry, the only record of an unhandled
|
|
62
|
+
exception, vanished silently. One `HTTP_UNEXPECTED_ERROR_DROPPED` line now goes to `stderr`, outside
|
|
63
|
+
the sink set that may be what failed. The response is unaffected.
|
|
64
|
+
- **`PrettyDevDestination` reports the real failure.** Every failure of `import('pino-pretty')` or of
|
|
65
|
+
`build()` was rethrown as "pino-pretty is not installed" with the original error discarded. Only a
|
|
66
|
+
failure to resolve `pino-pretty` itself keeps that message; anything else throws
|
|
67
|
+
`[PrettyDevDestination] pino-pretty failed to start: <message>`, and the original error is the
|
|
68
|
+
`cause` in both cases.
|
|
69
|
+
- **An error node says when its own fields are missing.** When an error's own properties could not be
|
|
70
|
+
read (a Proxy whose `ownKeys` throws, a getter that throws), none was copied and nothing said so. The
|
|
71
|
+
node now carries `_errorFieldsIncomplete: true`.
|
|
72
|
+
- **A broken `@opentelemetry/api` install is reported once.** Every failure to load it was folded into
|
|
73
|
+
"not installed", so correlation could be off with no sign. A missing module stays silent, as
|
|
74
|
+
designed; one that is installed but throws while loading, or exports no usable `trace` namespace, writes one
|
|
75
|
+
`LOGGER_OTEL_API_BROKEN` line to `stderr`.
|
|
76
|
+
|
|
77
|
+
#### Apply to a derived backend
|
|
78
|
+
|
|
79
|
+
Nothing to change in code. What can differ at run time:
|
|
80
|
+
|
|
81
|
+
- `stderr` can carry two lines it never carried: `HTTP_UNEXPECTED_ERROR_DROPPED` and
|
|
82
|
+
`LOGGER_OTEL_API_BROKEN`, both NDJSON like the destination-failure lines. Add them to whatever
|
|
83
|
+
alerts on the library's `stderr` codes. `RESERVED_LOG_KEYS` gains these two keys, so an application
|
|
84
|
+
event must not reuse them.
|
|
85
|
+
- A host that already logs an event under `HTTP_UNEXPECTED_ERROR_DROPPED` or `LOGGER_OTEL_API_BROKEN`
|
|
86
|
+
sees no change in that entry: nothing is renamed, rejected or dropped, and its `event.name` is
|
|
87
|
+
derived from the key as for any other (`http.unexpected.error.dropped`, `logger.otel.api.broken`).
|
|
88
|
+
What changes is that, where stdout and stderr reach the same place, an alert or filter on either
|
|
89
|
+
key now matches the library's `stderr` line and the host's own entries alike. Rename the host event.
|
|
90
|
+
- `LOGGER_DESTINATION_INIT_FAILED`, `LOGGER_DESTINATION_WRITE_FAILED` and the hook-failure line now
|
|
91
|
+
appear for failures that were silent before. A deployment that saw none was either healthy or
|
|
92
|
+
losing entries without knowing.
|
|
93
|
+
- Code that matches the text "pino-pretty is not installed" on a `PrettyDevDestination` init error now
|
|
94
|
+
matches only a genuine resolution failure; match `failed to start` for the rest, or read `cause`.
|
|
95
|
+
- Log processing that reads error nodes can meet `_errorFieldsIncomplete: true`, on an error whose own
|
|
96
|
+
fields could not be read.
|
|
97
|
+
|
|
98
|
+
### Tests
|
|
99
|
+
|
|
100
|
+
- A failure whose cause cannot be read, a dropped unexpected-error report, a `pino-pretty` failure of
|
|
101
|
+
each kind, an error whose own fields cannot be enumerated and a broken `@opentelemetry/api` each have
|
|
102
|
+
a failing-first spec. The fixtures that simulated a missing `pino-pretty` or `@opentelemetry/api` now
|
|
103
|
+
carry the resolution code Node attaches to the real error, because the code is what tells an absent
|
|
104
|
+
module from a broken one.
|
|
105
|
+
|
|
14
106
|
## [1.4.5] - 2026-11-01
|
|
15
107
|
|
|
16
108
|
### Documentation
|
|
@@ -2219,7 +2311,9 @@ published `dist/` is identical — no runtime behaviour changes for consumers.
|
|
|
2219
2311
|
- Professional CI suite: `ci.yml`, `bench.yml`, `codeql.yml`, `scorecard.yml`,
|
|
2220
2312
|
`release.yml`, Dependabot, and issue templates
|
|
2221
2313
|
|
|
2222
|
-
[Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.4.
|
|
2314
|
+
[Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.4.7...HEAD
|
|
2315
|
+
[1.4.7]: https://github.com/bymaxone/nest-logger/compare/v1.4.6...v1.4.7
|
|
2316
|
+
[1.4.6]: https://github.com/bymaxone/nest-logger/compare/v1.4.5...v1.4.6
|
|
2223
2317
|
[1.4.5]: https://github.com/bymaxone/nest-logger/compare/v1.4.4...v1.4.5
|
|
2224
2318
|
[1.4.4]: https://github.com/bymaxone/nest-logger/compare/v1.4.3...v1.4.4
|
|
2225
2319
|
[1.4.3]: https://github.com/bymaxone/nest-logger/compare/v1.4.2...v1.4.3
|
package/README.md
CHANGED
|
@@ -179,7 +179,7 @@ pnpm add @opentelemetry/api @opentelemetry/sdk-node
|
|
|
179
179
|
> packaging.
|
|
180
180
|
|
|
181
181
|
> [!NOTE]
|
|
182
|
-
> `@opentelemetry/api` is resolved lazily when the Pino instance is built. When it is absent the trace mixin silently steps aside — the logger never fails to start, and never warns, over an optional peer.
|
|
182
|
+
> `@opentelemetry/api` is resolved lazily when the Pino instance is built. When it is absent the trace mixin silently steps aside — the logger never fails to start, and never warns, over an optional peer. A copy that is installed but broken is different: it is reported once on `stderr` as `LOGGER_OTEL_API_BROKEN`, because correlation would otherwise be off with no sign.
|
|
183
183
|
|
|
184
184
|
> [!NOTE]
|
|
185
185
|
> The published declarations depend on no HTTP framework's types. The HTTP interceptor, the exception filter and the request-id middleware are typed by structural contracts (`LoggableRequest`, `LoggableResponse`, `NextHandler`) that an Express request and response satisfy as-is, so nothing extra is needed even when you compile with `skipLibCheck: false`. The emitted `.d.ts` imports only `@nestjs/common`, `pino` and `rxjs`.
|
|
@@ -727,6 +727,10 @@ any other serializer output.
|
|
|
727
727
|
An error's own enumerable properties (`code`, `statusCode`, domain fields) are carried through, as
|
|
728
728
|
Pino's standard serializer did.
|
|
729
729
|
|
|
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
|
+
|
|
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
|
+
|
|
730
734
|
### 7. Context propagation with `LogContextService`
|
|
731
735
|
|
|
732
736
|
```typescript
|
|
@@ -843,7 +847,7 @@ function assertValidLogKey(key: string) {
|
|
|
843
847
|
|
|
844
848
|
The following keys are used internally by the library — do not reuse them in application code:
|
|
845
849
|
|
|
846
|
-
`LOGGER_BOOTSTRAP_OK` · `LOGGER_BOOTSTRAP_WARNING` · `LOGGER_SHUTDOWN_OK` · `HTTP_REQUEST_START` · `HTTP_REQUEST_SUCCESS` · `HTTP_REQUEST_REDIRECT` · `HTTP_REQUEST_CLIENT_ERROR` · `HTTP_REQUEST_SERVER_ERROR` · `HTTP_REQUEST_ABORTED` · `HTTP_REQUEST_COMPLETED` · `HTTP_EXCEPTION_HANDLED` · `HTTP_EXCEPTION_UNHANDLED` · `HTTP_UNEXPECTED_ERROR` · `METHOD_EXECUTION` · `METHOD_SLOW_EXECUTION` · `LOGGER_DESTINATION_INIT_FAILED` · `LOGGER_DESTINATION_WRITE_FAILED` · `LOGGER_ENTRY_TRUNCATED` · `LOGGER_REDACTION_FAILED`
|
|
850
|
+
`LOGGER_BOOTSTRAP_OK` · `LOGGER_BOOTSTRAP_WARNING` · `LOGGER_SHUTDOWN_OK` · `HTTP_REQUEST_START` · `HTTP_REQUEST_SUCCESS` · `HTTP_REQUEST_REDIRECT` · `HTTP_REQUEST_CLIENT_ERROR` · `HTTP_REQUEST_SERVER_ERROR` · `HTTP_REQUEST_ABORTED` · `HTTP_REQUEST_COMPLETED` · `HTTP_EXCEPTION_HANDLED` · `HTTP_EXCEPTION_UNHANDLED` · `HTTP_UNEXPECTED_ERROR` · `HTTP_UNEXPECTED_ERROR_DROPPED` · `METHOD_EXECUTION` · `METHOD_SLOW_EXECUTION` · `LOGGER_DESTINATION_INIT_FAILED` · `LOGGER_DESTINATION_WRITE_FAILED` · `LOGGER_ENTRY_TRUNCATED` · `LOGGER_REDACTION_FAILED` · `LOGGER_OTEL_API_BROKEN`
|
|
847
851
|
|
|
848
852
|
All reserved keys are exported as the `RESERVED_LOG_KEYS` constant from `@bymax-one/nest-logger/shared`.
|
|
849
853
|
|
|
@@ -1579,22 +1583,25 @@ Two kinds of failure, and they surface differently. A **thrown `Error`** carries
|
|
|
1579
1583
|
| `[BymaxLoggerModule] options.` | `forRoot` / `forRootAsync`, at module setup | any option fails validation: a missing `service`, a `level` outside the set, a bad type | Fix the option the message names; it prints the path and what it received |
|
|
1580
1584
|
| `[BymaxLoggerModule] useNestLogger(app) called but` | `BymaxLoggerModule.useNestLogger` | the module was never imported into the application | Import `BymaxLoggerModule` before calling it |
|
|
1581
1585
|
| `[BymaxLoggerModule] applyAccessLog(app) called but` | `applyAccessLog` | the module was never imported into the application | Import `BymaxLoggerModule` before calling it |
|
|
1582
|
-
| `[PrettyDevDestination] pino-pretty is not installed` | `PrettyDevDestination.onInit` | the destination is registered and
|
|
1586
|
+
| `[PrettyDevDestination] pino-pretty is not installed` | `PrettyDevDestination.onInit` | the destination is registered and resolving `pino-pretty` itself fails | Install `pino-pretty` or drop the destination |
|
|
1587
|
+
| `[PrettyDevDestination] pino-pretty failed to start` | `PrettyDevDestination.onInit` | `pino-pretty` is found but importing it or building the transform throws | Read the message and the error's `cause`: it is the real failure |
|
|
1583
1588
|
| `[PrettyDevDestination] refused in production` | `PrettyDevDestination.onInit` | `NODE_ENV` is exactly `production` | Use `DefaultStdoutDestination` in production |
|
|
1584
1589
|
| `[LogContextService] set() called outside run() scope` | `LogContextService.set` | called with no active `run()` | Wrap the work in `logContext.run({ ... }, () => ...)` |
|
|
1585
1590
|
| `[LogContextService] Refusing to set unsafe context key` | `LogContextService.set` | the key is `__proto__`, `constructor` or `prototype` | Use an ordinary key |
|
|
1586
1591
|
|
|
1587
|
-
The
|
|
1592
|
+
The `PrettyDevDestination` errors are thrown inside `onInit`, which the registry contains: the boot continues and the failure is reported as `LOGGER_DESTINATION_INIT_FAILED` below.
|
|
1588
1593
|
|
|
1589
1594
|
### Log keys
|
|
1590
1595
|
|
|
1591
|
-
| Code | Surfaces as | When
|
|
1592
|
-
| --------------------------------- | -------------------------------------------------------- |
|
|
1593
|
-
| `LOGGER_DESTINATION_INIT_FAILED` | NDJSON line on `stderr` | `destination.onInit()` throws or rejects
|
|
1594
|
-
| `LOGGER_DESTINATION_WRITE_FAILED` | NDJSON line on `stderr` | `destination.write()` throws or rejects
|
|
1595
|
-
| `
|
|
1596
|
-
| `
|
|
1597
|
-
| `
|
|
1596
|
+
| Code | Surfaces as | When | Action |
|
|
1597
|
+
| --------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
1598
|
+
| `LOGGER_DESTINATION_INIT_FAILED` | NDJSON line on `stderr` | `destination.onInit()` throws or rejects | Sink is removed from the write fan-out and shutdown; boot continues |
|
|
1599
|
+
| `LOGGER_DESTINATION_WRITE_FAILED` | NDJSON line on `stderr` | `destination.write()` throws or rejects | Entry dropped for that destination; others continue |
|
|
1600
|
+
| `HTTP_UNEXPECTED_ERROR_DROPPED` | NDJSON line on `stderr` | `PinoUnexpectedErrorSink` could not log a report (the logger threw, or the report could not be read) | The report is dropped; the line names why. The response is unaffected |
|
|
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 |
|
|
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 |
|
|
1603
|
+
| `LOGGER_ENTRY_TRUNCATED` | `_logKey` in the envelope | a serialized field exceeds `maxEntrySizeBytes` | Reduce the payload or raise the ceiling |
|
|
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 |
|
|
1598
1605
|
|
|
1599
1606
|
---
|
|
1600
1607
|
|
package/dist/server/index.cjs
CHANGED
|
@@ -325,12 +325,14 @@ var RESERVED_LOG_KEYS = Object.freeze({
|
|
|
325
325
|
HTTP_EXCEPTION_HANDLED: "HTTP_EXCEPTION_HANDLED",
|
|
326
326
|
HTTP_EXCEPTION_UNHANDLED: "HTTP_EXCEPTION_UNHANDLED",
|
|
327
327
|
HTTP_UNEXPECTED_ERROR: "HTTP_UNEXPECTED_ERROR",
|
|
328
|
+
HTTP_UNEXPECTED_ERROR_DROPPED: "HTTP_UNEXPECTED_ERROR_DROPPED",
|
|
328
329
|
METHOD_EXECUTION: "METHOD_EXECUTION",
|
|
329
330
|
METHOD_SLOW_EXECUTION: "METHOD_SLOW_EXECUTION",
|
|
330
331
|
LOGGER_DESTINATION_INIT_FAILED: "LOGGER_DESTINATION_INIT_FAILED",
|
|
331
332
|
LOGGER_DESTINATION_WRITE_FAILED: "LOGGER_DESTINATION_WRITE_FAILED",
|
|
332
333
|
LOGGER_ENTRY_TRUNCATED: "LOGGER_ENTRY_TRUNCATED",
|
|
333
|
-
LOGGER_REDACTION_FAILED: "LOGGER_REDACTION_FAILED"
|
|
334
|
+
LOGGER_REDACTION_FAILED: "LOGGER_REDACTION_FAILED",
|
|
335
|
+
LOGGER_OTEL_API_BROKEN: "LOGGER_OTEL_API_BROKEN"
|
|
334
336
|
});
|
|
335
337
|
var RESERVED_LOG_KEYS_NOT_EMITTED = Object.freeze({
|
|
336
338
|
/**
|
|
@@ -397,12 +399,14 @@ function toSanitizedError(value, seen, depth2, maxDepth) {
|
|
|
397
399
|
}
|
|
398
400
|
seen.add(value);
|
|
399
401
|
const result = { name: value.name, message: value.message };
|
|
400
|
-
|
|
401
|
-
|
|
402
|
+
const stack = readOptional(() => value.stack, result);
|
|
403
|
+
if (stack !== void 0) {
|
|
404
|
+
result.stack = scrubStack(stack);
|
|
402
405
|
}
|
|
403
406
|
const isAtDepthLimit = depth2 >= maxDepth;
|
|
404
|
-
|
|
405
|
-
|
|
407
|
+
const cause = readOptional(() => value.cause, result);
|
|
408
|
+
if (cause !== void 0) {
|
|
409
|
+
result.cause = isAtDepthLimit ? createDepthMarker() : toSanitizedChild(cause, seen, depth2 + 1, maxDepth);
|
|
406
410
|
}
|
|
407
411
|
const aggregated = readAggregatedErrors(value);
|
|
408
412
|
if (aggregated !== void 0) {
|
|
@@ -429,6 +433,18 @@ function assignOwnErrorFields(target, source) {
|
|
|
429
433
|
}
|
|
430
434
|
}
|
|
431
435
|
} catch {
|
|
436
|
+
markIncomplete(target);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function markIncomplete(target) {
|
|
440
|
+
Reflect.defineProperty(target, "_errorFieldsIncomplete", { value: true, enumerable: true });
|
|
441
|
+
}
|
|
442
|
+
function readOptional(read, node) {
|
|
443
|
+
try {
|
|
444
|
+
return read();
|
|
445
|
+
} catch {
|
|
446
|
+
markIncomplete(node);
|
|
447
|
+
return void 0;
|
|
432
448
|
}
|
|
433
449
|
}
|
|
434
450
|
function toSanitizedChild(value, seen, depth2, maxDepth) {
|
|
@@ -1210,17 +1226,114 @@ var DEFAULT_REDACT_PATHS = [
|
|
|
1210
1226
|
...REDACT_COMMON_FIELDS.flatMap((field) => depth(field)),
|
|
1211
1227
|
...REDACT_ABSOLUTE_PATHS
|
|
1212
1228
|
];
|
|
1229
|
+
|
|
1230
|
+
// src/server/utils/report-destination-failure.util.ts
|
|
1231
|
+
function reportDestinationFailure(logKey, name, cause, msg) {
|
|
1232
|
+
writeStderrSafely(
|
|
1233
|
+
JSON.stringify({
|
|
1234
|
+
level: "error",
|
|
1235
|
+
logKey,
|
|
1236
|
+
destination: toSingleLineMessage(name),
|
|
1237
|
+
err: describeCause(cause),
|
|
1238
|
+
msg: toSingleLineMessage(msg)
|
|
1239
|
+
}) + "\n"
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
var UNREADABLE_CAUSE = { type: "UnknownError", message: "unreadable value" };
|
|
1243
|
+
function describeCause(cause) {
|
|
1244
|
+
try {
|
|
1245
|
+
return cause instanceof Error ? {
|
|
1246
|
+
type: toSingleLineMessage(String(cause.name)),
|
|
1247
|
+
message: toSingleLineMessage(String(cause.message))
|
|
1248
|
+
} : { type: "UnknownError", message: toSingleLineMessage(String(cause)) };
|
|
1249
|
+
} catch {
|
|
1250
|
+
return UNREADABLE_CAUSE;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function safeDestinationName(destination) {
|
|
1254
|
+
try {
|
|
1255
|
+
return String(destination.name);
|
|
1256
|
+
} catch {
|
|
1257
|
+
return "unknown";
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
function isLogLevel(value) {
|
|
1261
|
+
return LOG_LEVEL_PRIORITY.includes(value);
|
|
1262
|
+
}
|
|
1263
|
+
var resolvedMinLevels = /* @__PURE__ */ new WeakMap();
|
|
1264
|
+
function safeMinLevel(destination) {
|
|
1265
|
+
if (resolvedMinLevels.has(destination)) {
|
|
1266
|
+
return resolvedMinLevels.get(destination);
|
|
1267
|
+
}
|
|
1268
|
+
let resolved;
|
|
1269
|
+
try {
|
|
1270
|
+
const read = destination.minLevel;
|
|
1271
|
+
resolved = isLogLevel(read) ? read : void 0;
|
|
1272
|
+
} catch {
|
|
1273
|
+
}
|
|
1274
|
+
resolvedMinLevels.set(destination, resolved);
|
|
1275
|
+
return resolved;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// src/server/utils/otel-detector.ts
|
|
1279
|
+
var brokenInstallWarned = false;
|
|
1280
|
+
function isOtelApiMissing(cause) {
|
|
1281
|
+
try {
|
|
1282
|
+
const { code, message } = cause;
|
|
1283
|
+
return code === "MODULE_NOT_FOUND" && /^Cannot find module '@opentelemetry\/api'/.test(String(message));
|
|
1284
|
+
} catch {
|
|
1285
|
+
return false;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1213
1288
|
function loadTraceApiFrom(anchor) {
|
|
1289
|
+
let from;
|
|
1214
1290
|
try {
|
|
1215
|
-
|
|
1216
|
-
const mod = requireFrom("@opentelemetry/api");
|
|
1217
|
-
return mod.trace;
|
|
1291
|
+
from = anchor();
|
|
1218
1292
|
} catch {
|
|
1219
|
-
return void 0;
|
|
1293
|
+
return { api: void 0 };
|
|
1294
|
+
}
|
|
1295
|
+
try {
|
|
1296
|
+
const mod = module$1.createRequire(from)("@opentelemetry/api");
|
|
1297
|
+
const trace = typeof mod === "object" && mod !== null || typeof mod === "function" ? mod.trace : void 0;
|
|
1298
|
+
if (typeof trace?.getActiveSpan !== "function") {
|
|
1299
|
+
throw new TypeError("@opentelemetry/api resolved but exports no usable `trace` namespace");
|
|
1300
|
+
}
|
|
1301
|
+
return { api: trace };
|
|
1302
|
+
} catch (cause) {
|
|
1303
|
+
return isOtelApiMissing(cause) ? { api: void 0 } : { api: void 0, broken: { cause } };
|
|
1220
1304
|
}
|
|
1221
1305
|
}
|
|
1222
|
-
function
|
|
1223
|
-
|
|
1306
|
+
function warnBrokenInstallOnce(cause) {
|
|
1307
|
+
if (brokenInstallWarned) {
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
brokenInstallWarned = true;
|
|
1311
|
+
writeStderrSafely(
|
|
1312
|
+
JSON.stringify({
|
|
1313
|
+
level: "warn",
|
|
1314
|
+
logKey: RESERVED_LOG_KEYS.LOGGER_OTEL_API_BROKEN,
|
|
1315
|
+
msg: "@opentelemetry/api is installed but could not be loaded, so traceId and spanId are absent from every entry. Reinstall it, or set otel.shouldAutoInjectTraceContext to false.",
|
|
1316
|
+
err: describeCause(cause)
|
|
1317
|
+
}) + "\n"
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
var DEFAULT_ANCHORS = [
|
|
1321
|
+
() => __filename,
|
|
1322
|
+
() => path.join(process.cwd(), "noop.cjs")
|
|
1323
|
+
];
|
|
1324
|
+
function detectOtelTraceApi(anchors = DEFAULT_ANCHORS) {
|
|
1325
|
+
let broken;
|
|
1326
|
+
for (const anchor of anchors) {
|
|
1327
|
+
const attempt = loadTraceApiFrom(anchor);
|
|
1328
|
+
if (attempt.api !== void 0) {
|
|
1329
|
+
return attempt.api;
|
|
1330
|
+
}
|
|
1331
|
+
broken ??= attempt.broken;
|
|
1332
|
+
}
|
|
1333
|
+
if (broken !== void 0) {
|
|
1334
|
+
warnBrokenInstallOnce(broken.cause);
|
|
1335
|
+
}
|
|
1336
|
+
return void 0;
|
|
1224
1337
|
}
|
|
1225
1338
|
function isValidTraceId(traceId) {
|
|
1226
1339
|
return /^[0-9a-f]{32}$/.test(traceId) && !/^0+$/.test(traceId);
|
|
@@ -1239,17 +1352,21 @@ function createTraceContextMixin(logContext, opts) {
|
|
|
1239
1352
|
Object.assign(merged, store);
|
|
1240
1353
|
}
|
|
1241
1354
|
if (traceApi) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1355
|
+
try {
|
|
1356
|
+
const span = traceApi.getActiveSpan();
|
|
1357
|
+
if (span) {
|
|
1358
|
+
const { traceId, spanId, traceFlags } = span.spanContext();
|
|
1359
|
+
if (isValidTraceId(traceId) && isValidSpanId(spanId)) {
|
|
1360
|
+
const flags = typeof traceFlags === "number" ? (traceFlags & 255).toString(16).padStart(2, "0") : void 0;
|
|
1361
|
+
Reflect.set(merged, opts.traceIdField, traceId);
|
|
1362
|
+
Reflect.set(merged, opts.spanIdField, spanId);
|
|
1363
|
+
if (flags !== void 0) {
|
|
1364
|
+
Reflect.set(merged, opts.traceFlagsField, flags);
|
|
1365
|
+
}
|
|
1251
1366
|
}
|
|
1252
1367
|
}
|
|
1368
|
+
} catch (cause) {
|
|
1369
|
+
warnBrokenInstallOnce(cause);
|
|
1253
1370
|
}
|
|
1254
1371
|
}
|
|
1255
1372
|
return merged;
|
|
@@ -1261,52 +1378,6 @@ function compileRedactPaths(extraPaths, shouldSkipDefaults) {
|
|
|
1261
1378
|
const sources = shouldSkipDefaults ? [extraPaths] : [DEFAULT_REDACT_PATHS, extraPaths];
|
|
1262
1379
|
return Array.from(new Set(sources.flat()));
|
|
1263
1380
|
}
|
|
1264
|
-
|
|
1265
|
-
// src/server/utils/report-destination-failure.util.ts
|
|
1266
|
-
function reportDestinationFailure(logKey, name, cause, msg) {
|
|
1267
|
-
try {
|
|
1268
|
-
const err = cause instanceof Error ? {
|
|
1269
|
-
type: toSingleLineMessage(String(cause.name)),
|
|
1270
|
-
message: toSingleLineMessage(String(cause.message))
|
|
1271
|
-
} : { type: "UnknownError", message: toSingleLineMessage(String(cause)) };
|
|
1272
|
-
writeStderrSafely(
|
|
1273
|
-
JSON.stringify({
|
|
1274
|
-
level: "error",
|
|
1275
|
-
logKey,
|
|
1276
|
-
destination: toSingleLineMessage(name),
|
|
1277
|
-
err,
|
|
1278
|
-
msg: toSingleLineMessage(msg)
|
|
1279
|
-
}) + "\n"
|
|
1280
|
-
);
|
|
1281
|
-
} catch {
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
function safeDestinationName(destination) {
|
|
1285
|
-
try {
|
|
1286
|
-
return String(destination.name);
|
|
1287
|
-
} catch {
|
|
1288
|
-
return "unknown";
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
function isLogLevel(value) {
|
|
1292
|
-
return LOG_LEVEL_PRIORITY.includes(value);
|
|
1293
|
-
}
|
|
1294
|
-
var resolvedMinLevels = /* @__PURE__ */ new WeakMap();
|
|
1295
|
-
function safeMinLevel(destination) {
|
|
1296
|
-
if (resolvedMinLevels.has(destination)) {
|
|
1297
|
-
return resolvedMinLevels.get(destination);
|
|
1298
|
-
}
|
|
1299
|
-
let resolved;
|
|
1300
|
-
try {
|
|
1301
|
-
const read = destination.minLevel;
|
|
1302
|
-
resolved = isLogLevel(read) ? read : void 0;
|
|
1303
|
-
} catch {
|
|
1304
|
-
}
|
|
1305
|
-
resolvedMinLevels.set(destination, resolved);
|
|
1306
|
-
return resolved;
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
// src/server/utils/destination-to-stream.ts
|
|
1310
1381
|
function reportWriteFailure(destination, cause) {
|
|
1311
1382
|
const name = safeDestinationName(destination);
|
|
1312
1383
|
reportDestinationFailure(
|
|
@@ -1427,6 +1498,19 @@ function walk(value, sensitive, censor, depth2, ancestors, honorToJson = true) {
|
|
|
1427
1498
|
if (ancestors.has(value)) {
|
|
1428
1499
|
return REDACT_CIRCULAR;
|
|
1429
1500
|
}
|
|
1501
|
+
if (value instanceof Error) {
|
|
1502
|
+
return walkError(value, sensitive, censor, depth2, ancestors, honorToJson);
|
|
1503
|
+
}
|
|
1504
|
+
return walkObject(value, sensitive, censor, depth2, ancestors, honorToJson);
|
|
1505
|
+
}
|
|
1506
|
+
function walkError(error, sensitive, censor, depth2, ancestors, honorToJson) {
|
|
1507
|
+
try {
|
|
1508
|
+
return walkObject(error, sensitive, censor, depth2, ancestors, honorToJson);
|
|
1509
|
+
} catch {
|
|
1510
|
+
return walk(sanitizeError(error), sensitive, censor, depth2, ancestors, honorToJson);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
function walkObject(value, sensitive, censor, depth2, ancestors, honorToJson) {
|
|
1430
1514
|
const resolvedToJson = resolveToJson(value);
|
|
1431
1515
|
if (!resolvedToJson.fromAccessor && resolvedToJson.method === BUFFER_TO_JSON) {
|
|
1432
1516
|
return value;
|
|
@@ -2562,6 +2646,22 @@ var MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
|
2562
2646
|
function writeRawToStdout(payload) {
|
|
2563
2647
|
writeStdoutSafely(payload);
|
|
2564
2648
|
}
|
|
2649
|
+
var RESOLUTION_FAILURE_CODES = ["MODULE_NOT_FOUND", "ERR_MODULE_NOT_FOUND"];
|
|
2650
|
+
function isPinoPrettyMissing(cause) {
|
|
2651
|
+
try {
|
|
2652
|
+
const { code, message } = cause;
|
|
2653
|
+
return RESOLUTION_FAILURE_CODES.includes(code) && /^Cannot find (?:module|package) 'pino-pretty'/.test(String(message));
|
|
2654
|
+
} catch {
|
|
2655
|
+
return false;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
function describeFailure(cause) {
|
|
2659
|
+
try {
|
|
2660
|
+
return toSingleLineMessage(cause instanceof Error ? cause.message : String(cause));
|
|
2661
|
+
} catch {
|
|
2662
|
+
return "unreadable value";
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2565
2665
|
var PrettyDevDestination = class {
|
|
2566
2666
|
/**
|
|
2567
2667
|
* @param opts.minLevel — Optional minimum level filter applied by the
|
|
@@ -2634,10 +2734,11 @@ var PrettyDevDestination = class {
|
|
|
2634
2734
|
// and the last-resort rescue.
|
|
2635
2735
|
destination: process.stdout
|
|
2636
2736
|
});
|
|
2637
|
-
} catch {
|
|
2737
|
+
} catch (cause) {
|
|
2638
2738
|
this.initFailed = true;
|
|
2639
2739
|
throw new Error(
|
|
2640
|
-
"[PrettyDevDestination] pino-pretty is not installed. Install it as a peer dependency (`pnpm add -D pino-pretty`) or remove PrettyDevDestination from `destinations`."
|
|
2740
|
+
isPinoPrettyMissing(cause) ? "[PrettyDevDestination] pino-pretty is not installed. Install it as a peer dependency (`pnpm add -D pino-pretty`) or remove PrettyDevDestination from `destinations`." : `[PrettyDevDestination] pino-pretty failed to start: ${describeFailure(cause)}`,
|
|
2741
|
+
{ cause }
|
|
2641
2742
|
);
|
|
2642
2743
|
}
|
|
2643
2744
|
this.flushBuffer((payload) => {
|
|
@@ -3102,7 +3203,13 @@ exports.PinoUnexpectedErrorSink = class PinoUnexpectedErrorSink {
|
|
|
3102
3203
|
void 0,
|
|
3103
3204
|
metadata
|
|
3104
3205
|
);
|
|
3105
|
-
} catch {
|
|
3206
|
+
} catch (cause) {
|
|
3207
|
+
reportDestinationFailure(
|
|
3208
|
+
RESERVED_LOG_KEYS.HTTP_UNEXPECTED_ERROR_DROPPED,
|
|
3209
|
+
"PinoUnexpectedErrorSink",
|
|
3210
|
+
cause,
|
|
3211
|
+
"An unexpected-error report could not be logged and was dropped."
|
|
3212
|
+
);
|
|
3106
3213
|
}
|
|
3107
3214
|
}
|
|
3108
3215
|
};
|
package/dist/server/index.d.cts
CHANGED
|
@@ -585,12 +585,14 @@ declare const RESERVED_LOG_KEYS: Readonly<{
|
|
|
585
585
|
readonly HTTP_EXCEPTION_HANDLED: "HTTP_EXCEPTION_HANDLED";
|
|
586
586
|
readonly HTTP_EXCEPTION_UNHANDLED: "HTTP_EXCEPTION_UNHANDLED";
|
|
587
587
|
readonly HTTP_UNEXPECTED_ERROR: "HTTP_UNEXPECTED_ERROR";
|
|
588
|
+
readonly HTTP_UNEXPECTED_ERROR_DROPPED: "HTTP_UNEXPECTED_ERROR_DROPPED";
|
|
588
589
|
readonly METHOD_EXECUTION: "METHOD_EXECUTION";
|
|
589
590
|
readonly METHOD_SLOW_EXECUTION: "METHOD_SLOW_EXECUTION";
|
|
590
591
|
readonly LOGGER_DESTINATION_INIT_FAILED: "LOGGER_DESTINATION_INIT_FAILED";
|
|
591
592
|
readonly LOGGER_DESTINATION_WRITE_FAILED: "LOGGER_DESTINATION_WRITE_FAILED";
|
|
592
593
|
readonly LOGGER_ENTRY_TRUNCATED: "LOGGER_ENTRY_TRUNCATED";
|
|
593
594
|
readonly LOGGER_REDACTION_FAILED: "LOGGER_REDACTION_FAILED";
|
|
595
|
+
readonly LOGGER_OTEL_API_BROKEN: "LOGGER_OTEL_API_BROKEN";
|
|
594
596
|
}>;
|
|
595
597
|
/**
|
|
596
598
|
* Keys that are reserved but intentionally never emitted, each with the reason.
|
|
@@ -616,66 +618,6 @@ declare const RESERVED_LOG_KEYS_NOT_EMITTED: Readonly<{
|
|
|
616
618
|
*/
|
|
617
619
|
type ReservedLogKey = (typeof RESERVED_LOG_KEYS)[keyof typeof RESERVED_LOG_KEYS];
|
|
618
620
|
|
|
619
|
-
/**
|
|
620
|
-
* Name-based recursive redaction.
|
|
621
|
-
*
|
|
622
|
-
* Layer: server/utils — the engine behind the library's DEFAULT PII protection.
|
|
623
|
-
* It walks a log record once and censors every value whose KEY NAME is in the
|
|
624
|
-
* sensitive set, to a bounded depth (see {@link REDACT_MAX_TRAVERSAL_DEPTH}).
|
|
625
|
-
*
|
|
626
|
-
* Why this replaced path matching. The previous default compiled 140
|
|
627
|
-
* `fast-redact` paths, 108 of them multi-level wildcards (`*.password`,
|
|
628
|
-
* `*.*.password`, …). `fast-redact` walks every key at every listed level on
|
|
629
|
-
* every log call, so the cost grew with the PATH COUNT, not with the payload:
|
|
630
|
-
* measured at ~107 µs per entry (≈9.3 k logs/s) against ~0.9 µs with redaction
|
|
631
|
-
* off. A single traversal is O(nodes) — the same order Pino's serializer already
|
|
632
|
-
* pays. The shipped configuration measures 462 k logs/s against 9.3 k, for
|
|
633
|
-
* byte-identical output on every payload the old paths covered.
|
|
634
|
-
*
|
|
635
|
-
* It is also STRICTLY SAFER than the paths it replaces:
|
|
636
|
-
* - depth 100 instead of four, and past it the value is DROPPED rather than
|
|
637
|
-
* passed through, so the ceiling cannot become a leak the way the old one
|
|
638
|
-
* was (anything below the wildcard list's fourth level was emitted in clear);
|
|
639
|
-
* - a sensitive key is caught wherever it appears, so a headers bag logged as
|
|
640
|
-
* `{ headers: { authorization } }` is covered, not only the exact
|
|
641
|
-
* `req.headers.authorization` shape the absolute paths pinned.
|
|
642
|
-
*
|
|
643
|
-
* Three value kinds need special handling, and getting any of them wrong is a
|
|
644
|
-
* leak rather than a cosmetic issue:
|
|
645
|
-
* - `Error` — copied through its prototype and descriptors, so it stays an
|
|
646
|
-
* `Error` (Pino's `err` serializer keys off the instance, and `message` /
|
|
647
|
-
* `stack` are own but NON-enumerable). Skipping errors entirely left an
|
|
648
|
-
* error under any key WITHOUT a serializer serializing its own enumerable
|
|
649
|
-
* properties in clear.
|
|
650
|
-
* - anything carrying `toJSON` (`Date`, `Decimal`, Luxon, Prisma types) — the
|
|
651
|
-
* method decides the serialized form, so the walk inspects its OUTPUT.
|
|
652
|
-
* Skipping these let a `toJSON` SYNTHESIZE a secret straight past both hooks.
|
|
653
|
-
* - `ArrayBuffer` views (`Buffer`, typed arrays) — indexed byte containers,
|
|
654
|
-
* returned untouched.
|
|
655
|
-
*
|
|
656
|
-
* What this file cannot reach: bindings passed to `child()`. Pino pre-serializes
|
|
657
|
-
* them into the instance's `chindings` fragment before any formatter runs, so
|
|
658
|
-
* `PinoLoggerService.child()` applies this redactor itself.
|
|
659
|
-
*
|
|
660
|
-
* Contract: SNAPSHOT and NEVER-THROW. Nothing the caller passed is mutated; the
|
|
661
|
-
* result is a fresh structure built from values read exactly ONCE. That is a
|
|
662
|
-
* deliberate trade against an earlier copy-on-write design which returned a
|
|
663
|
-
* clean subtree by reference: doing so left every accessor in it to be evaluated
|
|
664
|
-
* a SECOND time by `JSON.stringify`, and a stateful getter (or `toJSON`) can
|
|
665
|
-
* answer clean to the walk and `{ password }` to the serializer. Inspection
|
|
666
|
-
* cannot close that window — only reading once and pinning the result can. It
|
|
667
|
-
* costs ~26 % of the entry throughput, measured, and the walk is still ~36x
|
|
668
|
-
* faster than the path expansion it replaced.
|
|
669
|
-
*
|
|
670
|
-
* Any internal failure — a throwing getter, a hostile proxy, a value the walk
|
|
671
|
-
* cannot READ — degrades the whole record to a marked, secret-free envelope rather
|
|
672
|
-
* than crashing the request that produced it. Writing the censor is not among those
|
|
673
|
-
* failures: every copy is built so the write cannot fail, which is why a frozen
|
|
674
|
-
* error keeps its fields instead of costing the record.
|
|
675
|
-
*
|
|
676
|
-
* @see {@link createNameRedactor}
|
|
677
|
-
*/
|
|
678
|
-
|
|
679
621
|
/**
|
|
680
622
|
* A configured redactor.
|
|
681
623
|
*
|
package/dist/server/index.d.ts
CHANGED
|
@@ -585,12 +585,14 @@ declare const RESERVED_LOG_KEYS: Readonly<{
|
|
|
585
585
|
readonly HTTP_EXCEPTION_HANDLED: "HTTP_EXCEPTION_HANDLED";
|
|
586
586
|
readonly HTTP_EXCEPTION_UNHANDLED: "HTTP_EXCEPTION_UNHANDLED";
|
|
587
587
|
readonly HTTP_UNEXPECTED_ERROR: "HTTP_UNEXPECTED_ERROR";
|
|
588
|
+
readonly HTTP_UNEXPECTED_ERROR_DROPPED: "HTTP_UNEXPECTED_ERROR_DROPPED";
|
|
588
589
|
readonly METHOD_EXECUTION: "METHOD_EXECUTION";
|
|
589
590
|
readonly METHOD_SLOW_EXECUTION: "METHOD_SLOW_EXECUTION";
|
|
590
591
|
readonly LOGGER_DESTINATION_INIT_FAILED: "LOGGER_DESTINATION_INIT_FAILED";
|
|
591
592
|
readonly LOGGER_DESTINATION_WRITE_FAILED: "LOGGER_DESTINATION_WRITE_FAILED";
|
|
592
593
|
readonly LOGGER_ENTRY_TRUNCATED: "LOGGER_ENTRY_TRUNCATED";
|
|
593
594
|
readonly LOGGER_REDACTION_FAILED: "LOGGER_REDACTION_FAILED";
|
|
595
|
+
readonly LOGGER_OTEL_API_BROKEN: "LOGGER_OTEL_API_BROKEN";
|
|
594
596
|
}>;
|
|
595
597
|
/**
|
|
596
598
|
* Keys that are reserved but intentionally never emitted, each with the reason.
|
|
@@ -616,66 +618,6 @@ declare const RESERVED_LOG_KEYS_NOT_EMITTED: Readonly<{
|
|
|
616
618
|
*/
|
|
617
619
|
type ReservedLogKey = (typeof RESERVED_LOG_KEYS)[keyof typeof RESERVED_LOG_KEYS];
|
|
618
620
|
|
|
619
|
-
/**
|
|
620
|
-
* Name-based recursive redaction.
|
|
621
|
-
*
|
|
622
|
-
* Layer: server/utils — the engine behind the library's DEFAULT PII protection.
|
|
623
|
-
* It walks a log record once and censors every value whose KEY NAME is in the
|
|
624
|
-
* sensitive set, to a bounded depth (see {@link REDACT_MAX_TRAVERSAL_DEPTH}).
|
|
625
|
-
*
|
|
626
|
-
* Why this replaced path matching. The previous default compiled 140
|
|
627
|
-
* `fast-redact` paths, 108 of them multi-level wildcards (`*.password`,
|
|
628
|
-
* `*.*.password`, …). `fast-redact` walks every key at every listed level on
|
|
629
|
-
* every log call, so the cost grew with the PATH COUNT, not with the payload:
|
|
630
|
-
* measured at ~107 µs per entry (≈9.3 k logs/s) against ~0.9 µs with redaction
|
|
631
|
-
* off. A single traversal is O(nodes) — the same order Pino's serializer already
|
|
632
|
-
* pays. The shipped configuration measures 462 k logs/s against 9.3 k, for
|
|
633
|
-
* byte-identical output on every payload the old paths covered.
|
|
634
|
-
*
|
|
635
|
-
* It is also STRICTLY SAFER than the paths it replaces:
|
|
636
|
-
* - depth 100 instead of four, and past it the value is DROPPED rather than
|
|
637
|
-
* passed through, so the ceiling cannot become a leak the way the old one
|
|
638
|
-
* was (anything below the wildcard list's fourth level was emitted in clear);
|
|
639
|
-
* - a sensitive key is caught wherever it appears, so a headers bag logged as
|
|
640
|
-
* `{ headers: { authorization } }` is covered, not only the exact
|
|
641
|
-
* `req.headers.authorization` shape the absolute paths pinned.
|
|
642
|
-
*
|
|
643
|
-
* Three value kinds need special handling, and getting any of them wrong is a
|
|
644
|
-
* leak rather than a cosmetic issue:
|
|
645
|
-
* - `Error` — copied through its prototype and descriptors, so it stays an
|
|
646
|
-
* `Error` (Pino's `err` serializer keys off the instance, and `message` /
|
|
647
|
-
* `stack` are own but NON-enumerable). Skipping errors entirely left an
|
|
648
|
-
* error under any key WITHOUT a serializer serializing its own enumerable
|
|
649
|
-
* properties in clear.
|
|
650
|
-
* - anything carrying `toJSON` (`Date`, `Decimal`, Luxon, Prisma types) — the
|
|
651
|
-
* method decides the serialized form, so the walk inspects its OUTPUT.
|
|
652
|
-
* Skipping these let a `toJSON` SYNTHESIZE a secret straight past both hooks.
|
|
653
|
-
* - `ArrayBuffer` views (`Buffer`, typed arrays) — indexed byte containers,
|
|
654
|
-
* returned untouched.
|
|
655
|
-
*
|
|
656
|
-
* What this file cannot reach: bindings passed to `child()`. Pino pre-serializes
|
|
657
|
-
* them into the instance's `chindings` fragment before any formatter runs, so
|
|
658
|
-
* `PinoLoggerService.child()` applies this redactor itself.
|
|
659
|
-
*
|
|
660
|
-
* Contract: SNAPSHOT and NEVER-THROW. Nothing the caller passed is mutated; the
|
|
661
|
-
* result is a fresh structure built from values read exactly ONCE. That is a
|
|
662
|
-
* deliberate trade against an earlier copy-on-write design which returned a
|
|
663
|
-
* clean subtree by reference: doing so left every accessor in it to be evaluated
|
|
664
|
-
* a SECOND time by `JSON.stringify`, and a stateful getter (or `toJSON`) can
|
|
665
|
-
* answer clean to the walk and `{ password }` to the serializer. Inspection
|
|
666
|
-
* cannot close that window — only reading once and pinning the result can. It
|
|
667
|
-
* costs ~26 % of the entry throughput, measured, and the walk is still ~36x
|
|
668
|
-
* faster than the path expansion it replaced.
|
|
669
|
-
*
|
|
670
|
-
* Any internal failure — a throwing getter, a hostile proxy, a value the walk
|
|
671
|
-
* cannot READ — degrades the whole record to a marked, secret-free envelope rather
|
|
672
|
-
* than crashing the request that produced it. Writing the censor is not among those
|
|
673
|
-
* failures: every copy is built so the write cannot fail, which is why a frozen
|
|
674
|
-
* error keeps its fields instead of costing the record.
|
|
675
|
-
*
|
|
676
|
-
* @see {@link createNameRedactor}
|
|
677
|
-
*/
|
|
678
|
-
|
|
679
621
|
/**
|
|
680
622
|
* A configured redactor.
|
|
681
623
|
*
|
package/dist/server/index.mjs
CHANGED
|
@@ -322,12 +322,14 @@ var RESERVED_LOG_KEYS = Object.freeze({
|
|
|
322
322
|
HTTP_EXCEPTION_HANDLED: "HTTP_EXCEPTION_HANDLED",
|
|
323
323
|
HTTP_EXCEPTION_UNHANDLED: "HTTP_EXCEPTION_UNHANDLED",
|
|
324
324
|
HTTP_UNEXPECTED_ERROR: "HTTP_UNEXPECTED_ERROR",
|
|
325
|
+
HTTP_UNEXPECTED_ERROR_DROPPED: "HTTP_UNEXPECTED_ERROR_DROPPED",
|
|
325
326
|
METHOD_EXECUTION: "METHOD_EXECUTION",
|
|
326
327
|
METHOD_SLOW_EXECUTION: "METHOD_SLOW_EXECUTION",
|
|
327
328
|
LOGGER_DESTINATION_INIT_FAILED: "LOGGER_DESTINATION_INIT_FAILED",
|
|
328
329
|
LOGGER_DESTINATION_WRITE_FAILED: "LOGGER_DESTINATION_WRITE_FAILED",
|
|
329
330
|
LOGGER_ENTRY_TRUNCATED: "LOGGER_ENTRY_TRUNCATED",
|
|
330
|
-
LOGGER_REDACTION_FAILED: "LOGGER_REDACTION_FAILED"
|
|
331
|
+
LOGGER_REDACTION_FAILED: "LOGGER_REDACTION_FAILED",
|
|
332
|
+
LOGGER_OTEL_API_BROKEN: "LOGGER_OTEL_API_BROKEN"
|
|
331
333
|
});
|
|
332
334
|
var RESERVED_LOG_KEYS_NOT_EMITTED = Object.freeze({
|
|
333
335
|
/**
|
|
@@ -394,12 +396,14 @@ function toSanitizedError(value, seen, depth2, maxDepth) {
|
|
|
394
396
|
}
|
|
395
397
|
seen.add(value);
|
|
396
398
|
const result = { name: value.name, message: value.message };
|
|
397
|
-
|
|
398
|
-
|
|
399
|
+
const stack = readOptional(() => value.stack, result);
|
|
400
|
+
if (stack !== void 0) {
|
|
401
|
+
result.stack = scrubStack(stack);
|
|
399
402
|
}
|
|
400
403
|
const isAtDepthLimit = depth2 >= maxDepth;
|
|
401
|
-
|
|
402
|
-
|
|
404
|
+
const cause = readOptional(() => value.cause, result);
|
|
405
|
+
if (cause !== void 0) {
|
|
406
|
+
result.cause = isAtDepthLimit ? createDepthMarker() : toSanitizedChild(cause, seen, depth2 + 1, maxDepth);
|
|
403
407
|
}
|
|
404
408
|
const aggregated = readAggregatedErrors(value);
|
|
405
409
|
if (aggregated !== void 0) {
|
|
@@ -426,6 +430,18 @@ function assignOwnErrorFields(target, source) {
|
|
|
426
430
|
}
|
|
427
431
|
}
|
|
428
432
|
} catch {
|
|
433
|
+
markIncomplete(target);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
function markIncomplete(target) {
|
|
437
|
+
Reflect.defineProperty(target, "_errorFieldsIncomplete", { value: true, enumerable: true });
|
|
438
|
+
}
|
|
439
|
+
function readOptional(read, node) {
|
|
440
|
+
try {
|
|
441
|
+
return read();
|
|
442
|
+
} catch {
|
|
443
|
+
markIncomplete(node);
|
|
444
|
+
return void 0;
|
|
429
445
|
}
|
|
430
446
|
}
|
|
431
447
|
function toSanitizedChild(value, seen, depth2, maxDepth) {
|
|
@@ -1207,17 +1223,114 @@ var DEFAULT_REDACT_PATHS = [
|
|
|
1207
1223
|
...REDACT_COMMON_FIELDS.flatMap((field) => depth(field)),
|
|
1208
1224
|
...REDACT_ABSOLUTE_PATHS
|
|
1209
1225
|
];
|
|
1226
|
+
|
|
1227
|
+
// src/server/utils/report-destination-failure.util.ts
|
|
1228
|
+
function reportDestinationFailure(logKey, name, cause, msg) {
|
|
1229
|
+
writeStderrSafely(
|
|
1230
|
+
JSON.stringify({
|
|
1231
|
+
level: "error",
|
|
1232
|
+
logKey,
|
|
1233
|
+
destination: toSingleLineMessage(name),
|
|
1234
|
+
err: describeCause(cause),
|
|
1235
|
+
msg: toSingleLineMessage(msg)
|
|
1236
|
+
}) + "\n"
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
var UNREADABLE_CAUSE = { type: "UnknownError", message: "unreadable value" };
|
|
1240
|
+
function describeCause(cause) {
|
|
1241
|
+
try {
|
|
1242
|
+
return cause instanceof Error ? {
|
|
1243
|
+
type: toSingleLineMessage(String(cause.name)),
|
|
1244
|
+
message: toSingleLineMessage(String(cause.message))
|
|
1245
|
+
} : { type: "UnknownError", message: toSingleLineMessage(String(cause)) };
|
|
1246
|
+
} catch {
|
|
1247
|
+
return UNREADABLE_CAUSE;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
function safeDestinationName(destination) {
|
|
1251
|
+
try {
|
|
1252
|
+
return String(destination.name);
|
|
1253
|
+
} catch {
|
|
1254
|
+
return "unknown";
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
function isLogLevel(value) {
|
|
1258
|
+
return LOG_LEVEL_PRIORITY.includes(value);
|
|
1259
|
+
}
|
|
1260
|
+
var resolvedMinLevels = /* @__PURE__ */ new WeakMap();
|
|
1261
|
+
function safeMinLevel(destination) {
|
|
1262
|
+
if (resolvedMinLevels.has(destination)) {
|
|
1263
|
+
return resolvedMinLevels.get(destination);
|
|
1264
|
+
}
|
|
1265
|
+
let resolved;
|
|
1266
|
+
try {
|
|
1267
|
+
const read = destination.minLevel;
|
|
1268
|
+
resolved = isLogLevel(read) ? read : void 0;
|
|
1269
|
+
} catch {
|
|
1270
|
+
}
|
|
1271
|
+
resolvedMinLevels.set(destination, resolved);
|
|
1272
|
+
return resolved;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// src/server/utils/otel-detector.ts
|
|
1276
|
+
var brokenInstallWarned = false;
|
|
1277
|
+
function isOtelApiMissing(cause) {
|
|
1278
|
+
try {
|
|
1279
|
+
const { code, message } = cause;
|
|
1280
|
+
return code === "MODULE_NOT_FOUND" && /^Cannot find module '@opentelemetry\/api'/.test(String(message));
|
|
1281
|
+
} catch {
|
|
1282
|
+
return false;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1210
1285
|
function loadTraceApiFrom(anchor) {
|
|
1286
|
+
let from;
|
|
1211
1287
|
try {
|
|
1212
|
-
|
|
1213
|
-
const mod = requireFrom("@opentelemetry/api");
|
|
1214
|
-
return mod.trace;
|
|
1288
|
+
from = anchor();
|
|
1215
1289
|
} catch {
|
|
1216
|
-
return void 0;
|
|
1290
|
+
return { api: void 0 };
|
|
1291
|
+
}
|
|
1292
|
+
try {
|
|
1293
|
+
const mod = createRequire(from)("@opentelemetry/api");
|
|
1294
|
+
const trace = typeof mod === "object" && mod !== null || typeof mod === "function" ? mod.trace : void 0;
|
|
1295
|
+
if (typeof trace?.getActiveSpan !== "function") {
|
|
1296
|
+
throw new TypeError("@opentelemetry/api resolved but exports no usable `trace` namespace");
|
|
1297
|
+
}
|
|
1298
|
+
return { api: trace };
|
|
1299
|
+
} catch (cause) {
|
|
1300
|
+
return isOtelApiMissing(cause) ? { api: void 0 } : { api: void 0, broken: { cause } };
|
|
1217
1301
|
}
|
|
1218
1302
|
}
|
|
1219
|
-
function
|
|
1220
|
-
|
|
1303
|
+
function warnBrokenInstallOnce(cause) {
|
|
1304
|
+
if (brokenInstallWarned) {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
brokenInstallWarned = true;
|
|
1308
|
+
writeStderrSafely(
|
|
1309
|
+
JSON.stringify({
|
|
1310
|
+
level: "warn",
|
|
1311
|
+
logKey: RESERVED_LOG_KEYS.LOGGER_OTEL_API_BROKEN,
|
|
1312
|
+
msg: "@opentelemetry/api is installed but could not be loaded, so traceId and spanId are absent from every entry. Reinstall it, or set otel.shouldAutoInjectTraceContext to false.",
|
|
1313
|
+
err: describeCause(cause)
|
|
1314
|
+
}) + "\n"
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
var DEFAULT_ANCHORS = [
|
|
1318
|
+
() => __filename$1,
|
|
1319
|
+
() => join(process.cwd(), "noop.cjs")
|
|
1320
|
+
];
|
|
1321
|
+
function detectOtelTraceApi(anchors = DEFAULT_ANCHORS) {
|
|
1322
|
+
let broken;
|
|
1323
|
+
for (const anchor of anchors) {
|
|
1324
|
+
const attempt = loadTraceApiFrom(anchor);
|
|
1325
|
+
if (attempt.api !== void 0) {
|
|
1326
|
+
return attempt.api;
|
|
1327
|
+
}
|
|
1328
|
+
broken ??= attempt.broken;
|
|
1329
|
+
}
|
|
1330
|
+
if (broken !== void 0) {
|
|
1331
|
+
warnBrokenInstallOnce(broken.cause);
|
|
1332
|
+
}
|
|
1333
|
+
return void 0;
|
|
1221
1334
|
}
|
|
1222
1335
|
function isValidTraceId(traceId) {
|
|
1223
1336
|
return /^[0-9a-f]{32}$/.test(traceId) && !/^0+$/.test(traceId);
|
|
@@ -1236,17 +1349,21 @@ function createTraceContextMixin(logContext, opts) {
|
|
|
1236
1349
|
Object.assign(merged, store);
|
|
1237
1350
|
}
|
|
1238
1351
|
if (traceApi) {
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1352
|
+
try {
|
|
1353
|
+
const span = traceApi.getActiveSpan();
|
|
1354
|
+
if (span) {
|
|
1355
|
+
const { traceId, spanId, traceFlags } = span.spanContext();
|
|
1356
|
+
if (isValidTraceId(traceId) && isValidSpanId(spanId)) {
|
|
1357
|
+
const flags = typeof traceFlags === "number" ? (traceFlags & 255).toString(16).padStart(2, "0") : void 0;
|
|
1358
|
+
Reflect.set(merged, opts.traceIdField, traceId);
|
|
1359
|
+
Reflect.set(merged, opts.spanIdField, spanId);
|
|
1360
|
+
if (flags !== void 0) {
|
|
1361
|
+
Reflect.set(merged, opts.traceFlagsField, flags);
|
|
1362
|
+
}
|
|
1248
1363
|
}
|
|
1249
1364
|
}
|
|
1365
|
+
} catch (cause) {
|
|
1366
|
+
warnBrokenInstallOnce(cause);
|
|
1250
1367
|
}
|
|
1251
1368
|
}
|
|
1252
1369
|
return merged;
|
|
@@ -1258,52 +1375,6 @@ function compileRedactPaths(extraPaths, shouldSkipDefaults) {
|
|
|
1258
1375
|
const sources = shouldSkipDefaults ? [extraPaths] : [DEFAULT_REDACT_PATHS, extraPaths];
|
|
1259
1376
|
return Array.from(new Set(sources.flat()));
|
|
1260
1377
|
}
|
|
1261
|
-
|
|
1262
|
-
// src/server/utils/report-destination-failure.util.ts
|
|
1263
|
-
function reportDestinationFailure(logKey, name, cause, msg) {
|
|
1264
|
-
try {
|
|
1265
|
-
const err = cause instanceof Error ? {
|
|
1266
|
-
type: toSingleLineMessage(String(cause.name)),
|
|
1267
|
-
message: toSingleLineMessage(String(cause.message))
|
|
1268
|
-
} : { type: "UnknownError", message: toSingleLineMessage(String(cause)) };
|
|
1269
|
-
writeStderrSafely(
|
|
1270
|
-
JSON.stringify({
|
|
1271
|
-
level: "error",
|
|
1272
|
-
logKey,
|
|
1273
|
-
destination: toSingleLineMessage(name),
|
|
1274
|
-
err,
|
|
1275
|
-
msg: toSingleLineMessage(msg)
|
|
1276
|
-
}) + "\n"
|
|
1277
|
-
);
|
|
1278
|
-
} catch {
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
function safeDestinationName(destination) {
|
|
1282
|
-
try {
|
|
1283
|
-
return String(destination.name);
|
|
1284
|
-
} catch {
|
|
1285
|
-
return "unknown";
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
function isLogLevel(value) {
|
|
1289
|
-
return LOG_LEVEL_PRIORITY.includes(value);
|
|
1290
|
-
}
|
|
1291
|
-
var resolvedMinLevels = /* @__PURE__ */ new WeakMap();
|
|
1292
|
-
function safeMinLevel(destination) {
|
|
1293
|
-
if (resolvedMinLevels.has(destination)) {
|
|
1294
|
-
return resolvedMinLevels.get(destination);
|
|
1295
|
-
}
|
|
1296
|
-
let resolved;
|
|
1297
|
-
try {
|
|
1298
|
-
const read = destination.minLevel;
|
|
1299
|
-
resolved = isLogLevel(read) ? read : void 0;
|
|
1300
|
-
} catch {
|
|
1301
|
-
}
|
|
1302
|
-
resolvedMinLevels.set(destination, resolved);
|
|
1303
|
-
return resolved;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
// src/server/utils/destination-to-stream.ts
|
|
1307
1378
|
function reportWriteFailure(destination, cause) {
|
|
1308
1379
|
const name = safeDestinationName(destination);
|
|
1309
1380
|
reportDestinationFailure(
|
|
@@ -1424,6 +1495,19 @@ function walk(value, sensitive, censor, depth2, ancestors, honorToJson = true) {
|
|
|
1424
1495
|
if (ancestors.has(value)) {
|
|
1425
1496
|
return REDACT_CIRCULAR;
|
|
1426
1497
|
}
|
|
1498
|
+
if (value instanceof Error) {
|
|
1499
|
+
return walkError(value, sensitive, censor, depth2, ancestors, honorToJson);
|
|
1500
|
+
}
|
|
1501
|
+
return walkObject(value, sensitive, censor, depth2, ancestors, honorToJson);
|
|
1502
|
+
}
|
|
1503
|
+
function walkError(error, sensitive, censor, depth2, ancestors, honorToJson) {
|
|
1504
|
+
try {
|
|
1505
|
+
return walkObject(error, sensitive, censor, depth2, ancestors, honorToJson);
|
|
1506
|
+
} catch {
|
|
1507
|
+
return walk(sanitizeError(error), sensitive, censor, depth2, ancestors, honorToJson);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
function walkObject(value, sensitive, censor, depth2, ancestors, honorToJson) {
|
|
1427
1511
|
const resolvedToJson = resolveToJson(value);
|
|
1428
1512
|
if (!resolvedToJson.fromAccessor && resolvedToJson.method === BUFFER_TO_JSON) {
|
|
1429
1513
|
return value;
|
|
@@ -2559,6 +2643,22 @@ var MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
|
2559
2643
|
function writeRawToStdout(payload) {
|
|
2560
2644
|
writeStdoutSafely(payload);
|
|
2561
2645
|
}
|
|
2646
|
+
var RESOLUTION_FAILURE_CODES = ["MODULE_NOT_FOUND", "ERR_MODULE_NOT_FOUND"];
|
|
2647
|
+
function isPinoPrettyMissing(cause) {
|
|
2648
|
+
try {
|
|
2649
|
+
const { code, message } = cause;
|
|
2650
|
+
return RESOLUTION_FAILURE_CODES.includes(code) && /^Cannot find (?:module|package) 'pino-pretty'/.test(String(message));
|
|
2651
|
+
} catch {
|
|
2652
|
+
return false;
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
function describeFailure(cause) {
|
|
2656
|
+
try {
|
|
2657
|
+
return toSingleLineMessage(cause instanceof Error ? cause.message : String(cause));
|
|
2658
|
+
} catch {
|
|
2659
|
+
return "unreadable value";
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2562
2662
|
var PrettyDevDestination = class {
|
|
2563
2663
|
/**
|
|
2564
2664
|
* @param opts.minLevel — Optional minimum level filter applied by the
|
|
@@ -2631,10 +2731,11 @@ var PrettyDevDestination = class {
|
|
|
2631
2731
|
// and the last-resort rescue.
|
|
2632
2732
|
destination: process.stdout
|
|
2633
2733
|
});
|
|
2634
|
-
} catch {
|
|
2734
|
+
} catch (cause) {
|
|
2635
2735
|
this.initFailed = true;
|
|
2636
2736
|
throw new Error(
|
|
2637
|
-
"[PrettyDevDestination] pino-pretty is not installed. Install it as a peer dependency (`pnpm add -D pino-pretty`) or remove PrettyDevDestination from `destinations`."
|
|
2737
|
+
isPinoPrettyMissing(cause) ? "[PrettyDevDestination] pino-pretty is not installed. Install it as a peer dependency (`pnpm add -D pino-pretty`) or remove PrettyDevDestination from `destinations`." : `[PrettyDevDestination] pino-pretty failed to start: ${describeFailure(cause)}`,
|
|
2738
|
+
{ cause }
|
|
2638
2739
|
);
|
|
2639
2740
|
}
|
|
2640
2741
|
this.flushBuffer((payload) => {
|
|
@@ -3099,7 +3200,13 @@ var PinoUnexpectedErrorSink = class {
|
|
|
3099
3200
|
void 0,
|
|
3100
3201
|
metadata
|
|
3101
3202
|
);
|
|
3102
|
-
} catch {
|
|
3203
|
+
} catch (cause) {
|
|
3204
|
+
reportDestinationFailure(
|
|
3205
|
+
RESERVED_LOG_KEYS.HTTP_UNEXPECTED_ERROR_DROPPED,
|
|
3206
|
+
"PinoUnexpectedErrorSink",
|
|
3207
|
+
cause,
|
|
3208
|
+
"An unexpected-error report could not be logged and was dropped."
|
|
3209
|
+
);
|
|
3103
3210
|
}
|
|
3104
3211
|
}
|
|
3105
3212
|
};
|
package/dist/shared/index.cjs
CHANGED
|
@@ -18,12 +18,14 @@ var RESERVED_LOG_KEYS = Object.freeze({
|
|
|
18
18
|
HTTP_EXCEPTION_HANDLED: "HTTP_EXCEPTION_HANDLED",
|
|
19
19
|
HTTP_EXCEPTION_UNHANDLED: "HTTP_EXCEPTION_UNHANDLED",
|
|
20
20
|
HTTP_UNEXPECTED_ERROR: "HTTP_UNEXPECTED_ERROR",
|
|
21
|
+
HTTP_UNEXPECTED_ERROR_DROPPED: "HTTP_UNEXPECTED_ERROR_DROPPED",
|
|
21
22
|
METHOD_EXECUTION: "METHOD_EXECUTION",
|
|
22
23
|
METHOD_SLOW_EXECUTION: "METHOD_SLOW_EXECUTION",
|
|
23
24
|
LOGGER_DESTINATION_INIT_FAILED: "LOGGER_DESTINATION_INIT_FAILED",
|
|
24
25
|
LOGGER_DESTINATION_WRITE_FAILED: "LOGGER_DESTINATION_WRITE_FAILED",
|
|
25
26
|
LOGGER_ENTRY_TRUNCATED: "LOGGER_ENTRY_TRUNCATED",
|
|
26
|
-
LOGGER_REDACTION_FAILED: "LOGGER_REDACTION_FAILED"
|
|
27
|
+
LOGGER_REDACTION_FAILED: "LOGGER_REDACTION_FAILED",
|
|
28
|
+
LOGGER_OTEL_API_BROKEN: "LOGGER_OTEL_API_BROKEN"
|
|
27
29
|
});
|
|
28
30
|
var RESERVED_LOG_KEYS_NOT_EMITTED = Object.freeze({
|
|
29
31
|
/**
|
package/dist/shared/index.d.cts
CHANGED
|
@@ -239,12 +239,14 @@ declare const RESERVED_LOG_KEYS: Readonly<{
|
|
|
239
239
|
readonly HTTP_EXCEPTION_HANDLED: "HTTP_EXCEPTION_HANDLED";
|
|
240
240
|
readonly HTTP_EXCEPTION_UNHANDLED: "HTTP_EXCEPTION_UNHANDLED";
|
|
241
241
|
readonly HTTP_UNEXPECTED_ERROR: "HTTP_UNEXPECTED_ERROR";
|
|
242
|
+
readonly HTTP_UNEXPECTED_ERROR_DROPPED: "HTTP_UNEXPECTED_ERROR_DROPPED";
|
|
242
243
|
readonly METHOD_EXECUTION: "METHOD_EXECUTION";
|
|
243
244
|
readonly METHOD_SLOW_EXECUTION: "METHOD_SLOW_EXECUTION";
|
|
244
245
|
readonly LOGGER_DESTINATION_INIT_FAILED: "LOGGER_DESTINATION_INIT_FAILED";
|
|
245
246
|
readonly LOGGER_DESTINATION_WRITE_FAILED: "LOGGER_DESTINATION_WRITE_FAILED";
|
|
246
247
|
readonly LOGGER_ENTRY_TRUNCATED: "LOGGER_ENTRY_TRUNCATED";
|
|
247
248
|
readonly LOGGER_REDACTION_FAILED: "LOGGER_REDACTION_FAILED";
|
|
249
|
+
readonly LOGGER_OTEL_API_BROKEN: "LOGGER_OTEL_API_BROKEN";
|
|
248
250
|
}>;
|
|
249
251
|
/**
|
|
250
252
|
* Keys that are reserved but intentionally never emitted, each with the reason.
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -239,12 +239,14 @@ declare const RESERVED_LOG_KEYS: Readonly<{
|
|
|
239
239
|
readonly HTTP_EXCEPTION_HANDLED: "HTTP_EXCEPTION_HANDLED";
|
|
240
240
|
readonly HTTP_EXCEPTION_UNHANDLED: "HTTP_EXCEPTION_UNHANDLED";
|
|
241
241
|
readonly HTTP_UNEXPECTED_ERROR: "HTTP_UNEXPECTED_ERROR";
|
|
242
|
+
readonly HTTP_UNEXPECTED_ERROR_DROPPED: "HTTP_UNEXPECTED_ERROR_DROPPED";
|
|
242
243
|
readonly METHOD_EXECUTION: "METHOD_EXECUTION";
|
|
243
244
|
readonly METHOD_SLOW_EXECUTION: "METHOD_SLOW_EXECUTION";
|
|
244
245
|
readonly LOGGER_DESTINATION_INIT_FAILED: "LOGGER_DESTINATION_INIT_FAILED";
|
|
245
246
|
readonly LOGGER_DESTINATION_WRITE_FAILED: "LOGGER_DESTINATION_WRITE_FAILED";
|
|
246
247
|
readonly LOGGER_ENTRY_TRUNCATED: "LOGGER_ENTRY_TRUNCATED";
|
|
247
248
|
readonly LOGGER_REDACTION_FAILED: "LOGGER_REDACTION_FAILED";
|
|
249
|
+
readonly LOGGER_OTEL_API_BROKEN: "LOGGER_OTEL_API_BROKEN";
|
|
248
250
|
}>;
|
|
249
251
|
/**
|
|
250
252
|
* Keys that are reserved but intentionally never emitted, each with the reason.
|
package/dist/shared/index.mjs
CHANGED
|
@@ -16,12 +16,14 @@ var RESERVED_LOG_KEYS = Object.freeze({
|
|
|
16
16
|
HTTP_EXCEPTION_HANDLED: "HTTP_EXCEPTION_HANDLED",
|
|
17
17
|
HTTP_EXCEPTION_UNHANDLED: "HTTP_EXCEPTION_UNHANDLED",
|
|
18
18
|
HTTP_UNEXPECTED_ERROR: "HTTP_UNEXPECTED_ERROR",
|
|
19
|
+
HTTP_UNEXPECTED_ERROR_DROPPED: "HTTP_UNEXPECTED_ERROR_DROPPED",
|
|
19
20
|
METHOD_EXECUTION: "METHOD_EXECUTION",
|
|
20
21
|
METHOD_SLOW_EXECUTION: "METHOD_SLOW_EXECUTION",
|
|
21
22
|
LOGGER_DESTINATION_INIT_FAILED: "LOGGER_DESTINATION_INIT_FAILED",
|
|
22
23
|
LOGGER_DESTINATION_WRITE_FAILED: "LOGGER_DESTINATION_WRITE_FAILED",
|
|
23
24
|
LOGGER_ENTRY_TRUNCATED: "LOGGER_ENTRY_TRUNCATED",
|
|
24
|
-
LOGGER_REDACTION_FAILED: "LOGGER_REDACTION_FAILED"
|
|
25
|
+
LOGGER_REDACTION_FAILED: "LOGGER_REDACTION_FAILED",
|
|
26
|
+
LOGGER_OTEL_API_BROKEN: "LOGGER_OTEL_API_BROKEN"
|
|
25
27
|
});
|
|
26
28
|
var RESERVED_LOG_KEYS_NOT_EMITTED = Object.freeze({
|
|
27
29
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bymax-one/nest-logger",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.7",
|
|
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",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"bench": "node --expose-gc --import tsx bench/throughput.bench.ts",
|
|
58
58
|
"build": "pnpm clean && tsup",
|
|
59
59
|
"check:exports": "attw --pack .",
|
|
60
|
+
"check:api": "node scripts/check-api-surface.mjs",
|
|
60
61
|
"check:docs": "node scripts/check-doc-snippets.mjs",
|
|
61
62
|
"check:mutants": "node scripts/check-mutation-directives.mjs",
|
|
62
63
|
"check:published": "node scripts/check-published-surface.mjs",
|