@bymax-one/nest-logger 1.2.5 → 1.2.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 +220 -1
- package/README.md +77 -1
- package/dist/server/index.cjs +158 -39
- package/dist/server/index.d.cts +139 -10
- package/dist/server/index.d.ts +139 -10
- package/dist/server/index.mjs +158 -39
- package/package.json +1 -1
package/dist/server/index.cjs
CHANGED
|
@@ -232,6 +232,13 @@ function escapeControlCharacters(text) {
|
|
|
232
232
|
var DEFAULT_MAX_CAUSE_DEPTH = 3;
|
|
233
233
|
var MAX_AGGREGATE_ERRORS = 10;
|
|
234
234
|
var CIRCULAR_SENTINEL = "[Circular]";
|
|
235
|
+
var DERIVED_ERROR_FIELDS = /* @__PURE__ */ new Set([
|
|
236
|
+
"name",
|
|
237
|
+
"message",
|
|
238
|
+
"stack",
|
|
239
|
+
"cause",
|
|
240
|
+
"errors"
|
|
241
|
+
]);
|
|
235
242
|
function sanitizeError(err, options) {
|
|
236
243
|
const maxCauseDepth = DEFAULT_MAX_CAUSE_DEPTH;
|
|
237
244
|
return toSanitizedError(err, /* @__PURE__ */ new WeakSet(), 0, maxCauseDepth);
|
|
@@ -267,11 +274,23 @@ function toSanitizedError(value, seen, depth2, maxDepth) {
|
|
|
267
274
|
result.errors.push(createWidthMarker(aggregated.length - MAX_AGGREGATE_ERRORS));
|
|
268
275
|
}
|
|
269
276
|
}
|
|
277
|
+
assignOwnErrorFields(result, value);
|
|
270
278
|
return result;
|
|
271
279
|
} catch {
|
|
272
280
|
return { name: "SanitizeFailed", message: "Failed to sanitize the thrown value" };
|
|
273
281
|
}
|
|
274
282
|
}
|
|
283
|
+
function assignOwnErrorFields(target, source) {
|
|
284
|
+
try {
|
|
285
|
+
const own = Array.isArray(source) ? {} : source;
|
|
286
|
+
for (const [key, value] of Object.entries(own)) {
|
|
287
|
+
if (!Object.hasOwn(target, key) && !DERIVED_ERROR_FIELDS.has(key)) {
|
|
288
|
+
Object.defineProperty(target, key, { value, enumerable: true });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} catch {
|
|
292
|
+
}
|
|
293
|
+
}
|
|
275
294
|
function toSanitizedChild(value, seen, depth2, maxDepth) {
|
|
276
295
|
if (isObject(value) && seen.has(value)) {
|
|
277
296
|
return CIRCULAR_SENTINEL;
|
|
@@ -586,6 +605,30 @@ function collectContextLoggerProviders() {
|
|
|
586
605
|
function collectContextLoggerTokens() {
|
|
587
606
|
return Array.from(contextTokens.values());
|
|
588
607
|
}
|
|
608
|
+
|
|
609
|
+
// src/server/utils/safe-stdio.util.ts
|
|
610
|
+
var swallowStreamError = () => void 0;
|
|
611
|
+
function ensureGuarded(stream) {
|
|
612
|
+
if (stream.listeners("error").includes(swallowStreamError)) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
stream.on("error", swallowStreamError);
|
|
616
|
+
}
|
|
617
|
+
function writeSafely(stream, payload) {
|
|
618
|
+
try {
|
|
619
|
+
ensureGuarded(stream);
|
|
620
|
+
stream.write(payload);
|
|
621
|
+
} catch {
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
function writeStdoutSafely(payload) {
|
|
625
|
+
writeSafely(process.stdout, payload);
|
|
626
|
+
}
|
|
627
|
+
function writeStderrSafely(line) {
|
|
628
|
+
writeSafely(process.stderr, line);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// src/server/destinations/default-stdout.destination.ts
|
|
589
632
|
exports.DefaultStdoutDestination = class DefaultStdoutDestination {
|
|
590
633
|
/**
|
|
591
634
|
* @param opts.minLevel — Optional minimum level filter. Entries below this
|
|
@@ -600,9 +643,17 @@ exports.DefaultStdoutDestination = class DefaultStdoutDestination {
|
|
|
600
643
|
}
|
|
601
644
|
/**
|
|
602
645
|
* Write a serialized JSON log entry (terminated with `\n`) to `stdout`.
|
|
646
|
+
*
|
|
647
|
+
* Routed through {@link writeStdoutSafely} rather than calling
|
|
648
|
+
* `process.stdout.write` directly. This is the sink a consumer gets when they
|
|
649
|
+
* configure nothing, so it is the path that decides whether a closed pipe
|
|
650
|
+
* (`node app | head`) kills the application — and unguarded, it did: EPIPE
|
|
651
|
+
* arrives asynchronously through the stream's `'error'` event, Node attaches no
|
|
652
|
+
* default handler, and the emit becomes an uncaught exception. Measured on the
|
|
653
|
+
* built artifact, exit code 42.
|
|
603
654
|
*/
|
|
604
655
|
write(payload) {
|
|
605
|
-
|
|
656
|
+
writeStdoutSafely(payload);
|
|
606
657
|
}
|
|
607
658
|
};
|
|
608
659
|
exports.DefaultStdoutDestination = __decorateClass([
|
|
@@ -1034,7 +1085,7 @@ function reportDestinationFailure(logKey, name, cause, msg) {
|
|
|
1034
1085
|
type: toSingleLineMessage(String(cause.name)),
|
|
1035
1086
|
message: toSingleLineMessage(String(cause.message))
|
|
1036
1087
|
} : { type: "UnknownError", message: toSingleLineMessage(String(cause)) };
|
|
1037
|
-
|
|
1088
|
+
writeStderrSafely(
|
|
1038
1089
|
JSON.stringify({
|
|
1039
1090
|
level: "error",
|
|
1040
1091
|
logKey,
|
|
@@ -1057,10 +1108,7 @@ function reportWriteFailure(name, cause) {
|
|
|
1057
1108
|
);
|
|
1058
1109
|
}
|
|
1059
1110
|
function rescueToStdout(payload) {
|
|
1060
|
-
|
|
1061
|
-
process.stdout.write(payload);
|
|
1062
|
-
} catch {
|
|
1063
|
-
}
|
|
1111
|
+
writeStdoutSafely(payload);
|
|
1064
1112
|
}
|
|
1065
1113
|
function destinationToStream(destination, health) {
|
|
1066
1114
|
return new stream.Writable({
|
|
@@ -1372,13 +1420,6 @@ function leafNameOf(path) {
|
|
|
1372
1420
|
}
|
|
1373
1421
|
return leaf;
|
|
1374
1422
|
}
|
|
1375
|
-
var DERIVED_ERROR_FIELDS = /* @__PURE__ */ new Set([
|
|
1376
|
-
"name",
|
|
1377
|
-
"message",
|
|
1378
|
-
"stack",
|
|
1379
|
-
"cause",
|
|
1380
|
-
"errors"
|
|
1381
|
-
]);
|
|
1382
1423
|
function serializeErrorValue(value_) {
|
|
1383
1424
|
const sanitized = sanitizeError(value_);
|
|
1384
1425
|
const serialized = {
|
|
@@ -1395,18 +1436,7 @@ function serializeErrorValue(value_) {
|
|
|
1395
1436
|
if (sanitized.errors !== void 0) {
|
|
1396
1437
|
serialized["errors"] = sanitized.errors;
|
|
1397
1438
|
}
|
|
1398
|
-
|
|
1399
|
-
const source = (
|
|
1400
|
-
// Stryker disable next-line ConditionalExpression: equivalent BY CONSTRUCTION, not untested — the never-throw catch below makes the mutant observationally identical: with the null check flipped to true, `Object.entries(null)` throws, the catch swallows it, and the emitted envelope is byte-for-byte the same. The guard stays because routing the ordinary null case through exception control flow would trade an explicit branch for a hidden one.
|
|
1401
|
-
typeof value_ === "object" && value_ !== null && !Array.isArray(value_) ? value_ : {}
|
|
1402
|
-
);
|
|
1403
|
-
for (const [key, value] of Object.entries(source)) {
|
|
1404
|
-
if (!Object.hasOwn(serialized, key) && !DERIVED_ERROR_FIELDS.has(key)) {
|
|
1405
|
-
Reflect.set(serialized, key, value);
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
} catch {
|
|
1409
|
-
}
|
|
1439
|
+
assignOwnErrorFields(serialized, sanitized);
|
|
1410
1440
|
return serialized;
|
|
1411
1441
|
}
|
|
1412
1442
|
function withSemconvException(record, format) {
|
|
@@ -1456,6 +1486,9 @@ function resolveNameRedactor(options) {
|
|
|
1456
1486
|
if (options.redactStrategy !== "names") {
|
|
1457
1487
|
return PASS_THROUGH;
|
|
1458
1488
|
}
|
|
1489
|
+
return buildNameRedactor(options);
|
|
1490
|
+
}
|
|
1491
|
+
function buildNameRedactor(options) {
|
|
1459
1492
|
const names = [
|
|
1460
1493
|
...options.shouldDisableDefaultRedact ? [] : REDACT_COMMON_FIELDS,
|
|
1461
1494
|
...options.redactPaths.flatMap((path) => leafNameOf(path) ?? [])
|
|
@@ -1474,8 +1507,9 @@ function resolveRedactOption(options) {
|
|
|
1474
1507
|
}
|
|
1475
1508
|
function buildPinoInstance(options, logContext, destinations, health) {
|
|
1476
1509
|
const maxBytes = options.maxEntrySizeBytes;
|
|
1477
|
-
const
|
|
1478
|
-
const
|
|
1510
|
+
const redactRecord = resolveNameRedactor(options);
|
|
1511
|
+
const redactSerialized = buildNameRedactor(options);
|
|
1512
|
+
const bound = (serializer) => createSizeBoundedSerializer((input) => redactSerialized(serializer(input)), maxBytes);
|
|
1479
1513
|
const serializers = {
|
|
1480
1514
|
err: bound(serializeErrorValue),
|
|
1481
1515
|
...Object.fromEntries(
|
|
@@ -1512,14 +1546,17 @@ function buildPinoInstance(options, logContext, destinations, health) {
|
|
|
1512
1546
|
// consumer's extra metadata instead of silently dropping it. Runs ONCE, at
|
|
1513
1547
|
// logger construction — no per-entry cost. `true`: bindings are a record
|
|
1514
1548
|
// root, iterated rather than serialized.
|
|
1515
|
-
bindings: (bindings) =>
|
|
1549
|
+
bindings: (bindings) => redactRecord(bindings, true),
|
|
1516
1550
|
// The name-walk redactor runs here, on the merged record (mixin output +
|
|
1517
1551
|
// the caller's object), which is exactly the surface a caller controls.
|
|
1518
1552
|
// `base` and child bindings are NOT visible at this hook — they are
|
|
1519
1553
|
// library-owned (`service`, the `@InjectLogger` context) and carry nothing
|
|
1520
1554
|
// sensitive, which `pino-factory.spec.ts` pins.
|
|
1521
1555
|
log: (record) => withSemconvException(
|
|
1522
|
-
withEventName(
|
|
1556
|
+
withEventName(
|
|
1557
|
+
redactRecord(record, true),
|
|
1558
|
+
options.eventNameField
|
|
1559
|
+
),
|
|
1523
1560
|
options.errorFormat
|
|
1524
1561
|
)
|
|
1525
1562
|
},
|
|
@@ -1757,7 +1794,7 @@ var DestinationRegistry = class {
|
|
|
1757
1794
|
await destination.onShutdown?.();
|
|
1758
1795
|
} catch (cause) {
|
|
1759
1796
|
const detail = cause instanceof Error ? cause.stack ?? cause.message : String(cause);
|
|
1760
|
-
|
|
1797
|
+
writeStderrSafely(
|
|
1761
1798
|
`[DestinationRegistry] Shutdown failed for "${destination.name}": ${detail}
|
|
1762
1799
|
`
|
|
1763
1800
|
);
|
|
@@ -2037,23 +2074,60 @@ exports.BymaxLoggerModule = __decorateClass([
|
|
|
2037
2074
|
], exports.BymaxLoggerModule);
|
|
2038
2075
|
|
|
2039
2076
|
// src/server/destinations/pretty-dev.destination.ts
|
|
2077
|
+
var DEFAULT_VIEW = {
|
|
2078
|
+
colorize: true,
|
|
2079
|
+
translateTime: "SYS:HH:MM:ss.l",
|
|
2080
|
+
ignore: "pid,hostname,service",
|
|
2081
|
+
singleLine: false
|
|
2082
|
+
};
|
|
2083
|
+
var MAX_BUFFERED_ENTRIES = 1e3;
|
|
2084
|
+
var MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
2085
|
+
function writeRawToStdout(payload) {
|
|
2086
|
+
writeStdoutSafely(payload);
|
|
2087
|
+
}
|
|
2040
2088
|
var PrettyDevDestination = class {
|
|
2041
2089
|
/**
|
|
2042
2090
|
* @param opts.minLevel — Optional minimum level filter applied by the
|
|
2043
2091
|
* destination registry's multi-stream wiring. Omit to accept every level.
|
|
2092
|
+
* @param opts.view — Optional rendering overrides; see {@link PrettyViewOptions}.
|
|
2093
|
+
* Omitted fields keep their default, so `new PrettyDevDestination()` renders
|
|
2094
|
+
* exactly as it did before this option existed.
|
|
2044
2095
|
*/
|
|
2045
2096
|
constructor(opts = {}) {
|
|
2046
2097
|
this.name = "pretty-dev";
|
|
2047
2098
|
/**
|
|
2048
2099
|
* Set when {@link onInit} fails (e.g. `pino-pretty` absent). Distinguishes a
|
|
2049
2100
|
* permanently-failed destination — whose writes are DROPPED, since the registry
|
|
2050
|
-
* dropped it from the active set — from the transient pre-init window
|
|
2051
|
-
* {@link write} falls back to raw stdout so a bootstrap log is not lost.
|
|
2101
|
+
* dropped it from the active set — from the transient pre-init window.
|
|
2052
2102
|
*/
|
|
2053
2103
|
this.initFailed = false;
|
|
2104
|
+
/**
|
|
2105
|
+
* Entries written before {@link onInit} built the transform, held so they can be
|
|
2106
|
+
* RENDERED rather than printed raw.
|
|
2107
|
+
*
|
|
2108
|
+
* The transform cannot exist earlier: loading the optional peer is async, and
|
|
2109
|
+
* `onInit` is the first moment the lifecycle offers. Before this buffer those
|
|
2110
|
+
* entries went to stdout as raw NDJSON — correct, nothing lost, but a real boot
|
|
2111
|
+
* put dozens of JSON lines on screen before the first rendered one, and a
|
|
2112
|
+
* developer who had just turned pretty on reasonably concluded it had not
|
|
2113
|
+
* worked. Reported from exactly that reading.
|
|
2114
|
+
*/
|
|
2115
|
+
this.buffer = [];
|
|
2116
|
+
/**
|
|
2117
|
+
* Set once {@link MAX_BUFFERED_ENTRIES} is reached. From then on entries go
|
|
2118
|
+
* straight to raw stdout — and the buffer is drained raw at the same moment, so
|
|
2119
|
+
* the output stays in ORDER rather than replaying old entries after newer ones.
|
|
2120
|
+
*/
|
|
2121
|
+
this.bufferOverflowed = false;
|
|
2122
|
+
/**
|
|
2123
|
+
* UTF-8 size of everything currently held, tracked incrementally so the bound
|
|
2124
|
+
* costs one `Buffer.byteLength` per entry rather than a walk of the buffer.
|
|
2125
|
+
*/
|
|
2126
|
+
this.bufferedBytes = 0;
|
|
2054
2127
|
if (opts.minLevel !== void 0) {
|
|
2055
2128
|
this.minLevel = opts.minLevel;
|
|
2056
2129
|
}
|
|
2130
|
+
this.view = opts.view ?? {};
|
|
2057
2131
|
}
|
|
2058
2132
|
/**
|
|
2059
2133
|
* Lazily resolve `pino-pretty` and build the colorized transform that renders
|
|
@@ -2066,18 +2140,31 @@ var PrettyDevDestination = class {
|
|
|
2066
2140
|
try {
|
|
2067
2141
|
const { build } = await import('pino-pretty');
|
|
2068
2142
|
this.stream = build({
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2143
|
+
...DEFAULT_VIEW,
|
|
2144
|
+
...this.view,
|
|
2145
|
+
// AFTER the consumer's view, deliberately. Omitting `destination` from
|
|
2146
|
+
// `PrettyViewOptions` states that the library owns the sink; applying it
|
|
2147
|
+
// last is what ENFORCES it, including against an untyped caller reaching
|
|
2148
|
+
// in from JavaScript. A redirected stream would route around the fan-out
|
|
2149
|
+
// and the last-resort rescue.
|
|
2073
2150
|
destination: process.stdout
|
|
2074
2151
|
});
|
|
2075
2152
|
} catch {
|
|
2076
2153
|
this.initFailed = true;
|
|
2154
|
+
this.flushBuffer((payload) => {
|
|
2155
|
+
writeRawToStdout(payload);
|
|
2156
|
+
});
|
|
2077
2157
|
throw new Error(
|
|
2078
2158
|
"[PrettyDevDestination] pino-pretty is not installed. Install it as a peer dependency (`pnpm add -D pino-pretty`) or remove PrettyDevDestination from `destinations`."
|
|
2079
2159
|
);
|
|
2080
2160
|
}
|
|
2161
|
+
this.flushBuffer((payload) => {
|
|
2162
|
+
try {
|
|
2163
|
+
this.stream?.write(payload);
|
|
2164
|
+
} catch {
|
|
2165
|
+
writeRawToStdout(payload);
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2081
2168
|
}
|
|
2082
2169
|
/**
|
|
2083
2170
|
* Write a serialized NDJSON entry to the pretty transform.
|
|
@@ -2088,8 +2175,10 @@ var PrettyDevDestination = class {
|
|
|
2088
2175
|
* fan-out already skips a failed destination before reaching this method
|
|
2089
2176
|
* (and rescues the entry when nothing else initialized); this branch is the
|
|
2090
2177
|
* belt to that, for any caller holding the destination directly;
|
|
2091
|
-
* - pre-init (transient window before {@link onInit}, e.g.
|
|
2092
|
-
*
|
|
2178
|
+
* - pre-init (transient window before {@link onInit}, e.g. every entry NestJS
|
|
2179
|
+
* emits while instantiating providers) → BUFFERED, then rendered through
|
|
2180
|
+
* the transform once it exists. Nothing is lost on any path: if init fails
|
|
2181
|
+
* the buffer is drained raw, and if it overflows it is drained raw in order.
|
|
2093
2182
|
*
|
|
2094
2183
|
* @param payload — Newline-terminated JSON entry, UTF-8 encoded.
|
|
2095
2184
|
*/
|
|
@@ -2101,7 +2190,36 @@ var PrettyDevDestination = class {
|
|
|
2101
2190
|
if (this.initFailed) {
|
|
2102
2191
|
return;
|
|
2103
2192
|
}
|
|
2104
|
-
|
|
2193
|
+
if (this.bufferOverflowed) {
|
|
2194
|
+
writeRawToStdout(payload);
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
const payloadBytes = Buffer.byteLength(payload, "utf8");
|
|
2198
|
+
if (this.buffer.length >= MAX_BUFFERED_ENTRIES || this.bufferedBytes + payloadBytes > MAX_BUFFERED_BYTES) {
|
|
2199
|
+
this.bufferOverflowed = true;
|
|
2200
|
+
this.flushBuffer(writeRawToStdout);
|
|
2201
|
+
writeRawToStdout(payload);
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
this.buffer.push(payload);
|
|
2205
|
+
this.bufferedBytes += payloadBytes;
|
|
2206
|
+
}
|
|
2207
|
+
/**
|
|
2208
|
+
* Hand every buffered entry to `emit`, in arrival order, and empty the buffer.
|
|
2209
|
+
*
|
|
2210
|
+
* Emptying unconditionally is what makes this safe to call from both the
|
|
2211
|
+
* success and failure paths: whichever runs first owns the entries, and the
|
|
2212
|
+
* other finds nothing to emit twice.
|
|
2213
|
+
*
|
|
2214
|
+
* @param emit - Receives each buffered payload, oldest first.
|
|
2215
|
+
*/
|
|
2216
|
+
flushBuffer(emit) {
|
|
2217
|
+
const held = this.buffer;
|
|
2218
|
+
this.buffer = [];
|
|
2219
|
+
this.bufferedBytes = 0;
|
|
2220
|
+
for (const payload of held) {
|
|
2221
|
+
emit(payload);
|
|
2222
|
+
}
|
|
2105
2223
|
}
|
|
2106
2224
|
/**
|
|
2107
2225
|
* Flush and close the pretty transform, resolving once the stream has ended.
|
|
@@ -2110,6 +2228,7 @@ var PrettyDevDestination = class {
|
|
|
2110
2228
|
async onShutdown() {
|
|
2111
2229
|
const stream = this.stream;
|
|
2112
2230
|
if (stream === void 0) {
|
|
2231
|
+
this.flushBuffer(writeRawToStdout);
|
|
2113
2232
|
return;
|
|
2114
2233
|
}
|
|
2115
2234
|
await new Promise((resolve, reject) => {
|
package/dist/server/index.d.cts
CHANGED
|
@@ -864,28 +864,119 @@ declare class DefaultStdoutDestination implements ILogDestination {
|
|
|
864
864
|
});
|
|
865
865
|
/**
|
|
866
866
|
* Write a serialized JSON log entry (terminated with `\n`) to `stdout`.
|
|
867
|
+
*
|
|
868
|
+
* Routed through {@link writeStdoutSafely} rather than calling
|
|
869
|
+
* `process.stdout.write` directly. This is the sink a consumer gets when they
|
|
870
|
+
* configure nothing, so it is the path that decides whether a closed pipe
|
|
871
|
+
* (`node app | head`) kills the application — and unguarded, it did: EPIPE
|
|
872
|
+
* arrives asynchronously through the stream's `'error'` event, Node attaches no
|
|
873
|
+
* default handler, and the emit becomes an uncaught exception. Measured on the
|
|
874
|
+
* built artifact, exit code 42.
|
|
867
875
|
*/
|
|
868
876
|
write(payload: string): void;
|
|
869
877
|
}
|
|
870
878
|
|
|
879
|
+
/**
|
|
880
|
+
* How the development terminal renders an entry — a VIEW over the record, never
|
|
881
|
+
* a change to it. Every field maps to the `pino-pretty` option of the same name.
|
|
882
|
+
*
|
|
883
|
+
* Declared here rather than reusing `pino-pretty`'s own `PrettyOptions`, and the
|
|
884
|
+
* reason is load-bearing: a type import from the peer emits a hard
|
|
885
|
+
* `import { PrettyOptions } from 'pino-pretty'` at the top of the published
|
|
886
|
+
* `.d.ts`, so every consumer who type-checks WITHOUT the optional peer installed
|
|
887
|
+
* would hit an unresolved module. That would make an optional peer effectively
|
|
888
|
+
* required — a worse regression than the missing options, and the same
|
|
889
|
+
* contradiction this library already refused when asked to hard-fail on a missing
|
|
890
|
+
* peer. Measured on the built artifact, not assumed.
|
|
891
|
+
*
|
|
892
|
+
* `destination` is deliberately absent. The library owns where entries go; a
|
|
893
|
+
* consumer redirecting the stream would route around the multistream fan-out and
|
|
894
|
+
* the last-resort rescue. Omitting it from this type states the intent — applying
|
|
895
|
+
* the library's own value AFTER the spread in {@link PrettyDevDestination.onInit}
|
|
896
|
+
* is what enforces it, including against an untyped JavaScript caller.
|
|
897
|
+
*
|
|
898
|
+
* @example
|
|
899
|
+
* // One line per entry, with the fields a project repeats on every line hidden.
|
|
900
|
+
* const view: PrettyViewOptions = {
|
|
901
|
+
* singleLine: true,
|
|
902
|
+
* ignore: 'pid,hostname,service,deployment,event\\.name'
|
|
903
|
+
* }
|
|
904
|
+
*/
|
|
905
|
+
interface PrettyViewOptions {
|
|
906
|
+
/**
|
|
907
|
+
* Collapse the whole entry onto one line. The single biggest change to how a
|
|
908
|
+
* terminal reads — `false` is what expands one entry into a block.
|
|
909
|
+
* Default: `false`.
|
|
910
|
+
*/
|
|
911
|
+
singleLine?: boolean;
|
|
912
|
+
/**
|
|
913
|
+
* Comma-separated field names to hide from the rendered line. Display-only:
|
|
914
|
+
* what a real sink receives is untouched. Extend rather than replace when the
|
|
915
|
+
* goal is quieter output — the default already hides the noisiest fields.
|
|
916
|
+
* Default: `'pid,hostname,service'`.
|
|
917
|
+
*
|
|
918
|
+
* **A dot means "nested path", so a field name CONTAINING a dot has to escape
|
|
919
|
+
* it.** This library emits `event.name` as a literal top-level key, so the
|
|
920
|
+
* obvious spelling silently hides nothing — `pino-pretty` looks for an `event`
|
|
921
|
+
* object with a `name` inside and finds neither. Measured on one entry, only
|
|
922
|
+
* the option changing:
|
|
923
|
+
*
|
|
924
|
+
* ```
|
|
925
|
+
* ignore: '…,event.name' → INFO: msg {"logKey":"X","event.name":"x"} ← still there
|
|
926
|
+
* ignore: '…,event\\.name' → INFO: msg {"logKey":"X"} ← hidden
|
|
927
|
+
* ```
|
|
928
|
+
*
|
|
929
|
+
* In a TypeScript string literal that is `'event\\.name'` — one real backslash
|
|
930
|
+
* reaching `pino-pretty`. Nested objects (`service`, `deployment`) need no
|
|
931
|
+
* escape; they really are paths.
|
|
932
|
+
*/
|
|
933
|
+
ignore?: string;
|
|
934
|
+
/**
|
|
935
|
+
* Render only the message line, hiding the record entirely. In pretty mode
|
|
936
|
+
* there is no JSON copy behind it (`destinations` REPLACES stdout), so a field
|
|
937
|
+
* hidden here is not visible anywhere — including `logKey`. That is the point
|
|
938
|
+
* of the option rather than a caveat; use `messageFormat` to pull a specific
|
|
939
|
+
* field back into the line. Default: `false`.
|
|
940
|
+
*/
|
|
941
|
+
hideObject?: boolean;
|
|
942
|
+
/**
|
|
943
|
+
* Template for the message line, e.g. `'[{context}] {msg}'`. The way to keep a
|
|
944
|
+
* chosen field visible when `hideObject` is on.
|
|
945
|
+
*/
|
|
946
|
+
messageFormat?: string;
|
|
947
|
+
/** Timestamp format, or `false` to leave it raw. Default: `'SYS:HH:MM:ss.l'`. */
|
|
948
|
+
translateTime?: string | boolean;
|
|
949
|
+
/** ANSI colour in the rendered line. Default: `true`. */
|
|
950
|
+
colorize?: boolean;
|
|
951
|
+
}
|
|
871
952
|
/**
|
|
872
953
|
* Pretty-print destination backed by `pino-pretty`.
|
|
873
954
|
*
|
|
874
955
|
* Lifecycle contract (driven by `DestinationRegistry`):
|
|
875
|
-
* 1. `onInit()` resolves `pino-pretty
|
|
876
|
-
* `process.stdout`).
|
|
956
|
+
* 1. `onInit()` resolves `pino-pretty`, builds the transform (rendering to
|
|
957
|
+
* `process.stdout`), and flushes anything written before it existed.
|
|
877
958
|
* 2. `write()` forwards each serialized line to the transform.
|
|
878
959
|
* 3. `onShutdown()` flushes and closes the transform.
|
|
879
960
|
*
|
|
880
961
|
* @example
|
|
881
|
-
*
|
|
882
|
-
*
|
|
883
|
-
*
|
|
962
|
+
* // Default view — unchanged by the options below.
|
|
963
|
+
* new PrettyDevDestination()
|
|
964
|
+
*
|
|
965
|
+
* // One line per entry, with this project's repeated fields hidden.
|
|
966
|
+
* new PrettyDevDestination({
|
|
967
|
+
* view: { singleLine: true, ignore: 'pid,hostname,service,deployment,event\\.name' }
|
|
968
|
+
* })
|
|
969
|
+
*
|
|
970
|
+
* // Message-only, with the context pulled back into the line.
|
|
971
|
+
* new PrettyDevDestination({
|
|
972
|
+
* view: { hideObject: true, messageFormat: '[{context}] {msg}' }
|
|
884
973
|
* })
|
|
885
974
|
*/
|
|
886
975
|
declare class PrettyDevDestination implements ILogDestination {
|
|
887
976
|
readonly name = "pretty-dev";
|
|
888
977
|
readonly minLevel?: LogLevel;
|
|
978
|
+
/** Consumer-chosen view, merged over {@link DEFAULT_VIEW} at init. */
|
|
979
|
+
private readonly view;
|
|
889
980
|
/**
|
|
890
981
|
* The `pino-pretty` transform stream. Undefined until {@link onInit} succeeds.
|
|
891
982
|
*/
|
|
@@ -893,16 +984,42 @@ declare class PrettyDevDestination implements ILogDestination {
|
|
|
893
984
|
/**
|
|
894
985
|
* Set when {@link onInit} fails (e.g. `pino-pretty` absent). Distinguishes a
|
|
895
986
|
* permanently-failed destination — whose writes are DROPPED, since the registry
|
|
896
|
-
* dropped it from the active set — from the transient pre-init window
|
|
897
|
-
* {@link write} falls back to raw stdout so a bootstrap log is not lost.
|
|
987
|
+
* dropped it from the active set — from the transient pre-init window.
|
|
898
988
|
*/
|
|
899
989
|
private initFailed;
|
|
990
|
+
/**
|
|
991
|
+
* Entries written before {@link onInit} built the transform, held so they can be
|
|
992
|
+
* RENDERED rather than printed raw.
|
|
993
|
+
*
|
|
994
|
+
* The transform cannot exist earlier: loading the optional peer is async, and
|
|
995
|
+
* `onInit` is the first moment the lifecycle offers. Before this buffer those
|
|
996
|
+
* entries went to stdout as raw NDJSON — correct, nothing lost, but a real boot
|
|
997
|
+
* put dozens of JSON lines on screen before the first rendered one, and a
|
|
998
|
+
* developer who had just turned pretty on reasonably concluded it had not
|
|
999
|
+
* worked. Reported from exactly that reading.
|
|
1000
|
+
*/
|
|
1001
|
+
private buffer;
|
|
1002
|
+
/**
|
|
1003
|
+
* Set once {@link MAX_BUFFERED_ENTRIES} is reached. From then on entries go
|
|
1004
|
+
* straight to raw stdout — and the buffer is drained raw at the same moment, so
|
|
1005
|
+
* the output stays in ORDER rather than replaying old entries after newer ones.
|
|
1006
|
+
*/
|
|
1007
|
+
private bufferOverflowed;
|
|
1008
|
+
/**
|
|
1009
|
+
* UTF-8 size of everything currently held, tracked incrementally so the bound
|
|
1010
|
+
* costs one `Buffer.byteLength` per entry rather than a walk of the buffer.
|
|
1011
|
+
*/
|
|
1012
|
+
private bufferedBytes;
|
|
900
1013
|
/**
|
|
901
1014
|
* @param opts.minLevel — Optional minimum level filter applied by the
|
|
902
1015
|
* destination registry's multi-stream wiring. Omit to accept every level.
|
|
1016
|
+
* @param opts.view — Optional rendering overrides; see {@link PrettyViewOptions}.
|
|
1017
|
+
* Omitted fields keep their default, so `new PrettyDevDestination()` renders
|
|
1018
|
+
* exactly as it did before this option existed.
|
|
903
1019
|
*/
|
|
904
1020
|
constructor(opts?: {
|
|
905
1021
|
minLevel?: LogLevel;
|
|
1022
|
+
view?: PrettyViewOptions;
|
|
906
1023
|
});
|
|
907
1024
|
/**
|
|
908
1025
|
* Lazily resolve `pino-pretty` and build the colorized transform that renders
|
|
@@ -921,12 +1038,24 @@ declare class PrettyDevDestination implements ILogDestination {
|
|
|
921
1038
|
* fan-out already skips a failed destination before reaching this method
|
|
922
1039
|
* (and rescues the entry when nothing else initialized); this branch is the
|
|
923
1040
|
* belt to that, for any caller holding the destination directly;
|
|
924
|
-
* - pre-init (transient window before {@link onInit}, e.g.
|
|
925
|
-
*
|
|
1041
|
+
* - pre-init (transient window before {@link onInit}, e.g. every entry NestJS
|
|
1042
|
+
* emits while instantiating providers) → BUFFERED, then rendered through
|
|
1043
|
+
* the transform once it exists. Nothing is lost on any path: if init fails
|
|
1044
|
+
* the buffer is drained raw, and if it overflows it is drained raw in order.
|
|
926
1045
|
*
|
|
927
1046
|
* @param payload — Newline-terminated JSON entry, UTF-8 encoded.
|
|
928
1047
|
*/
|
|
929
1048
|
write(payload: string): void;
|
|
1049
|
+
/**
|
|
1050
|
+
* Hand every buffered entry to `emit`, in arrival order, and empty the buffer.
|
|
1051
|
+
*
|
|
1052
|
+
* Emptying unconditionally is what makes this safe to call from both the
|
|
1053
|
+
* success and failure paths: whichever runs first owns the entries, and the
|
|
1054
|
+
* other finds nothing to emit twice.
|
|
1055
|
+
*
|
|
1056
|
+
* @param emit - Receives each buffered payload, oldest first.
|
|
1057
|
+
*/
|
|
1058
|
+
private flushBuffer;
|
|
930
1059
|
/**
|
|
931
1060
|
* Flush and close the pretty transform, resolving once the stream has ended.
|
|
932
1061
|
* A no-op when {@link onInit} never ran.
|
|
@@ -1382,4 +1511,4 @@ interface LogEntry {
|
|
|
1382
1511
|
*/
|
|
1383
1512
|
declare const LOG_KEYS_CONVENTION_REGEX: RegExp;
|
|
1384
1513
|
|
|
1385
|
-
export { BymaxLoggerModule, type BymaxLoggerModuleAsyncOptions, type BymaxLoggerModuleOptions, type BymaxLoggerModuleOptionsFactory, DEFAULT_REDACT_PATHS, DefaultStdoutDestination, type EmittedDeploymentResource, type EmittedServiceResource, HttpExceptionFilter, HttpLoggingInterceptor, type HttpOptions, type ILogDestination, type IncomingHeaders, InjectLogger, LOGGER_DESTINATIONS_TOKEN, LOGGER_OPTIONS_TOKEN, LOGGER_PINO_INSTANCE_TOKEN, LOG_CONTEXT_METADATA_KEY, LOG_CONTEXT_TOKEN, LOG_KEYS_CONVENTION_REGEX, LogContext, type LogContext$1 as LogContextBag, LogContextService, type LogEntry, type LogLevel, LogPerformance, type LoggableRequest, type LoggableResponse, type NextHandler, type OtelOptions, PINO_LEVEL_NAMES, PINO_LEVEL_NUMBERS, PinoLoggerService, PrettyDevDestination, RESERVED_LOG_KEYS, RESERVED_LOG_KEYS_NOT_EMITTED, RequestIdMiddleware, type ReservedLogKey, type ResolvedBymaxLoggerModuleOptions, type ResolvedServiceMetadata, type ServiceMetadata, applyRequestIdMiddleware };
|
|
1514
|
+
export { BymaxLoggerModule, type BymaxLoggerModuleAsyncOptions, type BymaxLoggerModuleOptions, type BymaxLoggerModuleOptionsFactory, DEFAULT_REDACT_PATHS, DefaultStdoutDestination, type EmittedDeploymentResource, type EmittedServiceResource, HttpExceptionFilter, HttpLoggingInterceptor, type HttpOptions, type ILogDestination, type IncomingHeaders, InjectLogger, LOGGER_DESTINATIONS_TOKEN, LOGGER_OPTIONS_TOKEN, LOGGER_PINO_INSTANCE_TOKEN, LOG_CONTEXT_METADATA_KEY, LOG_CONTEXT_TOKEN, LOG_KEYS_CONVENTION_REGEX, LogContext, type LogContext$1 as LogContextBag, LogContextService, type LogEntry, type LogLevel, LogPerformance, type LoggableRequest, type LoggableResponse, type NextHandler, type OtelOptions, PINO_LEVEL_NAMES, PINO_LEVEL_NUMBERS, PinoLoggerService, PrettyDevDestination, type PrettyViewOptions, RESERVED_LOG_KEYS, RESERVED_LOG_KEYS_NOT_EMITTED, RequestIdMiddleware, type ReservedLogKey, type ResolvedBymaxLoggerModuleOptions, type ResolvedServiceMetadata, type ServiceMetadata, applyRequestIdMiddleware };
|