@bymax-one/nest-logger 1.2.4 → 1.2.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 +211 -1
- package/README.md +66 -10
- package/dist/server/index.cjs +204 -87
- package/dist/server/index.d.cts +124 -10
- package/dist/server/index.d.ts +124 -10
- package/dist/server/index.mjs +204 -87
- package/package.json +1 -1
package/dist/server/index.cjs
CHANGED
|
@@ -228,6 +228,75 @@ function escapeControlCharacters(text) {
|
|
|
228
228
|
return text.replace(UNSAFE_IN_MULTILINE, toEscapeSequence);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
// src/server/utils/sanitize-error.util.ts
|
|
232
|
+
var DEFAULT_MAX_CAUSE_DEPTH = 3;
|
|
233
|
+
var MAX_AGGREGATE_ERRORS = 10;
|
|
234
|
+
var CIRCULAR_SENTINEL = "[Circular]";
|
|
235
|
+
function sanitizeError(err, options) {
|
|
236
|
+
const maxCauseDepth = DEFAULT_MAX_CAUSE_DEPTH;
|
|
237
|
+
return toSanitizedError(err, /* @__PURE__ */ new WeakSet(), 0, maxCauseDepth);
|
|
238
|
+
}
|
|
239
|
+
function isErrorLike(value) {
|
|
240
|
+
if (typeof value !== "object" || value === null) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
const candidate = value;
|
|
244
|
+
return typeof candidate.name === "string" && typeof candidate.message === "string";
|
|
245
|
+
}
|
|
246
|
+
function toSanitizedError(value, seen, depth2, maxDepth) {
|
|
247
|
+
try {
|
|
248
|
+
if (!isErrorLike(value)) {
|
|
249
|
+
return { name: "UnknownError", message: String(value) };
|
|
250
|
+
}
|
|
251
|
+
seen.add(value);
|
|
252
|
+
const result = { name: value.name, message: value.message };
|
|
253
|
+
if (value.stack !== void 0) {
|
|
254
|
+
result.stack = scrubStack(value.stack);
|
|
255
|
+
}
|
|
256
|
+
const isAtDepthLimit = depth2 >= maxDepth;
|
|
257
|
+
if (value.cause !== void 0) {
|
|
258
|
+
result.cause = isAtDepthLimit ? createDepthMarker() : toSanitizedChild(value.cause, seen, depth2 + 1, maxDepth);
|
|
259
|
+
}
|
|
260
|
+
const aggregated = readAggregatedErrors(value);
|
|
261
|
+
if (aggregated !== void 0) {
|
|
262
|
+
const capped = aggregated.slice(0, MAX_AGGREGATE_ERRORS);
|
|
263
|
+
result.errors = capped.map(
|
|
264
|
+
(inner) => isAtDepthLimit ? createDepthMarker() : toSanitizedChild(inner, seen, depth2 + 1, maxDepth)
|
|
265
|
+
);
|
|
266
|
+
if (aggregated.length > MAX_AGGREGATE_ERRORS) {
|
|
267
|
+
result.errors.push(createWidthMarker(aggregated.length - MAX_AGGREGATE_ERRORS));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return result;
|
|
271
|
+
} catch {
|
|
272
|
+
return { name: "SanitizeFailed", message: "Failed to sanitize the thrown value" };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function toSanitizedChild(value, seen, depth2, maxDepth) {
|
|
276
|
+
if (isObject(value) && seen.has(value)) {
|
|
277
|
+
return CIRCULAR_SENTINEL;
|
|
278
|
+
}
|
|
279
|
+
return toSanitizedError(value, seen, depth2, maxDepth);
|
|
280
|
+
}
|
|
281
|
+
function readAggregatedErrors(value) {
|
|
282
|
+
const candidate = value.errors;
|
|
283
|
+
return Array.isArray(candidate) ? candidate : void 0;
|
|
284
|
+
}
|
|
285
|
+
function scrubStack(stack) {
|
|
286
|
+
return escapeControlCharacters(
|
|
287
|
+
stack.split("\n").filter((line) => !line.includes("node_modules")).join("\n")
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
function isObject(value) {
|
|
291
|
+
return typeof value === "object" && value !== null;
|
|
292
|
+
}
|
|
293
|
+
function createDepthMarker() {
|
|
294
|
+
return { _truncated: true, _reason: "cause-depth-exceeded" };
|
|
295
|
+
}
|
|
296
|
+
function createWidthMarker(omitted) {
|
|
297
|
+
return { _truncated: true, _reason: "aggregate-width-exceeded", _omitted: omitted };
|
|
298
|
+
}
|
|
299
|
+
|
|
231
300
|
// src/server/services/pino-logger.service.ts
|
|
232
301
|
var OWNED_PAYLOAD_KEYS = ["logKey", "userId", "context"];
|
|
233
302
|
function withoutOwnedKeys(metadata) {
|
|
@@ -256,6 +325,9 @@ function safeErrorMessage(error) {
|
|
|
256
325
|
return "Unreadable error message";
|
|
257
326
|
}
|
|
258
327
|
}
|
|
328
|
+
function findErrorParam(optionalParams) {
|
|
329
|
+
return optionalParams.find((param) => isErrorLike(param));
|
|
330
|
+
}
|
|
259
331
|
function assignIfDefined(payload, key, value) {
|
|
260
332
|
if (value !== void 0) {
|
|
261
333
|
Reflect.defineProperty(payload, key, { value, enumerable: true });
|
|
@@ -292,7 +364,7 @@ exports.PinoLoggerService = class PinoLoggerService {
|
|
|
292
364
|
* @param optionalParams - NestJS trailing params (`stack` at index 0, `context` at index 1).
|
|
293
365
|
*/
|
|
294
366
|
error(message, ...optionalParams) {
|
|
295
|
-
if (message
|
|
367
|
+
if (isErrorLike(message)) {
|
|
296
368
|
const payload2 = { err: message };
|
|
297
369
|
assignIfDefined(payload2, "context", this.resolveContext(optionalParams));
|
|
298
370
|
this.pino.error(payload2, safeErrorMessage(message));
|
|
@@ -303,6 +375,7 @@ exports.PinoLoggerService = class PinoLoggerService {
|
|
|
303
375
|
const payload = {};
|
|
304
376
|
assignIfDefined(payload, "context", context);
|
|
305
377
|
assignIfDefined(payload, "stack", stack);
|
|
378
|
+
assignIfDefined(payload, "err", findErrorParam(optionalParams));
|
|
306
379
|
this.pino.error(
|
|
307
380
|
payload,
|
|
308
381
|
toSingleLineMessage(typeof message === "string" ? message : String(message))
|
|
@@ -461,7 +534,9 @@ exports.PinoLoggerService = class PinoLoggerService {
|
|
|
461
534
|
emitNestStyle(level, message, optionalParams) {
|
|
462
535
|
const payload = {};
|
|
463
536
|
assignIfDefined(payload, "context", this.resolveContext(optionalParams));
|
|
464
|
-
const
|
|
537
|
+
const leading = isErrorLike(message) ? message : void 0;
|
|
538
|
+
assignIfDefined(payload, "err", leading ?? findErrorParam(optionalParams));
|
|
539
|
+
const msg = leading === void 0 ? toSingleLineMessage(typeof message === "string" ? message : String(message)) : safeErrorMessage(leading);
|
|
465
540
|
switch (level) {
|
|
466
541
|
case "info":
|
|
467
542
|
this.pino.info(payload, msg);
|
|
@@ -511,6 +586,30 @@ function collectContextLoggerProviders() {
|
|
|
511
586
|
function collectContextLoggerTokens() {
|
|
512
587
|
return Array.from(contextTokens.values());
|
|
513
588
|
}
|
|
589
|
+
|
|
590
|
+
// src/server/utils/safe-stdio.util.ts
|
|
591
|
+
var swallowStreamError = () => void 0;
|
|
592
|
+
function ensureGuarded(stream) {
|
|
593
|
+
if (stream.listeners("error").includes(swallowStreamError)) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
stream.on("error", swallowStreamError);
|
|
597
|
+
}
|
|
598
|
+
function writeSafely(stream, payload) {
|
|
599
|
+
try {
|
|
600
|
+
ensureGuarded(stream);
|
|
601
|
+
stream.write(payload);
|
|
602
|
+
} catch {
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function writeStdoutSafely(payload) {
|
|
606
|
+
writeSafely(process.stdout, payload);
|
|
607
|
+
}
|
|
608
|
+
function writeStderrSafely(line) {
|
|
609
|
+
writeSafely(process.stderr, line);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// src/server/destinations/default-stdout.destination.ts
|
|
514
613
|
exports.DefaultStdoutDestination = class DefaultStdoutDestination {
|
|
515
614
|
/**
|
|
516
615
|
* @param opts.minLevel — Optional minimum level filter. Entries below this
|
|
@@ -525,9 +624,17 @@ exports.DefaultStdoutDestination = class DefaultStdoutDestination {
|
|
|
525
624
|
}
|
|
526
625
|
/**
|
|
527
626
|
* Write a serialized JSON log entry (terminated with `\n`) to `stdout`.
|
|
627
|
+
*
|
|
628
|
+
* Routed through {@link writeStdoutSafely} rather than calling
|
|
629
|
+
* `process.stdout.write` directly. This is the sink a consumer gets when they
|
|
630
|
+
* configure nothing, so it is the path that decides whether a closed pipe
|
|
631
|
+
* (`node app | head`) kills the application — and unguarded, it did: EPIPE
|
|
632
|
+
* arrives asynchronously through the stream's `'error'` event, Node attaches no
|
|
633
|
+
* default handler, and the emit becomes an uncaught exception. Measured on the
|
|
634
|
+
* built artifact, exit code 42.
|
|
528
635
|
*/
|
|
529
636
|
write(payload) {
|
|
530
|
-
|
|
637
|
+
writeStdoutSafely(payload);
|
|
531
638
|
}
|
|
532
639
|
};
|
|
533
640
|
exports.DefaultStdoutDestination = __decorateClass([
|
|
@@ -548,75 +655,6 @@ function stripQueryString(url) {
|
|
|
548
655
|
return queryStart === -1 ? url : url.slice(0, queryStart);
|
|
549
656
|
}
|
|
550
657
|
|
|
551
|
-
// src/server/utils/sanitize-error.util.ts
|
|
552
|
-
var DEFAULT_MAX_CAUSE_DEPTH = 3;
|
|
553
|
-
var MAX_AGGREGATE_ERRORS = 10;
|
|
554
|
-
var CIRCULAR_SENTINEL = "[Circular]";
|
|
555
|
-
function sanitizeError(err, options) {
|
|
556
|
-
const maxCauseDepth = DEFAULT_MAX_CAUSE_DEPTH;
|
|
557
|
-
return toSanitizedError(err, /* @__PURE__ */ new WeakSet(), 0, maxCauseDepth);
|
|
558
|
-
}
|
|
559
|
-
function isErrorLike(value) {
|
|
560
|
-
if (typeof value !== "object" || value === null) {
|
|
561
|
-
return false;
|
|
562
|
-
}
|
|
563
|
-
const candidate = value;
|
|
564
|
-
return typeof candidate.name === "string" && typeof candidate.message === "string";
|
|
565
|
-
}
|
|
566
|
-
function toSanitizedError(value, seen, depth2, maxDepth) {
|
|
567
|
-
try {
|
|
568
|
-
if (!isErrorLike(value)) {
|
|
569
|
-
return { name: "UnknownError", message: String(value) };
|
|
570
|
-
}
|
|
571
|
-
seen.add(value);
|
|
572
|
-
const result = { name: value.name, message: value.message };
|
|
573
|
-
if (value.stack !== void 0) {
|
|
574
|
-
result.stack = scrubStack(value.stack);
|
|
575
|
-
}
|
|
576
|
-
const isAtDepthLimit = depth2 >= maxDepth;
|
|
577
|
-
if (value.cause !== void 0) {
|
|
578
|
-
result.cause = isAtDepthLimit ? createDepthMarker() : toSanitizedChild(value.cause, seen, depth2 + 1, maxDepth);
|
|
579
|
-
}
|
|
580
|
-
const aggregated = readAggregatedErrors(value);
|
|
581
|
-
if (aggregated !== void 0) {
|
|
582
|
-
const capped = aggregated.slice(0, MAX_AGGREGATE_ERRORS);
|
|
583
|
-
result.errors = capped.map(
|
|
584
|
-
(inner) => isAtDepthLimit ? createDepthMarker() : toSanitizedChild(inner, seen, depth2 + 1, maxDepth)
|
|
585
|
-
);
|
|
586
|
-
if (aggregated.length > MAX_AGGREGATE_ERRORS) {
|
|
587
|
-
result.errors.push(createWidthMarker(aggregated.length - MAX_AGGREGATE_ERRORS));
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
return result;
|
|
591
|
-
} catch {
|
|
592
|
-
return { name: "SanitizeFailed", message: "Failed to sanitize the thrown value" };
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
function toSanitizedChild(value, seen, depth2, maxDepth) {
|
|
596
|
-
if (isObject(value) && seen.has(value)) {
|
|
597
|
-
return CIRCULAR_SENTINEL;
|
|
598
|
-
}
|
|
599
|
-
return toSanitizedError(value, seen, depth2, maxDepth);
|
|
600
|
-
}
|
|
601
|
-
function readAggregatedErrors(value) {
|
|
602
|
-
const candidate = value.errors;
|
|
603
|
-
return Array.isArray(candidate) ? candidate : void 0;
|
|
604
|
-
}
|
|
605
|
-
function scrubStack(stack) {
|
|
606
|
-
return escapeControlCharacters(
|
|
607
|
-
stack.split("\n").filter((line) => !line.includes("node_modules")).join("\n")
|
|
608
|
-
);
|
|
609
|
-
}
|
|
610
|
-
function isObject(value) {
|
|
611
|
-
return typeof value === "object" && value !== null;
|
|
612
|
-
}
|
|
613
|
-
function createDepthMarker() {
|
|
614
|
-
return { _truncated: true, _reason: "cause-depth-exceeded" };
|
|
615
|
-
}
|
|
616
|
-
function createWidthMarker(omitted) {
|
|
617
|
-
return { _truncated: true, _reason: "aggregate-width-exceeded", _omitted: omitted };
|
|
618
|
-
}
|
|
619
|
-
|
|
620
658
|
// src/server/filters/http-exception.filter.ts
|
|
621
659
|
var INTERNAL_ERROR_BODY = {
|
|
622
660
|
statusCode: common.HttpStatus.INTERNAL_SERVER_ERROR,
|
|
@@ -1028,7 +1066,7 @@ function reportDestinationFailure(logKey, name, cause, msg) {
|
|
|
1028
1066
|
type: toSingleLineMessage(String(cause.name)),
|
|
1029
1067
|
message: toSingleLineMessage(String(cause.message))
|
|
1030
1068
|
} : { type: "UnknownError", message: toSingleLineMessage(String(cause)) };
|
|
1031
|
-
|
|
1069
|
+
writeStderrSafely(
|
|
1032
1070
|
JSON.stringify({
|
|
1033
1071
|
level: "error",
|
|
1034
1072
|
logKey,
|
|
@@ -1051,10 +1089,7 @@ function reportWriteFailure(name, cause) {
|
|
|
1051
1089
|
);
|
|
1052
1090
|
}
|
|
1053
1091
|
function rescueToStdout(payload) {
|
|
1054
|
-
|
|
1055
|
-
process.stdout.write(payload);
|
|
1056
|
-
} catch {
|
|
1057
|
-
}
|
|
1092
|
+
writeStdoutSafely(payload);
|
|
1058
1093
|
}
|
|
1059
1094
|
function destinationToStream(destination, health) {
|
|
1060
1095
|
return new stream.Writable({
|
|
@@ -1751,7 +1786,7 @@ var DestinationRegistry = class {
|
|
|
1751
1786
|
await destination.onShutdown?.();
|
|
1752
1787
|
} catch (cause) {
|
|
1753
1788
|
const detail = cause instanceof Error ? cause.stack ?? cause.message : String(cause);
|
|
1754
|
-
|
|
1789
|
+
writeStderrSafely(
|
|
1755
1790
|
`[DestinationRegistry] Shutdown failed for "${destination.name}": ${detail}
|
|
1756
1791
|
`
|
|
1757
1792
|
);
|
|
@@ -2031,23 +2066,60 @@ exports.BymaxLoggerModule = __decorateClass([
|
|
|
2031
2066
|
], exports.BymaxLoggerModule);
|
|
2032
2067
|
|
|
2033
2068
|
// src/server/destinations/pretty-dev.destination.ts
|
|
2069
|
+
var DEFAULT_VIEW = {
|
|
2070
|
+
colorize: true,
|
|
2071
|
+
translateTime: "SYS:HH:MM:ss.l",
|
|
2072
|
+
ignore: "pid,hostname,service",
|
|
2073
|
+
singleLine: false
|
|
2074
|
+
};
|
|
2075
|
+
var MAX_BUFFERED_ENTRIES = 1e3;
|
|
2076
|
+
var MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
2077
|
+
function writeRawToStdout(payload) {
|
|
2078
|
+
writeStdoutSafely(payload);
|
|
2079
|
+
}
|
|
2034
2080
|
var PrettyDevDestination = class {
|
|
2035
2081
|
/**
|
|
2036
2082
|
* @param opts.minLevel — Optional minimum level filter applied by the
|
|
2037
2083
|
* destination registry's multi-stream wiring. Omit to accept every level.
|
|
2084
|
+
* @param opts.view — Optional rendering overrides; see {@link PrettyViewOptions}.
|
|
2085
|
+
* Omitted fields keep their default, so `new PrettyDevDestination()` renders
|
|
2086
|
+
* exactly as it did before this option existed.
|
|
2038
2087
|
*/
|
|
2039
2088
|
constructor(opts = {}) {
|
|
2040
2089
|
this.name = "pretty-dev";
|
|
2041
2090
|
/**
|
|
2042
2091
|
* Set when {@link onInit} fails (e.g. `pino-pretty` absent). Distinguishes a
|
|
2043
2092
|
* permanently-failed destination — whose writes are DROPPED, since the registry
|
|
2044
|
-
* dropped it from the active set — from the transient pre-init window
|
|
2045
|
-
* {@link write} falls back to raw stdout so a bootstrap log is not lost.
|
|
2093
|
+
* dropped it from the active set — from the transient pre-init window.
|
|
2046
2094
|
*/
|
|
2047
2095
|
this.initFailed = false;
|
|
2096
|
+
/**
|
|
2097
|
+
* Entries written before {@link onInit} built the transform, held so they can be
|
|
2098
|
+
* RENDERED rather than printed raw.
|
|
2099
|
+
*
|
|
2100
|
+
* The transform cannot exist earlier: loading the optional peer is async, and
|
|
2101
|
+
* `onInit` is the first moment the lifecycle offers. Before this buffer those
|
|
2102
|
+
* entries went to stdout as raw NDJSON — correct, nothing lost, but a real boot
|
|
2103
|
+
* put dozens of JSON lines on screen before the first rendered one, and a
|
|
2104
|
+
* developer who had just turned pretty on reasonably concluded it had not
|
|
2105
|
+
* worked. Reported from exactly that reading.
|
|
2106
|
+
*/
|
|
2107
|
+
this.buffer = [];
|
|
2108
|
+
/**
|
|
2109
|
+
* Set once {@link MAX_BUFFERED_ENTRIES} is reached. From then on entries go
|
|
2110
|
+
* straight to raw stdout — and the buffer is drained raw at the same moment, so
|
|
2111
|
+
* the output stays in ORDER rather than replaying old entries after newer ones.
|
|
2112
|
+
*/
|
|
2113
|
+
this.bufferOverflowed = false;
|
|
2114
|
+
/**
|
|
2115
|
+
* UTF-8 size of everything currently held, tracked incrementally so the bound
|
|
2116
|
+
* costs one `Buffer.byteLength` per entry rather than a walk of the buffer.
|
|
2117
|
+
*/
|
|
2118
|
+
this.bufferedBytes = 0;
|
|
2048
2119
|
if (opts.minLevel !== void 0) {
|
|
2049
2120
|
this.minLevel = opts.minLevel;
|
|
2050
2121
|
}
|
|
2122
|
+
this.view = opts.view ?? {};
|
|
2051
2123
|
}
|
|
2052
2124
|
/**
|
|
2053
2125
|
* Lazily resolve `pino-pretty` and build the colorized transform that renders
|
|
@@ -2060,18 +2132,31 @@ var PrettyDevDestination = class {
|
|
|
2060
2132
|
try {
|
|
2061
2133
|
const { build } = await import('pino-pretty');
|
|
2062
2134
|
this.stream = build({
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2135
|
+
...DEFAULT_VIEW,
|
|
2136
|
+
...this.view,
|
|
2137
|
+
// AFTER the consumer's view, deliberately. Omitting `destination` from
|
|
2138
|
+
// `PrettyViewOptions` states that the library owns the sink; applying it
|
|
2139
|
+
// last is what ENFORCES it, including against an untyped caller reaching
|
|
2140
|
+
// in from JavaScript. A redirected stream would route around the fan-out
|
|
2141
|
+
// and the last-resort rescue.
|
|
2067
2142
|
destination: process.stdout
|
|
2068
2143
|
});
|
|
2069
2144
|
} catch {
|
|
2070
2145
|
this.initFailed = true;
|
|
2146
|
+
this.flushBuffer((payload) => {
|
|
2147
|
+
writeRawToStdout(payload);
|
|
2148
|
+
});
|
|
2071
2149
|
throw new Error(
|
|
2072
2150
|
"[PrettyDevDestination] pino-pretty is not installed. Install it as a peer dependency (`pnpm add -D pino-pretty`) or remove PrettyDevDestination from `destinations`."
|
|
2073
2151
|
);
|
|
2074
2152
|
}
|
|
2153
|
+
this.flushBuffer((payload) => {
|
|
2154
|
+
try {
|
|
2155
|
+
this.stream?.write(payload);
|
|
2156
|
+
} catch {
|
|
2157
|
+
writeRawToStdout(payload);
|
|
2158
|
+
}
|
|
2159
|
+
});
|
|
2075
2160
|
}
|
|
2076
2161
|
/**
|
|
2077
2162
|
* Write a serialized NDJSON entry to the pretty transform.
|
|
@@ -2082,8 +2167,10 @@ var PrettyDevDestination = class {
|
|
|
2082
2167
|
* fan-out already skips a failed destination before reaching this method
|
|
2083
2168
|
* (and rescues the entry when nothing else initialized); this branch is the
|
|
2084
2169
|
* belt to that, for any caller holding the destination directly;
|
|
2085
|
-
* - pre-init (transient window before {@link onInit}, e.g.
|
|
2086
|
-
*
|
|
2170
|
+
* - pre-init (transient window before {@link onInit}, e.g. every entry NestJS
|
|
2171
|
+
* emits while instantiating providers) → BUFFERED, then rendered through
|
|
2172
|
+
* the transform once it exists. Nothing is lost on any path: if init fails
|
|
2173
|
+
* the buffer is drained raw, and if it overflows it is drained raw in order.
|
|
2087
2174
|
*
|
|
2088
2175
|
* @param payload — Newline-terminated JSON entry, UTF-8 encoded.
|
|
2089
2176
|
*/
|
|
@@ -2095,7 +2182,36 @@ var PrettyDevDestination = class {
|
|
|
2095
2182
|
if (this.initFailed) {
|
|
2096
2183
|
return;
|
|
2097
2184
|
}
|
|
2098
|
-
|
|
2185
|
+
if (this.bufferOverflowed) {
|
|
2186
|
+
writeRawToStdout(payload);
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
const payloadBytes = Buffer.byteLength(payload, "utf8");
|
|
2190
|
+
if (this.buffer.length >= MAX_BUFFERED_ENTRIES || this.bufferedBytes + payloadBytes > MAX_BUFFERED_BYTES) {
|
|
2191
|
+
this.bufferOverflowed = true;
|
|
2192
|
+
this.flushBuffer(writeRawToStdout);
|
|
2193
|
+
writeRawToStdout(payload);
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
this.buffer.push(payload);
|
|
2197
|
+
this.bufferedBytes += payloadBytes;
|
|
2198
|
+
}
|
|
2199
|
+
/**
|
|
2200
|
+
* Hand every buffered entry to `emit`, in arrival order, and empty the buffer.
|
|
2201
|
+
*
|
|
2202
|
+
* Emptying unconditionally is what makes this safe to call from both the
|
|
2203
|
+
* success and failure paths: whichever runs first owns the entries, and the
|
|
2204
|
+
* other finds nothing to emit twice.
|
|
2205
|
+
*
|
|
2206
|
+
* @param emit - Receives each buffered payload, oldest first.
|
|
2207
|
+
*/
|
|
2208
|
+
flushBuffer(emit) {
|
|
2209
|
+
const held = this.buffer;
|
|
2210
|
+
this.buffer = [];
|
|
2211
|
+
this.bufferedBytes = 0;
|
|
2212
|
+
for (const payload of held) {
|
|
2213
|
+
emit(payload);
|
|
2214
|
+
}
|
|
2099
2215
|
}
|
|
2100
2216
|
/**
|
|
2101
2217
|
* Flush and close the pretty transform, resolving once the stream has ended.
|
|
@@ -2104,6 +2220,7 @@ var PrettyDevDestination = class {
|
|
|
2104
2220
|
async onShutdown() {
|
|
2105
2221
|
const stream = this.stream;
|
|
2106
2222
|
if (stream === void 0) {
|
|
2223
|
+
this.flushBuffer(writeRawToStdout);
|
|
2107
2224
|
return;
|
|
2108
2225
|
}
|
|
2109
2226
|
await new Promise((resolve, reject) => {
|
package/dist/server/index.d.cts
CHANGED
|
@@ -864,28 +864,104 @@ 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
|
+
ignore?: string;
|
|
919
|
+
/**
|
|
920
|
+
* Render only the message line, hiding the record entirely. In pretty mode
|
|
921
|
+
* there is no JSON copy behind it (`destinations` REPLACES stdout), so a field
|
|
922
|
+
* hidden here is not visible anywhere — including `logKey`. That is the point
|
|
923
|
+
* of the option rather than a caveat; use `messageFormat` to pull a specific
|
|
924
|
+
* field back into the line. Default: `false`.
|
|
925
|
+
*/
|
|
926
|
+
hideObject?: boolean;
|
|
927
|
+
/**
|
|
928
|
+
* Template for the message line, e.g. `'[{context}] {msg}'`. The way to keep a
|
|
929
|
+
* chosen field visible when `hideObject` is on.
|
|
930
|
+
*/
|
|
931
|
+
messageFormat?: string;
|
|
932
|
+
/** Timestamp format, or `false` to leave it raw. Default: `'SYS:HH:MM:ss.l'`. */
|
|
933
|
+
translateTime?: string | boolean;
|
|
934
|
+
/** ANSI colour in the rendered line. Default: `true`. */
|
|
935
|
+
colorize?: boolean;
|
|
936
|
+
}
|
|
871
937
|
/**
|
|
872
938
|
* Pretty-print destination backed by `pino-pretty`.
|
|
873
939
|
*
|
|
874
940
|
* Lifecycle contract (driven by `DestinationRegistry`):
|
|
875
|
-
* 1. `onInit()` resolves `pino-pretty
|
|
876
|
-
* `process.stdout`).
|
|
941
|
+
* 1. `onInit()` resolves `pino-pretty`, builds the transform (rendering to
|
|
942
|
+
* `process.stdout`), and flushes anything written before it existed.
|
|
877
943
|
* 2. `write()` forwards each serialized line to the transform.
|
|
878
944
|
* 3. `onShutdown()` flushes and closes the transform.
|
|
879
945
|
*
|
|
880
946
|
* @example
|
|
881
|
-
*
|
|
882
|
-
*
|
|
883
|
-
*
|
|
947
|
+
* // Default view — unchanged by the options below.
|
|
948
|
+
* new PrettyDevDestination()
|
|
949
|
+
*
|
|
950
|
+
* // One line per entry, with this project's repeated fields hidden.
|
|
951
|
+
* new PrettyDevDestination({
|
|
952
|
+
* view: { singleLine: true, ignore: 'pid,hostname,service,deployment,event.name' }
|
|
953
|
+
* })
|
|
954
|
+
*
|
|
955
|
+
* // Message-only, with the context pulled back into the line.
|
|
956
|
+
* new PrettyDevDestination({
|
|
957
|
+
* view: { hideObject: true, messageFormat: '[{context}] {msg}' }
|
|
884
958
|
* })
|
|
885
959
|
*/
|
|
886
960
|
declare class PrettyDevDestination implements ILogDestination {
|
|
887
961
|
readonly name = "pretty-dev";
|
|
888
962
|
readonly minLevel?: LogLevel;
|
|
963
|
+
/** Consumer-chosen view, merged over {@link DEFAULT_VIEW} at init. */
|
|
964
|
+
private readonly view;
|
|
889
965
|
/**
|
|
890
966
|
* The `pino-pretty` transform stream. Undefined until {@link onInit} succeeds.
|
|
891
967
|
*/
|
|
@@ -893,16 +969,42 @@ declare class PrettyDevDestination implements ILogDestination {
|
|
|
893
969
|
/**
|
|
894
970
|
* Set when {@link onInit} fails (e.g. `pino-pretty` absent). Distinguishes a
|
|
895
971
|
* 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.
|
|
972
|
+
* dropped it from the active set — from the transient pre-init window.
|
|
898
973
|
*/
|
|
899
974
|
private initFailed;
|
|
975
|
+
/**
|
|
976
|
+
* Entries written before {@link onInit} built the transform, held so they can be
|
|
977
|
+
* RENDERED rather than printed raw.
|
|
978
|
+
*
|
|
979
|
+
* The transform cannot exist earlier: loading the optional peer is async, and
|
|
980
|
+
* `onInit` is the first moment the lifecycle offers. Before this buffer those
|
|
981
|
+
* entries went to stdout as raw NDJSON — correct, nothing lost, but a real boot
|
|
982
|
+
* put dozens of JSON lines on screen before the first rendered one, and a
|
|
983
|
+
* developer who had just turned pretty on reasonably concluded it had not
|
|
984
|
+
* worked. Reported from exactly that reading.
|
|
985
|
+
*/
|
|
986
|
+
private buffer;
|
|
987
|
+
/**
|
|
988
|
+
* Set once {@link MAX_BUFFERED_ENTRIES} is reached. From then on entries go
|
|
989
|
+
* straight to raw stdout — and the buffer is drained raw at the same moment, so
|
|
990
|
+
* the output stays in ORDER rather than replaying old entries after newer ones.
|
|
991
|
+
*/
|
|
992
|
+
private bufferOverflowed;
|
|
993
|
+
/**
|
|
994
|
+
* UTF-8 size of everything currently held, tracked incrementally so the bound
|
|
995
|
+
* costs one `Buffer.byteLength` per entry rather than a walk of the buffer.
|
|
996
|
+
*/
|
|
997
|
+
private bufferedBytes;
|
|
900
998
|
/**
|
|
901
999
|
* @param opts.minLevel — Optional minimum level filter applied by the
|
|
902
1000
|
* destination registry's multi-stream wiring. Omit to accept every level.
|
|
1001
|
+
* @param opts.view — Optional rendering overrides; see {@link PrettyViewOptions}.
|
|
1002
|
+
* Omitted fields keep their default, so `new PrettyDevDestination()` renders
|
|
1003
|
+
* exactly as it did before this option existed.
|
|
903
1004
|
*/
|
|
904
1005
|
constructor(opts?: {
|
|
905
1006
|
minLevel?: LogLevel;
|
|
1007
|
+
view?: PrettyViewOptions;
|
|
906
1008
|
});
|
|
907
1009
|
/**
|
|
908
1010
|
* Lazily resolve `pino-pretty` and build the colorized transform that renders
|
|
@@ -921,12 +1023,24 @@ declare class PrettyDevDestination implements ILogDestination {
|
|
|
921
1023
|
* fan-out already skips a failed destination before reaching this method
|
|
922
1024
|
* (and rescues the entry when nothing else initialized); this branch is the
|
|
923
1025
|
* belt to that, for any caller holding the destination directly;
|
|
924
|
-
* - pre-init (transient window before {@link onInit}, e.g.
|
|
925
|
-
*
|
|
1026
|
+
* - pre-init (transient window before {@link onInit}, e.g. every entry NestJS
|
|
1027
|
+
* emits while instantiating providers) → BUFFERED, then rendered through
|
|
1028
|
+
* the transform once it exists. Nothing is lost on any path: if init fails
|
|
1029
|
+
* the buffer is drained raw, and if it overflows it is drained raw in order.
|
|
926
1030
|
*
|
|
927
1031
|
* @param payload — Newline-terminated JSON entry, UTF-8 encoded.
|
|
928
1032
|
*/
|
|
929
1033
|
write(payload: string): void;
|
|
1034
|
+
/**
|
|
1035
|
+
* Hand every buffered entry to `emit`, in arrival order, and empty the buffer.
|
|
1036
|
+
*
|
|
1037
|
+
* Emptying unconditionally is what makes this safe to call from both the
|
|
1038
|
+
* success and failure paths: whichever runs first owns the entries, and the
|
|
1039
|
+
* other finds nothing to emit twice.
|
|
1040
|
+
*
|
|
1041
|
+
* @param emit - Receives each buffered payload, oldest first.
|
|
1042
|
+
*/
|
|
1043
|
+
private flushBuffer;
|
|
930
1044
|
/**
|
|
931
1045
|
* Flush and close the pretty transform, resolving once the stream has ended.
|
|
932
1046
|
* A no-op when {@link onInit} never ran.
|
|
@@ -1382,4 +1496,4 @@ interface LogEntry {
|
|
|
1382
1496
|
*/
|
|
1383
1497
|
declare const LOG_KEYS_CONVENTION_REGEX: RegExp;
|
|
1384
1498
|
|
|
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 };
|
|
1499
|
+
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 };
|