@bymax-one/nest-logger 1.2.6 → 1.2.9
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 +616 -2
- package/README.md +71 -5
- package/dist/server/index.cjs +366 -55
- package/dist/server/index.d.cts +149 -12
- package/dist/server/index.d.ts +149 -12
- package/dist/server/index.mjs +366 -55
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -534,6 +534,33 @@ any other field. `AggregateError` members reach the entry as `err.errors`. There
|
|
|
534
534
|
OpenTelemetry attribute for a cause chain, so these stay namespaced under `err` rather than
|
|
535
535
|
inventing an `exception.cause` the spec does not define.
|
|
536
536
|
|
|
537
|
+
**An error's own enumerable properties travel with it at every depth** — `code` on a Node system
|
|
538
|
+
error, `statusCode` from an HTTP layer, whatever domain fields you attached — including on a nested
|
|
539
|
+
`cause` and on each `AggregateError` member:
|
|
540
|
+
|
|
541
|
+
```json
|
|
542
|
+
{
|
|
543
|
+
"err": {
|
|
544
|
+
"type": "Error",
|
|
545
|
+
"message": "bootstrap failed",
|
|
546
|
+
"code": "EBOOT",
|
|
547
|
+
"cause": {
|
|
548
|
+
"name": "Error",
|
|
549
|
+
"message": "config invalid",
|
|
550
|
+
"code": "BYMAX_CONFIG_VALIDATION",
|
|
551
|
+
"issues": [{ "variable": "DATABASE_URL", "code": "invalid_url" }]
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
This matters because `code` is usually the field you alert on. Until 1.2.7 those properties survived
|
|
558
|
+
only on the error handed to the log call and were dropped the moment the same error was wrapped as
|
|
559
|
+
someone else's `cause` — which is what any handler that wraps what it caught does before
|
|
560
|
+
logging it. They never shadow a
|
|
561
|
+
field the serializer derives, and they pass through the same name-based redaction and size bound as
|
|
562
|
+
any other serializer output.
|
|
563
|
+
|
|
537
564
|
An error's own enumerable properties (`code`, `statusCode`, domain fields) are carried through, as
|
|
538
565
|
Pino's standard serializer did.
|
|
539
566
|
|
|
@@ -760,10 +787,11 @@ So the common accident — adding `new PrettyDevDestination()` without installin
|
|
|
760
787
|
```typescript
|
|
761
788
|
// One line per entry, with the fields your project repeats on every line hidden.
|
|
762
789
|
new PrettyDevDestination({
|
|
763
|
-
view: { singleLine: true, ignore: 'pid,hostname,service,deployment,event
|
|
790
|
+
view: { singleLine: true, ignore: 'pid,hostname,service,deployment,event\\.name' }
|
|
764
791
|
})
|
|
765
792
|
|
|
766
793
|
// Message only, with the context pulled back into the line.
|
|
794
|
+
// See the messageFormat warning below before interpolating any other field.
|
|
767
795
|
new PrettyDevDestination({
|
|
768
796
|
view: { hideObject: true, messageFormat: '[{context}] {msg}' }
|
|
769
797
|
})
|
|
@@ -778,10 +806,29 @@ new PrettyDevDestination({
|
|
|
778
806
|
| `translateTime` | `'SYS:HH:MM:ss.l'` | Or `false` for the raw timestamp |
|
|
779
807
|
| `colorize` | `true` | ANSI colour |
|
|
780
808
|
|
|
781
|
-
> **`hideObject` hides it
|
|
809
|
+
> **`hideObject` hides it from THIS destination.** Any other destination you registered still receives the complete entry — the option is display-only and scoped to the pretty renderer. When pretty is your **only** destination that scope becomes total: `destinations` **replaces** stdout, so there is no JSON copy behind the rendering and a hidden field, `logKey` included, is not visible anywhere. That is what the option is for; `messageFormat` is how you pull a specific field back.
|
|
810
|
+
|
|
811
|
+
> **A dot in `ignore` means "nested path", so a field name that CONTAINS a dot must escape it.** This library emits `event.name` as a literal top-level key, so the obvious spelling hides nothing at all — `pino-pretty` looks for an `event` object with a `name` inside, finds neither, and says nothing. Measured on one entry with only the option changing:
|
|
812
|
+
>
|
|
813
|
+
> ```
|
|
814
|
+
> ignore: '…,event.name' → INFO: msg {"logKey":"X","event.name":"x"} ← still there
|
|
815
|
+
> ignore: '…,event\\.name' → INFO: msg {"logKey":"X"} ← hidden
|
|
816
|
+
> ```
|
|
817
|
+
>
|
|
818
|
+
> In a TypeScript string literal that is `'event\\.name'` — one real backslash reaching `pino-pretty`. `service` and `deployment` need no escape; they really are nested objects. Reported by a consumer who copied the example above when it still had the unescaped form.
|
|
782
819
|
|
|
783
820
|
The shape is exported as `PrettyViewOptions` when you want to build the view separately — it is this library's own interface, not a re-export of `pino-pretty`'s `PrettyOptions`, so type-checking without the optional peer installed still resolves.
|
|
784
821
|
|
|
822
|
+
> **`messageFormat` interpolates every placeholder except `{msg}` raw — that can forge a terminal entry.** This library normalizes line separators and control characters in `msg` and in the stack, never in metadata, because escaping data would mean rewriting what you asked to be logged. `pino-pretty` substitutes whatever the field holds, so a newline in an interpolated field splits one entry into two and the second reads like a genuine record. Measured, with a newline in `context`:
|
|
823
|
+
>
|
|
824
|
+
> ```
|
|
825
|
+
> lines produced by ONE entry: 2
|
|
826
|
+
> 1: "[10:35:14.484] INFO: [Auth"
|
|
827
|
+
> 2: "[10:00:00.000] INFO: FORGED admin promoted] real entry …"
|
|
828
|
+
> ```
|
|
829
|
+
>
|
|
830
|
+
> Interpolate only fields your own code sets. Never one carrying user input — `{tenantId}`, `{userId}`, a header, a query value. If you need one of those visible, leave it in the record instead. That closes **this** path and no more: a metadata value is not safe terminal text either, since `JSON.stringify` escapes only C0 and emits DEL, the C1 range (U+0085 NEL included), U+2028 and U+2029 verbatim — see _One entry, one line_ below, where the same boundary is stated. Only `msg` and the stack carry that guarantee.
|
|
831
|
+
|
|
785
832
|
> **`destination` is not exposed, by design.** The library owns where entries go — a redirected stream would route around the fan-out and the last-resort rescue above. It is absent from the type and applied after your options are merged, so it cannot be overridden from untyped JavaScript either.
|
|
786
833
|
|
|
787
834
|
**The first entries of a boot are held, not lost.** The transform cannot exist until `onInit` — loading the optional peer is async — so everything NestJS emits while instantiating providers arrives before it. Those entries are buffered and then rendered through the transform in arrival order. If the peer is missing, the bound is reached, or the app shuts down before init, they are written as raw NDJSON instead: degraded, never dropped.
|
|
@@ -1008,6 +1055,21 @@ Trace context is never copied verbatim into a log entry. The OTel mixin reads th
|
|
|
1008
1055
|
|
|
1009
1056
|
Every `write()` runs inside a try/catch. A destination that throws or rejects produces a `LOGGER_DESTINATION_WRITE_FAILED` line on `stderr` — never back through the logger, which would turn a broken sink into a write → log → write feedback loop — and the entry is dropped for that sink only. A destination whose `onInit()` rejects is reported as `LOGGER_DESTINATION_INIT_FAILED` and excluded from the shutdown sequence without blocking boot; it stays in the write fan-out, where the same fail-soft wrapper contains it. A logging backend going down degrades logging — it never takes the application with it.
|
|
1010
1057
|
|
|
1058
|
+
### What redaction covers, and what it cannot
|
|
1059
|
+
|
|
1060
|
+
Redaction here matches **field names**, not values. Under the default `redactStrategy: 'names'` a secret stored under a covered name is censored at any depth — under the legacy `'paths'` strategy the four-level `fast-redact` ceiling applies instead, and a deeper field is not covered. Either way, the same secret **embedded inside a string** under an uncovered name travels intact. Measured:
|
|
1061
|
+
|
|
1062
|
+
```json
|
|
1063
|
+
{
|
|
1064
|
+
"password": "[REDACTED]",
|
|
1065
|
+
"renderedBody": "Hi Ana, your reset code is 481920. Expires in 10 minutes."
|
|
1066
|
+
}
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
There is no name rule that reaches the second one, and scanning inside values for secret-shaped text would mean rewriting what you asked to be logged — wrong in both directions, silently. **The boundary is yours to hold:** do not put rendered bodies, raw provider responses or whole request payloads into a field, and be aware that attaching an upstream error as `cause` carries whatever that error carries. Since 1.2.7 preserves an error's own fields at every depth of the chain, a cause that quotes something it should not quote is now more visible, not less.
|
|
1070
|
+
|
|
1071
|
+
`redactStrategy` governs **your** payload — the record you log, your child bindings, and the output of any serializer you supply. The built-in `err` serializer is the library's own output and is always name-walked, including under `'paths'`, because `fast-redact`'s four-wildcard ceiling cannot reach the depth a `cause` chain serializes to.
|
|
1072
|
+
|
|
1011
1073
|
### A closed pipe does not kill the process
|
|
1012
1074
|
|
|
1013
1075
|
`node app | head` closes the read end of stdout while the application is still writing. The stream reports that as `EPIPE` **asynchronously**, through its `'error'` event, after `write()` has already returned — so a `try/catch` around the write catches nothing, and because Node attaches no default handler to `process.stdout`, the emit becomes an uncaught exception and the process dies. Measured, not assumed: `0` listeners, no synchronous throw, `UNCAUGHT:EPIPE`, exit 42.
|
|
@@ -1262,9 +1324,13 @@ interface ILogDestination {
|
|
|
1262
1324
|
readonly name: string
|
|
1263
1325
|
readonly minLevel?: LogLevel
|
|
1264
1326
|
/** `payload` is a newline-terminated JSON entry, UTF-8 encoded — not an object. */
|
|
1265
|
-
write(payload: string): void |
|
|
1266
|
-
onInit?():
|
|
1267
|
-
|
|
1327
|
+
write(payload: string): void | PromiseLike<void>
|
|
1328
|
+
onInit?(): void | PromiseLike<void>
|
|
1329
|
+
/** Called once after EVERY destination's `onInit` settled. Only useful if you buffer. */
|
|
1330
|
+
onRegistryReady?(status: {
|
|
1331
|
+
readonly heldEntriesDeliveredElsewhere: boolean
|
|
1332
|
+
}): void | PromiseLike<void>
|
|
1333
|
+
onShutdown?(): void | PromiseLike<void>
|
|
1268
1334
|
}
|
|
1269
1335
|
```
|
|
1270
1336
|
|