@bymax-one/nest-logger 1.4.4 → 1.4.6
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 +72 -1
- package/README.md +18 -13
- package/dist/server/index.cjs +146 -65
- package/dist/server/index.d.cts +2 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.mjs +146 -65
- 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,75 @@ heading here.
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [1.4.6] - 2026-11-01
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
Five places swallowed a failed side effect with no trace. Each now leaves one.
|
|
19
|
+
|
|
20
|
+
- **A destination failure whose cause cannot be read is no longer dropped.** When a sink's `onInit`,
|
|
21
|
+
`onRegistryReady` or `write` failed with a value that has no text form (a null-prototype object, a
|
|
22
|
+
throwing `toString`, an `Error` with throwing `name` or `message` getters), the `stderr` line was not
|
|
23
|
+
written at all and the sink stopped receiving entries unannounced. The line is now written with
|
|
24
|
+
`err: { type: 'UnknownError', message: 'unreadable value' }`.
|
|
25
|
+
- **`PinoUnexpectedErrorSink` announces a report it had to drop.** When logging the report threw, or
|
|
26
|
+
the report could not be read, the `HTTP_UNEXPECTED_ERROR` entry, the only record of an unhandled
|
|
27
|
+
exception, vanished silently. One `HTTP_UNEXPECTED_ERROR_DROPPED` line now goes to `stderr`, outside
|
|
28
|
+
the sink set that may be what failed. The response is unaffected.
|
|
29
|
+
- **`PrettyDevDestination` reports the real failure.** Every failure of `import('pino-pretty')` or of
|
|
30
|
+
`build()` was rethrown as "pino-pretty is not installed" with the original error discarded. Only a
|
|
31
|
+
failure to resolve `pino-pretty` itself keeps that message; anything else throws
|
|
32
|
+
`[PrettyDevDestination] pino-pretty failed to start: <message>`, and the original error is the
|
|
33
|
+
`cause` in both cases.
|
|
34
|
+
- **An error node says when its own fields are missing.** When an error's own properties could not be
|
|
35
|
+
read (a Proxy whose `ownKeys` throws, a getter that throws), none was copied and nothing said so. The
|
|
36
|
+
node now carries `_errorFieldsIncomplete: true`.
|
|
37
|
+
- **A broken `@opentelemetry/api` install is reported once.** Every failure to load it was folded into
|
|
38
|
+
"not installed", so correlation could be off with no sign. A missing module stays silent, as
|
|
39
|
+
designed; one that is installed but throws while loading, or exports no usable `trace` namespace, writes one
|
|
40
|
+
`LOGGER_OTEL_API_BROKEN` line to `stderr`.
|
|
41
|
+
|
|
42
|
+
#### Apply to a derived backend
|
|
43
|
+
|
|
44
|
+
Nothing to change in code. What can differ at run time:
|
|
45
|
+
|
|
46
|
+
- `stderr` can carry two lines it never carried: `HTTP_UNEXPECTED_ERROR_DROPPED` and
|
|
47
|
+
`LOGGER_OTEL_API_BROKEN`, both NDJSON like the destination-failure lines. Add them to whatever
|
|
48
|
+
alerts on the library's `stderr` codes. `RESERVED_LOG_KEYS` gains these two keys, so an application
|
|
49
|
+
event must not reuse them.
|
|
50
|
+
- A host that already logs an event under `HTTP_UNEXPECTED_ERROR_DROPPED` or `LOGGER_OTEL_API_BROKEN`
|
|
51
|
+
sees no change in that entry: nothing is renamed, rejected or dropped, and its `event.name` is
|
|
52
|
+
derived from the key as for any other (`http.unexpected.error.dropped`, `logger.otel.api.broken`).
|
|
53
|
+
What changes is that, where stdout and stderr reach the same place, an alert or filter on either
|
|
54
|
+
key now matches the library's `stderr` line and the host's own entries alike. Rename the host event.
|
|
55
|
+
- `LOGGER_DESTINATION_INIT_FAILED`, `LOGGER_DESTINATION_WRITE_FAILED` and the hook-failure line now
|
|
56
|
+
appear for failures that were silent before. A deployment that saw none was either healthy or
|
|
57
|
+
losing entries without knowing.
|
|
58
|
+
- Code that matches the text "pino-pretty is not installed" on a `PrettyDevDestination` init error now
|
|
59
|
+
matches only a genuine resolution failure; match `failed to start` for the rest, or read `cause`.
|
|
60
|
+
- Log processing that reads error nodes can meet `_errorFieldsIncomplete: true`, on an error whose own
|
|
61
|
+
fields could not be read.
|
|
62
|
+
|
|
63
|
+
### Tests
|
|
64
|
+
|
|
65
|
+
- A failure whose cause cannot be read, a dropped unexpected-error report, a `pino-pretty` failure of
|
|
66
|
+
each kind, an error whose own fields cannot be enumerated and a broken `@opentelemetry/api` each have
|
|
67
|
+
a failing-first spec. The fixtures that simulated a missing `pino-pretty` or `@opentelemetry/api` now
|
|
68
|
+
carry the resolution code Node attaches to the real error, because the code is what tells an absent
|
|
69
|
+
module from a broken one.
|
|
70
|
+
|
|
71
|
+
## [1.4.5] - 2026-11-01
|
|
72
|
+
|
|
73
|
+
### Documentation
|
|
74
|
+
|
|
75
|
+
- The README's testing section and command list now state the mutation gate as 100, to match
|
|
76
|
+
`stryker.config.json`. The live contributor docs that named 95, or called 97.42 % the
|
|
77
|
+
theoretical maximum, say 100 and describe the in-source suppressions.
|
|
78
|
+
|
|
79
|
+
### Tooling
|
|
80
|
+
|
|
81
|
+
- Stryker's `thresholds.break` is 100, so `pnpm mutation` and the cold run fail below a score of 100. `high` and `low` are 100 too.
|
|
82
|
+
|
|
14
83
|
## [1.4.4] - 2026-11-01
|
|
15
84
|
|
|
16
85
|
### Documentation
|
|
@@ -2207,7 +2276,9 @@ published `dist/` is identical — no runtime behaviour changes for consumers.
|
|
|
2207
2276
|
- Professional CI suite: `ci.yml`, `bench.yml`, `codeql.yml`, `scorecard.yml`,
|
|
2208
2277
|
`release.yml`, Dependabot, and issue templates
|
|
2209
2278
|
|
|
2210
|
-
[Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.4.
|
|
2279
|
+
[Unreleased]: https://github.com/bymaxone/nest-logger/compare/v1.4.6...HEAD
|
|
2280
|
+
[1.4.6]: https://github.com/bymaxone/nest-logger/compare/v1.4.5...v1.4.6
|
|
2281
|
+
[1.4.5]: https://github.com/bymaxone/nest-logger/compare/v1.4.4...v1.4.5
|
|
2211
2282
|
[1.4.4]: https://github.com/bymaxone/nest-logger/compare/v1.4.3...v1.4.4
|
|
2212
2283
|
[1.4.3]: https://github.com/bymaxone/nest-logger/compare/v1.4.2...v1.4.3
|
|
2213
2284
|
[1.4.2]: https://github.com/bymaxone/nest-logger/compare/v1.4.1...v1.4.2
|
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,8 @@ 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
|
+
|
|
730
732
|
### 7. Context propagation with `LogContextService`
|
|
731
733
|
|
|
732
734
|
```typescript
|
|
@@ -843,7 +845,7 @@ function assertValidLogKey(key: string) {
|
|
|
843
845
|
|
|
844
846
|
The following keys are used internally by the library — do not reuse them in application code:
|
|
845
847
|
|
|
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`
|
|
848
|
+
`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
849
|
|
|
848
850
|
All reserved keys are exported as the `RESERVED_LOG_KEYS` constant from `@bymax-one/nest-logger/shared`.
|
|
849
851
|
|
|
@@ -1339,7 +1341,7 @@ A logger runs on every request of every service that installs it, so the suite i
|
|
|
1339
1341
|
|
|
1340
1342
|
- ✅ **100% line coverage** — statements, branches, functions, and lines, enforced as a release gate across unit + e2e
|
|
1341
1343
|
- ✅ **100% mutation score** — verified with [Stryker](https://stryker-mutator.io/) on a cold run
|
|
1342
|
-
against a `break` threshold of
|
|
1344
|
+
against a `break` threshold of 100, with **zero** surviving mutants. The handful that no test can
|
|
1343
1345
|
kill are suppressed on the line they apply to and each carries its reason; a rule against those
|
|
1344
1346
|
comments was retired once their cost was measured at **+0.10 kB brotli**, roughly a tenth of
|
|
1345
1347
|
what it had been assumed to be (see the [report](./docs/mutation_testing_results.md))
|
|
@@ -1351,7 +1353,7 @@ pnpm test # unit tests (Jest)
|
|
|
1351
1353
|
pnpm test:cov # coverage report
|
|
1352
1354
|
pnpm test:e2e # end-to-end tests (supertest)
|
|
1353
1355
|
pnpm test:cov:all # full coverage gate (100% statements/branches/functions/lines)
|
|
1354
|
-
pnpm mutation # Stryker mutation testing (
|
|
1356
|
+
pnpm mutation # Stryker mutation testing (100% break gate)
|
|
1355
1357
|
pnpm typecheck # tsc strict check (all tsconfig variants)
|
|
1356
1358
|
pnpm lint # ESLint
|
|
1357
1359
|
```
|
|
@@ -1579,22 +1581,25 @@ Two kinds of failure, and they surface differently. A **thrown `Error`** carries
|
|
|
1579
1581
|
| `[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
1582
|
| `[BymaxLoggerModule] useNestLogger(app) called but` | `BymaxLoggerModule.useNestLogger` | the module was never imported into the application | Import `BymaxLoggerModule` before calling it |
|
|
1581
1583
|
| `[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
|
|
1584
|
+
| `[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 |
|
|
1585
|
+
| `[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
1586
|
| `[PrettyDevDestination] refused in production` | `PrettyDevDestination.onInit` | `NODE_ENV` is exactly `production` | Use `DefaultStdoutDestination` in production |
|
|
1584
1587
|
| `[LogContextService] set() called outside run() scope` | `LogContextService.set` | called with no active `run()` | Wrap the work in `logContext.run({ ... }, () => ...)` |
|
|
1585
1588
|
| `[LogContextService] Refusing to set unsafe context key` | `LogContextService.set` | the key is `__proto__`, `constructor` or `prototype` | Use an ordinary key |
|
|
1586
1589
|
|
|
1587
|
-
The
|
|
1590
|
+
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
1591
|
|
|
1589
1592
|
### Log keys
|
|
1590
1593
|
|
|
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
|
-
| `
|
|
1594
|
+
| Code | Surfaces as | When | Action |
|
|
1595
|
+
| --------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
1596
|
+
| `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 |
|
|
1597
|
+
| `LOGGER_DESTINATION_WRITE_FAILED` | NDJSON line on `stderr` | `destination.write()` throws or rejects | Entry dropped for that destination; others continue |
|
|
1598
|
+
| `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 |
|
|
1599
|
+
| `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
|
+
| `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
|
+
| `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) | The record is dropped whole, never partly emitted; the message and ambient context stay. Find the value that broke the walk |
|
|
1598
1603
|
|
|
1599
1604
|
---
|
|
1600
1605
|
|
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
|
/**
|
|
@@ -429,6 +431,7 @@ function assignOwnErrorFields(target, source) {
|
|
|
429
431
|
}
|
|
430
432
|
}
|
|
431
433
|
} catch {
|
|
434
|
+
Reflect.defineProperty(target, "_errorFieldsIncomplete", { value: true, enumerable: true });
|
|
432
435
|
}
|
|
433
436
|
}
|
|
434
437
|
function toSanitizedChild(value, seen, depth2, maxDepth) {
|
|
@@ -1210,17 +1213,114 @@ var DEFAULT_REDACT_PATHS = [
|
|
|
1210
1213
|
...REDACT_COMMON_FIELDS.flatMap((field) => depth(field)),
|
|
1211
1214
|
...REDACT_ABSOLUTE_PATHS
|
|
1212
1215
|
];
|
|
1216
|
+
|
|
1217
|
+
// src/server/utils/report-destination-failure.util.ts
|
|
1218
|
+
function reportDestinationFailure(logKey, name, cause, msg) {
|
|
1219
|
+
writeStderrSafely(
|
|
1220
|
+
JSON.stringify({
|
|
1221
|
+
level: "error",
|
|
1222
|
+
logKey,
|
|
1223
|
+
destination: toSingleLineMessage(name),
|
|
1224
|
+
err: describeCause(cause),
|
|
1225
|
+
msg: toSingleLineMessage(msg)
|
|
1226
|
+
}) + "\n"
|
|
1227
|
+
);
|
|
1228
|
+
}
|
|
1229
|
+
var UNREADABLE_CAUSE = { type: "UnknownError", message: "unreadable value" };
|
|
1230
|
+
function describeCause(cause) {
|
|
1231
|
+
try {
|
|
1232
|
+
return cause instanceof Error ? {
|
|
1233
|
+
type: toSingleLineMessage(String(cause.name)),
|
|
1234
|
+
message: toSingleLineMessage(String(cause.message))
|
|
1235
|
+
} : { type: "UnknownError", message: toSingleLineMessage(String(cause)) };
|
|
1236
|
+
} catch {
|
|
1237
|
+
return UNREADABLE_CAUSE;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
function safeDestinationName(destination) {
|
|
1241
|
+
try {
|
|
1242
|
+
return String(destination.name);
|
|
1243
|
+
} catch {
|
|
1244
|
+
return "unknown";
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
function isLogLevel(value) {
|
|
1248
|
+
return LOG_LEVEL_PRIORITY.includes(value);
|
|
1249
|
+
}
|
|
1250
|
+
var resolvedMinLevels = /* @__PURE__ */ new WeakMap();
|
|
1251
|
+
function safeMinLevel(destination) {
|
|
1252
|
+
if (resolvedMinLevels.has(destination)) {
|
|
1253
|
+
return resolvedMinLevels.get(destination);
|
|
1254
|
+
}
|
|
1255
|
+
let resolved;
|
|
1256
|
+
try {
|
|
1257
|
+
const read = destination.minLevel;
|
|
1258
|
+
resolved = isLogLevel(read) ? read : void 0;
|
|
1259
|
+
} catch {
|
|
1260
|
+
}
|
|
1261
|
+
resolvedMinLevels.set(destination, resolved);
|
|
1262
|
+
return resolved;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// src/server/utils/otel-detector.ts
|
|
1266
|
+
var brokenInstallWarned = false;
|
|
1267
|
+
function isOtelApiMissing(cause) {
|
|
1268
|
+
try {
|
|
1269
|
+
const { code, message } = cause;
|
|
1270
|
+
return code === "MODULE_NOT_FOUND" && /^Cannot find module '@opentelemetry\/api'/.test(String(message));
|
|
1271
|
+
} catch {
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1213
1275
|
function loadTraceApiFrom(anchor) {
|
|
1276
|
+
let from;
|
|
1214
1277
|
try {
|
|
1215
|
-
|
|
1216
|
-
const mod = requireFrom("@opentelemetry/api");
|
|
1217
|
-
return mod.trace;
|
|
1278
|
+
from = anchor();
|
|
1218
1279
|
} catch {
|
|
1219
|
-
return void 0;
|
|
1280
|
+
return { api: void 0 };
|
|
1281
|
+
}
|
|
1282
|
+
try {
|
|
1283
|
+
const mod = module$1.createRequire(from)("@opentelemetry/api");
|
|
1284
|
+
const trace = typeof mod === "object" && mod !== null || typeof mod === "function" ? mod.trace : void 0;
|
|
1285
|
+
if (typeof trace?.getActiveSpan !== "function") {
|
|
1286
|
+
throw new TypeError("@opentelemetry/api resolved but exports no usable `trace` namespace");
|
|
1287
|
+
}
|
|
1288
|
+
return { api: trace };
|
|
1289
|
+
} catch (cause) {
|
|
1290
|
+
return isOtelApiMissing(cause) ? { api: void 0 } : { api: void 0, broken: { cause } };
|
|
1220
1291
|
}
|
|
1221
1292
|
}
|
|
1222
|
-
function
|
|
1223
|
-
|
|
1293
|
+
function warnBrokenInstallOnce(cause) {
|
|
1294
|
+
if (brokenInstallWarned) {
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
brokenInstallWarned = true;
|
|
1298
|
+
writeStderrSafely(
|
|
1299
|
+
JSON.stringify({
|
|
1300
|
+
level: "warn",
|
|
1301
|
+
logKey: RESERVED_LOG_KEYS.LOGGER_OTEL_API_BROKEN,
|
|
1302
|
+
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.",
|
|
1303
|
+
err: describeCause(cause)
|
|
1304
|
+
}) + "\n"
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
var DEFAULT_ANCHORS = [
|
|
1308
|
+
() => __filename,
|
|
1309
|
+
() => path.join(process.cwd(), "noop.cjs")
|
|
1310
|
+
];
|
|
1311
|
+
function detectOtelTraceApi(anchors = DEFAULT_ANCHORS) {
|
|
1312
|
+
let broken;
|
|
1313
|
+
for (const anchor of anchors) {
|
|
1314
|
+
const attempt = loadTraceApiFrom(anchor);
|
|
1315
|
+
if (attempt.api !== void 0) {
|
|
1316
|
+
return attempt.api;
|
|
1317
|
+
}
|
|
1318
|
+
broken ??= attempt.broken;
|
|
1319
|
+
}
|
|
1320
|
+
if (broken !== void 0) {
|
|
1321
|
+
warnBrokenInstallOnce(broken.cause);
|
|
1322
|
+
}
|
|
1323
|
+
return void 0;
|
|
1224
1324
|
}
|
|
1225
1325
|
function isValidTraceId(traceId) {
|
|
1226
1326
|
return /^[0-9a-f]{32}$/.test(traceId) && !/^0+$/.test(traceId);
|
|
@@ -1239,17 +1339,21 @@ function createTraceContextMixin(logContext, opts) {
|
|
|
1239
1339
|
Object.assign(merged, store);
|
|
1240
1340
|
}
|
|
1241
1341
|
if (traceApi) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1342
|
+
try {
|
|
1343
|
+
const span = traceApi.getActiveSpan();
|
|
1344
|
+
if (span) {
|
|
1345
|
+
const { traceId, spanId, traceFlags } = span.spanContext();
|
|
1346
|
+
if (isValidTraceId(traceId) && isValidSpanId(spanId)) {
|
|
1347
|
+
const flags = typeof traceFlags === "number" ? (traceFlags & 255).toString(16).padStart(2, "0") : void 0;
|
|
1348
|
+
Reflect.set(merged, opts.traceIdField, traceId);
|
|
1349
|
+
Reflect.set(merged, opts.spanIdField, spanId);
|
|
1350
|
+
if (flags !== void 0) {
|
|
1351
|
+
Reflect.set(merged, opts.traceFlagsField, flags);
|
|
1352
|
+
}
|
|
1251
1353
|
}
|
|
1252
1354
|
}
|
|
1355
|
+
} catch (cause) {
|
|
1356
|
+
warnBrokenInstallOnce(cause);
|
|
1253
1357
|
}
|
|
1254
1358
|
}
|
|
1255
1359
|
return merged;
|
|
@@ -1261,52 +1365,6 @@ function compileRedactPaths(extraPaths, shouldSkipDefaults) {
|
|
|
1261
1365
|
const sources = shouldSkipDefaults ? [extraPaths] : [DEFAULT_REDACT_PATHS, extraPaths];
|
|
1262
1366
|
return Array.from(new Set(sources.flat()));
|
|
1263
1367
|
}
|
|
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
1368
|
function reportWriteFailure(destination, cause) {
|
|
1311
1369
|
const name = safeDestinationName(destination);
|
|
1312
1370
|
reportDestinationFailure(
|
|
@@ -2562,6 +2620,22 @@ var MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
|
2562
2620
|
function writeRawToStdout(payload) {
|
|
2563
2621
|
writeStdoutSafely(payload);
|
|
2564
2622
|
}
|
|
2623
|
+
var RESOLUTION_FAILURE_CODES = ["MODULE_NOT_FOUND", "ERR_MODULE_NOT_FOUND"];
|
|
2624
|
+
function isPinoPrettyMissing(cause) {
|
|
2625
|
+
try {
|
|
2626
|
+
const { code, message } = cause;
|
|
2627
|
+
return RESOLUTION_FAILURE_CODES.includes(code) && /^Cannot find (?:module|package) 'pino-pretty'/.test(String(message));
|
|
2628
|
+
} catch {
|
|
2629
|
+
return false;
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
function describeFailure(cause) {
|
|
2633
|
+
try {
|
|
2634
|
+
return toSingleLineMessage(cause instanceof Error ? cause.message : String(cause));
|
|
2635
|
+
} catch {
|
|
2636
|
+
return "unreadable value";
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2565
2639
|
var PrettyDevDestination = class {
|
|
2566
2640
|
/**
|
|
2567
2641
|
* @param opts.minLevel — Optional minimum level filter applied by the
|
|
@@ -2634,10 +2708,11 @@ var PrettyDevDestination = class {
|
|
|
2634
2708
|
// and the last-resort rescue.
|
|
2635
2709
|
destination: process.stdout
|
|
2636
2710
|
});
|
|
2637
|
-
} catch {
|
|
2711
|
+
} catch (cause) {
|
|
2638
2712
|
this.initFailed = true;
|
|
2639
2713
|
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`."
|
|
2714
|
+
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)}`,
|
|
2715
|
+
{ cause }
|
|
2641
2716
|
);
|
|
2642
2717
|
}
|
|
2643
2718
|
this.flushBuffer((payload) => {
|
|
@@ -3102,7 +3177,13 @@ exports.PinoUnexpectedErrorSink = class PinoUnexpectedErrorSink {
|
|
|
3102
3177
|
void 0,
|
|
3103
3178
|
metadata
|
|
3104
3179
|
);
|
|
3105
|
-
} catch {
|
|
3180
|
+
} catch (cause) {
|
|
3181
|
+
reportDestinationFailure(
|
|
3182
|
+
RESERVED_LOG_KEYS.HTTP_UNEXPECTED_ERROR_DROPPED,
|
|
3183
|
+
"PinoUnexpectedErrorSink",
|
|
3184
|
+
cause,
|
|
3185
|
+
"An unexpected-error report could not be logged and was dropped."
|
|
3186
|
+
);
|
|
3106
3187
|
}
|
|
3107
3188
|
}
|
|
3108
3189
|
};
|
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.
|
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.
|
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
|
/**
|
|
@@ -426,6 +428,7 @@ function assignOwnErrorFields(target, source) {
|
|
|
426
428
|
}
|
|
427
429
|
}
|
|
428
430
|
} catch {
|
|
431
|
+
Reflect.defineProperty(target, "_errorFieldsIncomplete", { value: true, enumerable: true });
|
|
429
432
|
}
|
|
430
433
|
}
|
|
431
434
|
function toSanitizedChild(value, seen, depth2, maxDepth) {
|
|
@@ -1207,17 +1210,114 @@ var DEFAULT_REDACT_PATHS = [
|
|
|
1207
1210
|
...REDACT_COMMON_FIELDS.flatMap((field) => depth(field)),
|
|
1208
1211
|
...REDACT_ABSOLUTE_PATHS
|
|
1209
1212
|
];
|
|
1213
|
+
|
|
1214
|
+
// src/server/utils/report-destination-failure.util.ts
|
|
1215
|
+
function reportDestinationFailure(logKey, name, cause, msg) {
|
|
1216
|
+
writeStderrSafely(
|
|
1217
|
+
JSON.stringify({
|
|
1218
|
+
level: "error",
|
|
1219
|
+
logKey,
|
|
1220
|
+
destination: toSingleLineMessage(name),
|
|
1221
|
+
err: describeCause(cause),
|
|
1222
|
+
msg: toSingleLineMessage(msg)
|
|
1223
|
+
}) + "\n"
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
var UNREADABLE_CAUSE = { type: "UnknownError", message: "unreadable value" };
|
|
1227
|
+
function describeCause(cause) {
|
|
1228
|
+
try {
|
|
1229
|
+
return cause instanceof Error ? {
|
|
1230
|
+
type: toSingleLineMessage(String(cause.name)),
|
|
1231
|
+
message: toSingleLineMessage(String(cause.message))
|
|
1232
|
+
} : { type: "UnknownError", message: toSingleLineMessage(String(cause)) };
|
|
1233
|
+
} catch {
|
|
1234
|
+
return UNREADABLE_CAUSE;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
function safeDestinationName(destination) {
|
|
1238
|
+
try {
|
|
1239
|
+
return String(destination.name);
|
|
1240
|
+
} catch {
|
|
1241
|
+
return "unknown";
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
function isLogLevel(value) {
|
|
1245
|
+
return LOG_LEVEL_PRIORITY.includes(value);
|
|
1246
|
+
}
|
|
1247
|
+
var resolvedMinLevels = /* @__PURE__ */ new WeakMap();
|
|
1248
|
+
function safeMinLevel(destination) {
|
|
1249
|
+
if (resolvedMinLevels.has(destination)) {
|
|
1250
|
+
return resolvedMinLevels.get(destination);
|
|
1251
|
+
}
|
|
1252
|
+
let resolved;
|
|
1253
|
+
try {
|
|
1254
|
+
const read = destination.minLevel;
|
|
1255
|
+
resolved = isLogLevel(read) ? read : void 0;
|
|
1256
|
+
} catch {
|
|
1257
|
+
}
|
|
1258
|
+
resolvedMinLevels.set(destination, resolved);
|
|
1259
|
+
return resolved;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// src/server/utils/otel-detector.ts
|
|
1263
|
+
var brokenInstallWarned = false;
|
|
1264
|
+
function isOtelApiMissing(cause) {
|
|
1265
|
+
try {
|
|
1266
|
+
const { code, message } = cause;
|
|
1267
|
+
return code === "MODULE_NOT_FOUND" && /^Cannot find module '@opentelemetry\/api'/.test(String(message));
|
|
1268
|
+
} catch {
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1210
1272
|
function loadTraceApiFrom(anchor) {
|
|
1273
|
+
let from;
|
|
1211
1274
|
try {
|
|
1212
|
-
|
|
1213
|
-
const mod = requireFrom("@opentelemetry/api");
|
|
1214
|
-
return mod.trace;
|
|
1275
|
+
from = anchor();
|
|
1215
1276
|
} catch {
|
|
1216
|
-
return void 0;
|
|
1277
|
+
return { api: void 0 };
|
|
1278
|
+
}
|
|
1279
|
+
try {
|
|
1280
|
+
const mod = createRequire(from)("@opentelemetry/api");
|
|
1281
|
+
const trace = typeof mod === "object" && mod !== null || typeof mod === "function" ? mod.trace : void 0;
|
|
1282
|
+
if (typeof trace?.getActiveSpan !== "function") {
|
|
1283
|
+
throw new TypeError("@opentelemetry/api resolved but exports no usable `trace` namespace");
|
|
1284
|
+
}
|
|
1285
|
+
return { api: trace };
|
|
1286
|
+
} catch (cause) {
|
|
1287
|
+
return isOtelApiMissing(cause) ? { api: void 0 } : { api: void 0, broken: { cause } };
|
|
1217
1288
|
}
|
|
1218
1289
|
}
|
|
1219
|
-
function
|
|
1220
|
-
|
|
1290
|
+
function warnBrokenInstallOnce(cause) {
|
|
1291
|
+
if (brokenInstallWarned) {
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
brokenInstallWarned = true;
|
|
1295
|
+
writeStderrSafely(
|
|
1296
|
+
JSON.stringify({
|
|
1297
|
+
level: "warn",
|
|
1298
|
+
logKey: RESERVED_LOG_KEYS.LOGGER_OTEL_API_BROKEN,
|
|
1299
|
+
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.",
|
|
1300
|
+
err: describeCause(cause)
|
|
1301
|
+
}) + "\n"
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
var DEFAULT_ANCHORS = [
|
|
1305
|
+
() => __filename$1,
|
|
1306
|
+
() => join(process.cwd(), "noop.cjs")
|
|
1307
|
+
];
|
|
1308
|
+
function detectOtelTraceApi(anchors = DEFAULT_ANCHORS) {
|
|
1309
|
+
let broken;
|
|
1310
|
+
for (const anchor of anchors) {
|
|
1311
|
+
const attempt = loadTraceApiFrom(anchor);
|
|
1312
|
+
if (attempt.api !== void 0) {
|
|
1313
|
+
return attempt.api;
|
|
1314
|
+
}
|
|
1315
|
+
broken ??= attempt.broken;
|
|
1316
|
+
}
|
|
1317
|
+
if (broken !== void 0) {
|
|
1318
|
+
warnBrokenInstallOnce(broken.cause);
|
|
1319
|
+
}
|
|
1320
|
+
return void 0;
|
|
1221
1321
|
}
|
|
1222
1322
|
function isValidTraceId(traceId) {
|
|
1223
1323
|
return /^[0-9a-f]{32}$/.test(traceId) && !/^0+$/.test(traceId);
|
|
@@ -1236,17 +1336,21 @@ function createTraceContextMixin(logContext, opts) {
|
|
|
1236
1336
|
Object.assign(merged, store);
|
|
1237
1337
|
}
|
|
1238
1338
|
if (traceApi) {
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1339
|
+
try {
|
|
1340
|
+
const span = traceApi.getActiveSpan();
|
|
1341
|
+
if (span) {
|
|
1342
|
+
const { traceId, spanId, traceFlags } = span.spanContext();
|
|
1343
|
+
if (isValidTraceId(traceId) && isValidSpanId(spanId)) {
|
|
1344
|
+
const flags = typeof traceFlags === "number" ? (traceFlags & 255).toString(16).padStart(2, "0") : void 0;
|
|
1345
|
+
Reflect.set(merged, opts.traceIdField, traceId);
|
|
1346
|
+
Reflect.set(merged, opts.spanIdField, spanId);
|
|
1347
|
+
if (flags !== void 0) {
|
|
1348
|
+
Reflect.set(merged, opts.traceFlagsField, flags);
|
|
1349
|
+
}
|
|
1248
1350
|
}
|
|
1249
1351
|
}
|
|
1352
|
+
} catch (cause) {
|
|
1353
|
+
warnBrokenInstallOnce(cause);
|
|
1250
1354
|
}
|
|
1251
1355
|
}
|
|
1252
1356
|
return merged;
|
|
@@ -1258,52 +1362,6 @@ function compileRedactPaths(extraPaths, shouldSkipDefaults) {
|
|
|
1258
1362
|
const sources = shouldSkipDefaults ? [extraPaths] : [DEFAULT_REDACT_PATHS, extraPaths];
|
|
1259
1363
|
return Array.from(new Set(sources.flat()));
|
|
1260
1364
|
}
|
|
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
1365
|
function reportWriteFailure(destination, cause) {
|
|
1308
1366
|
const name = safeDestinationName(destination);
|
|
1309
1367
|
reportDestinationFailure(
|
|
@@ -2559,6 +2617,22 @@ var MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
|
2559
2617
|
function writeRawToStdout(payload) {
|
|
2560
2618
|
writeStdoutSafely(payload);
|
|
2561
2619
|
}
|
|
2620
|
+
var RESOLUTION_FAILURE_CODES = ["MODULE_NOT_FOUND", "ERR_MODULE_NOT_FOUND"];
|
|
2621
|
+
function isPinoPrettyMissing(cause) {
|
|
2622
|
+
try {
|
|
2623
|
+
const { code, message } = cause;
|
|
2624
|
+
return RESOLUTION_FAILURE_CODES.includes(code) && /^Cannot find (?:module|package) 'pino-pretty'/.test(String(message));
|
|
2625
|
+
} catch {
|
|
2626
|
+
return false;
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
function describeFailure(cause) {
|
|
2630
|
+
try {
|
|
2631
|
+
return toSingleLineMessage(cause instanceof Error ? cause.message : String(cause));
|
|
2632
|
+
} catch {
|
|
2633
|
+
return "unreadable value";
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2562
2636
|
var PrettyDevDestination = class {
|
|
2563
2637
|
/**
|
|
2564
2638
|
* @param opts.minLevel — Optional minimum level filter applied by the
|
|
@@ -2631,10 +2705,11 @@ var PrettyDevDestination = class {
|
|
|
2631
2705
|
// and the last-resort rescue.
|
|
2632
2706
|
destination: process.stdout
|
|
2633
2707
|
});
|
|
2634
|
-
} catch {
|
|
2708
|
+
} catch (cause) {
|
|
2635
2709
|
this.initFailed = true;
|
|
2636
2710
|
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`."
|
|
2711
|
+
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)}`,
|
|
2712
|
+
{ cause }
|
|
2638
2713
|
);
|
|
2639
2714
|
}
|
|
2640
2715
|
this.flushBuffer((payload) => {
|
|
@@ -3099,7 +3174,13 @@ var PinoUnexpectedErrorSink = class {
|
|
|
3099
3174
|
void 0,
|
|
3100
3175
|
metadata
|
|
3101
3176
|
);
|
|
3102
|
-
} catch {
|
|
3177
|
+
} catch (cause) {
|
|
3178
|
+
reportDestinationFailure(
|
|
3179
|
+
RESERVED_LOG_KEYS.HTTP_UNEXPECTED_ERROR_DROPPED,
|
|
3180
|
+
"PinoUnexpectedErrorSink",
|
|
3181
|
+
cause,
|
|
3182
|
+
"An unexpected-error report could not be logged and was dropped."
|
|
3183
|
+
);
|
|
3103
3184
|
}
|
|
3104
3185
|
}
|
|
3105
3186
|
};
|
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.6",
|
|
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",
|