@zudojs/logger 1.1.0 → 1.3.0
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/README.md +63 -12
- package/dist/loggerCore/core/loggerCore.core.d.ts +7 -1
- package/dist/loggerCore/core/loggerCore.core.js +18 -4
- package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.dispatch.js +62 -17
- package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.entry.d.ts +6 -1
- package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.entry.js +8 -3
- package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.level.js +23 -2
- package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.lifecycle.d.ts +9 -0
- package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.lifecycle.js +51 -49
- package/dist/loggerEntry/index.d.ts +1 -0
- package/dist/loggerEntry/index.js +1 -0
- package/dist/loggerEntry/loggerEntry.secretFields.d.ts +19 -0
- package/dist/loggerEntry/loggerEntry.secretFields.js +90 -0
- package/dist/loggerEntry/loggerEntryCreate.js +2 -1
- package/dist/loggerEntry/loggerEntryHelpers/index.d.ts +1 -1
- package/dist/loggerEntry/loggerEntryHelpers/index.js +1 -1
- package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.sanitize.d.ts +29 -12
- package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.sanitize.js +89 -31
- package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.serialize.js +2 -1
- package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.valueSerialize.d.ts +5 -0
- package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.valueSerialize.js +43 -12
- package/dist/loggerErrors/loggerError.helpers.d.ts +11 -0
- package/dist/loggerErrors/loggerError.helpers.js +21 -0
- package/dist/loggerFactory/loggerFactory.core.d.ts +7 -0
- package/dist/loggerFactory/loggerFactory.core.js +10 -0
- package/dist/loggerFormatter/loggerFormatter.core.js +3 -2
- package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.js +1 -1
- package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.json.js +26 -13
- package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.metadata.d.ts +6 -0
- package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.metadata.js +43 -8
- package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.text.js +1 -1
- package/dist/loggerLevel/loggerLevel.type.js +3 -2
- package/dist/loggerManager/loggerManager.core.d.ts +10 -0
- package/dist/loggerManager/loggerManager.core.js +24 -7
- package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.buffered.js +66 -10
- package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.d.ts +9 -0
- package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.js +29 -8
- package/package.json +2 -2
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default secret-field matching for logger redaction.
|
|
3
|
+
*
|
|
4
|
+
* Field names are split into words (`x-api-key`, `api_key` and `apiKey`
|
|
5
|
+
* all become `api key`) and matched on whole-word runs, the same scheme
|
|
6
|
+
* @zudojs/observability's `isSensitiveField` uses. A raw substring test
|
|
7
|
+
* redacted `passenger`, `compass` and `bypassCache` (they contain `pass`)
|
|
8
|
+
* while missing `auth`, `sessionId`, `sid`, `jwt`, `ssn`, `cardNumber`,
|
|
9
|
+
* `cvv` and `otp` entirely.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Secret field names matched on whole words by default.
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_LOGGER_SECRET_FIELDS = Object.freeze([
|
|
15
|
+
"password",
|
|
16
|
+
"passwd",
|
|
17
|
+
"passphrase",
|
|
18
|
+
"pwd",
|
|
19
|
+
"secret",
|
|
20
|
+
"token",
|
|
21
|
+
"jwt",
|
|
22
|
+
"bearer",
|
|
23
|
+
"authorization",
|
|
24
|
+
"auth",
|
|
25
|
+
"cookie",
|
|
26
|
+
"session",
|
|
27
|
+
"sid",
|
|
28
|
+
"credential",
|
|
29
|
+
"credentials",
|
|
30
|
+
"api_key",
|
|
31
|
+
"private_key",
|
|
32
|
+
"client_secret",
|
|
33
|
+
"credit_card",
|
|
34
|
+
"card_number",
|
|
35
|
+
"cvv",
|
|
36
|
+
"cvc",
|
|
37
|
+
"ssn",
|
|
38
|
+
"social_security",
|
|
39
|
+
"pin",
|
|
40
|
+
"otp",
|
|
41
|
+
]);
|
|
42
|
+
/**
|
|
43
|
+
* Unambiguous secret words that are also matched as a substring of a
|
|
44
|
+
* lowercase run-together name (`userpassword`, `accesstoken`), which the
|
|
45
|
+
* word split alone cannot see.
|
|
46
|
+
*/
|
|
47
|
+
const SUBSTRING_SECRET_WORDS = Object.freeze([
|
|
48
|
+
"password",
|
|
49
|
+
"passwd",
|
|
50
|
+
"secret",
|
|
51
|
+
"token",
|
|
52
|
+
"apikey",
|
|
53
|
+
"privatekey",
|
|
54
|
+
"credential",
|
|
55
|
+
"authorization",
|
|
56
|
+
"cookie",
|
|
57
|
+
]);
|
|
58
|
+
/** Splits a field name into lowercase words. */
|
|
59
|
+
function splitWords(key) {
|
|
60
|
+
return key
|
|
61
|
+
.replace(/([a-z0-9])([A-Z])/gu, "$1 $2")
|
|
62
|
+
.replace(/([A-Z]+)([A-Z][a-z])/gu, "$1 $2")
|
|
63
|
+
.toLowerCase()
|
|
64
|
+
.split(/[^a-z0-9]+/u)
|
|
65
|
+
.filter(Boolean);
|
|
66
|
+
}
|
|
67
|
+
/** Normalizes a field-list entry the way word runs are joined. */
|
|
68
|
+
function normalizeField(field) {
|
|
69
|
+
return field.toLowerCase().replace(/[^a-z0-9]/gu, "");
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Builds the default secret-field predicate from a word list.
|
|
73
|
+
*/
|
|
74
|
+
export function createDefaultSecretFieldMatcher(fields = DEFAULT_LOGGER_SECRET_FIELDS) {
|
|
75
|
+
const targets = new Set(fields.map(normalizeField).filter(Boolean));
|
|
76
|
+
return (key) => {
|
|
77
|
+
const words = splitWords(key);
|
|
78
|
+
for (let start = 0; start < words.length; start += 1) {
|
|
79
|
+
let joined = "";
|
|
80
|
+
for (let end = start; end < words.length; end += 1) {
|
|
81
|
+
joined += words[end] ?? "";
|
|
82
|
+
if (targets.has(joined))
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const compact = key.toLowerCase().replace(/[^a-z0-9]/gu, "");
|
|
87
|
+
return SUBSTRING_SECRET_WORDS.some((word) => compact.includes(word));
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=loggerEntry.secretFields.js.map
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Logger entry creation from input.
|
|
3
3
|
*/
|
|
4
4
|
import { createLoggerEntryId } from "./loggerEntry.core.js";
|
|
5
|
+
import { InvalidLoggerEntryError } from "../loggerErrors/loggerError.base.js";
|
|
5
6
|
import { loggerLevelNameFallback } from "./loggerEntryHelpers/loggerEntryHelpers.serialize.js";
|
|
6
7
|
/**
|
|
7
8
|
* Creates a normalized LoggerEntry.
|
|
@@ -10,7 +11,7 @@ export function createLoggerEntry(input) {
|
|
|
10
11
|
const timestamp = input.timestamp ?? new Date();
|
|
11
12
|
const timestampMs = timestamp.getTime();
|
|
12
13
|
if (!Number.isFinite(timestampMs)) {
|
|
13
|
-
throw new
|
|
14
|
+
throw new InvalidLoggerEntryError("Logger entry timestamp must be a valid date.");
|
|
14
15
|
}
|
|
15
16
|
const levelName = input.levelName ?? loggerLevelNameFallback(input.level);
|
|
16
17
|
return Object.freeze({
|
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
export type { LogValue, LogMetadata, LoggerSource, LoggerEntryContext, } from "./loggerEntryHelpers.types.js";
|
|
7
7
|
export type { LoggerEntry, LoggerEntryInput, } from "./loggerEntryHelpers.interfaces.js";
|
|
8
8
|
export { serializeLoggerEntry, serializeLoggerError, serializeLoggerValue, loggerLevelNameFallback, } from "./loggerEntryHelpers.serialize.js";
|
|
9
|
-
export { LOGGER_REDACTION_TOKEN, DEFAULT_LOGGER_SECRET_PATTERN, escapeLogText, hasLogControlCharacters, createSecretMatcher, redactLogValue, } from "./loggerEntryHelpers.sanitize.js";
|
|
9
|
+
export { LOGGER_REDACTION_TOKEN, LOGGER_UNREADABLE_TOKEN, DEFAULT_LOGGER_SECRET_PATTERN, escapeLogText, hasLogControlCharacters, createSecretMatcher, redactLogValue, } from "./loggerEntryHelpers.sanitize.js";
|
|
10
10
|
export type { LoggerRedactionOptions } from "./loggerEntryHelpers.sanitize.js";
|
|
11
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* Logger entry helper types and utilities.
|
|
5
5
|
*/
|
|
6
6
|
export { serializeLoggerEntry, serializeLoggerError, serializeLoggerValue, loggerLevelNameFallback, } from "./loggerEntryHelpers.serialize.js";
|
|
7
|
-
export { LOGGER_REDACTION_TOKEN, DEFAULT_LOGGER_SECRET_PATTERN, escapeLogText, hasLogControlCharacters, createSecretMatcher, redactLogValue, } from "./loggerEntryHelpers.sanitize.js";
|
|
7
|
+
export { LOGGER_REDACTION_TOKEN, LOGGER_UNREADABLE_TOKEN, DEFAULT_LOGGER_SECRET_PATTERN, escapeLogText, hasLogControlCharacters, createSecretMatcher, redactLogValue, } from "./loggerEntryHelpers.sanitize.js";
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -13,10 +13,21 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export declare const LOGGER_REDACTION_TOKEN = "[REDACTED]";
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Replacement token written in place of a property whose getter threw.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* A throwing accessor used to propagate out of `logger.info(...)` and
|
|
19
|
+
* abort the caller. The field is marked instead, the rest of the entry
|
|
20
|
+
* is logged, and the failure is reported as an infrastructure error.
|
|
21
|
+
*/
|
|
22
|
+
export declare const LOGGER_UNREADABLE_TOKEN = "[Unreadable]";
|
|
23
|
+
/**
|
|
24
|
+
* Legacy substring pattern for secret field names.
|
|
25
|
+
*
|
|
26
|
+
* @deprecated No longer the default: it redacted `passenger`/`compass`
|
|
27
|
+
* and missed `auth`, `sessionId`, `ssn`, `cardNumber`, `cvv` and `otp`.
|
|
28
|
+
* The default is now the word-based matcher over
|
|
29
|
+
* `DEFAULT_LOGGER_SECRET_FIELDS`. Pass this as `redact.pattern` to opt
|
|
30
|
+
* back into the old behaviour.
|
|
20
31
|
*/
|
|
21
32
|
export declare const DEFAULT_LOGGER_SECRET_PATTERN: RegExp;
|
|
22
33
|
/**
|
|
@@ -32,8 +43,9 @@ export interface LoggerRedactionOptions {
|
|
|
32
43
|
*/
|
|
33
44
|
readonly keys?: readonly string[];
|
|
34
45
|
/**
|
|
35
|
-
* Field-name pattern.
|
|
36
|
-
* Pass a pattern that never
|
|
46
|
+
* Field-name pattern. When omitted, the word-based default matcher over
|
|
47
|
+
* `DEFAULT_LOGGER_SECRET_FIELDS` is used. Pass a pattern that never
|
|
48
|
+
* matches to rely on `keys` alone.
|
|
37
49
|
*/
|
|
38
50
|
readonly pattern?: RegExp;
|
|
39
51
|
/**
|
|
@@ -45,8 +57,8 @@ export interface LoggerRedactionOptions {
|
|
|
45
57
|
* Escapes control characters that could forge log records.
|
|
46
58
|
*
|
|
47
59
|
* CR, LF, TAB and the ANSI escape byte become printable escapes; every
|
|
48
|
-
* other C0 control character and DEL becomes `\xNN
|
|
49
|
-
*
|
|
60
|
+
* other C0/C1 control character and DEL becomes `\xNN`, and U+2028 /
|
|
61
|
+
* U+2029 become `\u2028` / `\u2029`. All other text is unchanged.
|
|
50
62
|
*/
|
|
51
63
|
export declare function escapeLogText(value: string): string;
|
|
52
64
|
/**
|
|
@@ -60,10 +72,15 @@ export declare function createSecretMatcher(options?: LoggerRedactionOptions): (
|
|
|
60
72
|
/**
|
|
61
73
|
* Recursively replaces secret-named fields with a redaction token.
|
|
62
74
|
*
|
|
63
|
-
* Nesting, arrays and getters are all covered: the walk
|
|
64
|
-
* every enumerable own property, and a getter is read
|
|
65
|
-
* before the value can reach a transport.
|
|
66
|
-
*
|
|
75
|
+
* Nesting, arrays, `Map`, `Set` and getters are all covered: the walk
|
|
76
|
+
* descends into every enumerable own property, and a getter is read
|
|
77
|
+
* here — once, before the value can reach a transport. A getter that
|
|
78
|
+
* throws yields {@link LOGGER_UNREADABLE_TOKEN} and is reported through
|
|
79
|
+
* `onReadError` instead of aborting the caller's log statement.
|
|
80
|
+
*
|
|
81
|
+
* `seen` tracks the ANCESTOR PATH only (each object is unmarked as the
|
|
82
|
+
* walk ascends), so a back-edge resolves to "[Circular]" while an
|
|
83
|
+
* object merely referenced twice in one payload is logged both times.
|
|
67
84
|
*/
|
|
68
|
-
export declare function redactLogValue(value: unknown, isSecret: (key: string) => boolean, replacement?: string, seen?: WeakSet<object
|
|
85
|
+
export declare function redactLogValue(value: unknown, isSecret: (key: string) => boolean, replacement?: string, seen?: WeakSet<object>, onReadError?: (key: string, error: unknown) => void): unknown;
|
|
69
86
|
//# sourceMappingURL=loggerEntryHelpers.sanitize.d.ts.map
|
|
@@ -8,28 +8,41 @@
|
|
|
8
8
|
* terminal. Every string that reaches a text-shaped formatter is
|
|
9
9
|
* therefore escaped here first.
|
|
10
10
|
*/
|
|
11
|
+
import { createDefaultSecretFieldMatcher } from "../loggerEntry.secretFields.js";
|
|
11
12
|
/**
|
|
12
|
-
* Matches C0
|
|
13
|
-
*
|
|
13
|
+
* Matches C0 controls, DEL, C1 controls (NEL, CSI) and the Unicode
|
|
14
|
+
* line/paragraph separators — everything that can forge a record
|
|
15
|
+
* boundary or drive a terminal.
|
|
14
16
|
*/
|
|
15
|
-
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/g;
|
|
17
|
+
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g;
|
|
16
18
|
/**
|
|
17
19
|
* Replacement token written in place of a redacted value.
|
|
18
20
|
*/
|
|
19
21
|
export const LOGGER_REDACTION_TOKEN = "[REDACTED]";
|
|
20
22
|
/**
|
|
21
|
-
*
|
|
23
|
+
* Replacement token written in place of a property whose getter threw.
|
|
22
24
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
+
* A throwing accessor used to propagate out of `logger.info(...)` and
|
|
26
|
+
* abort the caller. The field is marked instead, the rest of the entry
|
|
27
|
+
* is logged, and the failure is reported as an infrastructure error.
|
|
28
|
+
*/
|
|
29
|
+
export const LOGGER_UNREADABLE_TOKEN = "[Unreadable]";
|
|
30
|
+
/**
|
|
31
|
+
* Legacy substring pattern for secret field names.
|
|
32
|
+
*
|
|
33
|
+
* @deprecated No longer the default: it redacted `passenger`/`compass`
|
|
34
|
+
* and missed `auth`, `sessionId`, `ssn`, `cardNumber`, `cvv` and `otp`.
|
|
35
|
+
* The default is now the word-based matcher over
|
|
36
|
+
* `DEFAULT_LOGGER_SECRET_FIELDS`. Pass this as `redact.pattern` to opt
|
|
37
|
+
* back into the old behaviour.
|
|
25
38
|
*/
|
|
26
39
|
export const DEFAULT_LOGGER_SECRET_PATTERN = /(pass(word|wd)?|secret|token|api[-_.]?key|private[-_.]?key|credential|authorization|cookie)/i;
|
|
27
40
|
/**
|
|
28
41
|
* Escapes control characters that could forge log records.
|
|
29
42
|
*
|
|
30
43
|
* CR, LF, TAB and the ANSI escape byte become printable escapes; every
|
|
31
|
-
* other C0 control character and DEL becomes `\xNN
|
|
32
|
-
*
|
|
44
|
+
* other C0/C1 control character and DEL becomes `\xNN`, and U+2028 /
|
|
45
|
+
* U+2029 become `\u2028` / `\u2029`. All other text is unchanged.
|
|
33
46
|
*/
|
|
34
47
|
export function escapeLogText(value) {
|
|
35
48
|
return value.replace(CONTROL_CHARACTERS, (character) => {
|
|
@@ -42,6 +55,10 @@ export function escapeLogText(value) {
|
|
|
42
55
|
return "\\t";
|
|
43
56
|
case "\u001b":
|
|
44
57
|
return "\\u001b";
|
|
58
|
+
case "\u2028":
|
|
59
|
+
return "\\u2028";
|
|
60
|
+
case "\u2029":
|
|
61
|
+
return "\\u2029";
|
|
45
62
|
default: {
|
|
46
63
|
const code = character.charCodeAt(0);
|
|
47
64
|
return `\\x${code.toString(16).padStart(2, "0")}`;
|
|
@@ -62,25 +79,46 @@ export function createSecretMatcher(options = {}) {
|
|
|
62
79
|
if (options.enabled === false) {
|
|
63
80
|
return () => false;
|
|
64
81
|
}
|
|
82
|
+
const exact = new Set((options.keys ?? []).map((key) => key.toLowerCase()));
|
|
83
|
+
const configured = options.pattern;
|
|
84
|
+
if (configured === undefined) {
|
|
85
|
+
const isDefaultSecret = createDefaultSecretFieldMatcher();
|
|
86
|
+
return (key) => exact.has(key.toLowerCase()) || isDefaultSecret(key);
|
|
87
|
+
}
|
|
65
88
|
// Copy the pattern without `g`/`y`: those flags make `test()` advance
|
|
66
89
|
// `lastIndex`, so a shared pattern would match a secret-named field on
|
|
67
90
|
// one entry and let it through unredacted on the next.
|
|
68
|
-
const configured = options.pattern ?? DEFAULT_LOGGER_SECRET_PATTERN;
|
|
69
91
|
const pattern = configured.global || configured.sticky
|
|
70
92
|
? new RegExp(configured.source, configured.flags.replace(/[gy]/gu, ""))
|
|
71
93
|
: configured;
|
|
72
|
-
const exact = new Set((options.keys ?? []).map((key) => key.toLowerCase()));
|
|
73
94
|
return (key) => exact.has(key.toLowerCase()) || pattern.test(key);
|
|
74
95
|
}
|
|
96
|
+
/** Defines an own, enumerable property without touching a setter. */
|
|
97
|
+
function defineLogProperty(target, key, value) {
|
|
98
|
+
// defineProperty, never assignment: a "__proto__" key coming from
|
|
99
|
+
// JSON.parse of untrusted input would otherwise reach the inherited
|
|
100
|
+
// setter and replace this object's prototype.
|
|
101
|
+
Object.defineProperty(target, key, {
|
|
102
|
+
value,
|
|
103
|
+
enumerable: true,
|
|
104
|
+
writable: true,
|
|
105
|
+
configurable: true,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
75
108
|
/**
|
|
76
109
|
* Recursively replaces secret-named fields with a redaction token.
|
|
77
110
|
*
|
|
78
|
-
* Nesting, arrays and getters are all covered: the walk
|
|
79
|
-
* every enumerable own property, and a getter is read
|
|
80
|
-
* before the value can reach a transport.
|
|
81
|
-
*
|
|
111
|
+
* Nesting, arrays, `Map`, `Set` and getters are all covered: the walk
|
|
112
|
+
* descends into every enumerable own property, and a getter is read
|
|
113
|
+
* here — once, before the value can reach a transport. A getter that
|
|
114
|
+
* throws yields {@link LOGGER_UNREADABLE_TOKEN} and is reported through
|
|
115
|
+
* `onReadError` instead of aborting the caller's log statement.
|
|
116
|
+
*
|
|
117
|
+
* `seen` tracks the ANCESTOR PATH only (each object is unmarked as the
|
|
118
|
+
* walk ascends), so a back-edge resolves to "[Circular]" while an
|
|
119
|
+
* object merely referenced twice in one payload is logged both times.
|
|
82
120
|
*/
|
|
83
|
-
export function redactLogValue(value, isSecret, replacement = LOGGER_REDACTION_TOKEN, seen = new WeakSet()) {
|
|
121
|
+
export function redactLogValue(value, isSecret, replacement = LOGGER_REDACTION_TOKEN, seen = new WeakSet(), onReadError) {
|
|
84
122
|
if (value === null || typeof value !== "object") {
|
|
85
123
|
return value;
|
|
86
124
|
}
|
|
@@ -91,23 +129,43 @@ export function redactLogValue(value, isSecret, replacement = LOGGER_REDACTION_T
|
|
|
91
129
|
return "[Circular]";
|
|
92
130
|
}
|
|
93
131
|
seen.add(value);
|
|
94
|
-
|
|
95
|
-
|
|
132
|
+
try {
|
|
133
|
+
const descend = (item) => redactLogValue(item, isSecret, replacement, seen, onReadError);
|
|
134
|
+
if (Array.isArray(value)) {
|
|
135
|
+
return value.map(descend);
|
|
136
|
+
}
|
|
137
|
+
if (value instanceof Set) {
|
|
138
|
+
return Array.from(value, descend);
|
|
139
|
+
}
|
|
140
|
+
const result = {};
|
|
141
|
+
if (value instanceof Map) {
|
|
142
|
+
for (const [key, item] of value.entries()) {
|
|
143
|
+
const name = typeof key === "string" ? key : String(key);
|
|
144
|
+
defineLogProperty(result, name, isSecret(name) ? replacement : descend(item));
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
for (const key of Object.keys(value)) {
|
|
149
|
+
if (isSecret(key)) {
|
|
150
|
+
// Never even read a secret-named accessor.
|
|
151
|
+
defineLogProperty(result, key, replacement);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
let item;
|
|
155
|
+
try {
|
|
156
|
+
item = value[key];
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
onReadError?.(key, error);
|
|
160
|
+
defineLogProperty(result, key, LOGGER_UNREADABLE_TOKEN);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
defineLogProperty(result, key, descend(item));
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
96
166
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// defineProperty, never assignment: a "__proto__" key coming from
|
|
100
|
-
// JSON.parse of untrusted input would otherwise reach the inherited
|
|
101
|
-
// setter and replace this object's prototype.
|
|
102
|
-
Object.defineProperty(result, key, {
|
|
103
|
-
value: isSecret(key)
|
|
104
|
-
? replacement
|
|
105
|
-
: redactLogValue(item, isSecret, replacement, seen),
|
|
106
|
-
enumerable: true,
|
|
107
|
-
writable: true,
|
|
108
|
-
configurable: true,
|
|
109
|
-
});
|
|
167
|
+
finally {
|
|
168
|
+
seen.delete(value);
|
|
110
169
|
}
|
|
111
|
-
return result;
|
|
112
170
|
}
|
|
113
171
|
//# sourceMappingURL=loggerEntryHelpers.sanitize.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Logger entry serialization helpers.
|
|
3
3
|
*/
|
|
4
|
+
import { InvalidLoggerLevelError } from "../../loggerErrors/loggerError.base.js";
|
|
4
5
|
import { serializeLoggerError, serializeLoggerValue, } from "./loggerEntryHelpers.valueSerialize.js";
|
|
5
6
|
/**
|
|
6
7
|
* Returns a plain serializable representation of an entry.
|
|
@@ -49,7 +50,7 @@ export function loggerLevelNameFallback(level) {
|
|
|
49
50
|
case 5:
|
|
50
51
|
return "trace";
|
|
51
52
|
default:
|
|
52
|
-
throw new
|
|
53
|
+
throw new InvalidLoggerLevelError(level);
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
//# sourceMappingURL=loggerEntryHelpers.serialize.js.map
|
|
@@ -11,6 +11,11 @@ export declare function serializeLoggerError(error: {
|
|
|
11
11
|
}): Record<string, unknown>;
|
|
12
12
|
/**
|
|
13
13
|
* Converts arbitrary values into safer serializable values.
|
|
14
|
+
*
|
|
15
|
+
* `seen` tracks the ANCESTOR PATH only, so only a genuine back-edge
|
|
16
|
+
* becomes "[Circular]"; `Map` and `Set` keep their contents; and a
|
|
17
|
+
* property whose getter throws becomes "[Unreadable]" rather than
|
|
18
|
+
* taking the whole log line down.
|
|
14
19
|
*/
|
|
15
20
|
export declare function serializeLoggerValue(value: unknown, seen?: WeakSet<object>): unknown;
|
|
16
21
|
//# sourceMappingURL=loggerEntryHelpers.valueSerialize.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Logger entry value serialization.
|
|
3
3
|
*/
|
|
4
|
+
import { LOGGER_UNREADABLE_TOKEN } from "./loggerEntryHelpers.sanitize.js";
|
|
4
5
|
/**
|
|
5
6
|
* Serializes an error-like object into a plain object.
|
|
6
7
|
*/
|
|
@@ -13,6 +14,11 @@ export function serializeLoggerError(error) {
|
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Converts arbitrary values into safer serializable values.
|
|
17
|
+
*
|
|
18
|
+
* `seen` tracks the ANCESTOR PATH only, so only a genuine back-edge
|
|
19
|
+
* becomes "[Circular]"; `Map` and `Set` keep their contents; and a
|
|
20
|
+
* property whose getter throws becomes "[Unreadable]" rather than
|
|
21
|
+
* taking the whole log line down.
|
|
16
22
|
*/
|
|
17
23
|
export function serializeLoggerValue(value, seen = new WeakSet()) {
|
|
18
24
|
if (value === null ||
|
|
@@ -44,21 +50,46 @@ export function serializeLoggerValue(value, seen = new WeakSet()) {
|
|
|
44
50
|
return "[Circular]";
|
|
45
51
|
}
|
|
46
52
|
seen.add(value);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
try {
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
return value.map((item) => serializeLoggerValue(item, seen));
|
|
56
|
+
}
|
|
57
|
+
if (value instanceof Set) {
|
|
58
|
+
return Array.from(value, (item) => serializeLoggerValue(item, seen));
|
|
59
|
+
}
|
|
60
|
+
const result = {};
|
|
52
61
|
// defineProperty, never assignment: a "__proto__" key from an
|
|
53
62
|
// untrusted payload would otherwise reach the inherited setter and
|
|
54
63
|
// replace the serialized object's prototype.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
const define = (key, item) => {
|
|
65
|
+
Object.defineProperty(result, key, {
|
|
66
|
+
value: item,
|
|
67
|
+
enumerable: true,
|
|
68
|
+
writable: true,
|
|
69
|
+
configurable: true,
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
if (value instanceof Map) {
|
|
73
|
+
for (const [key, item] of value.entries()) {
|
|
74
|
+
define(typeof key === "string" ? key : String(key), serializeLoggerValue(item, seen));
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
for (const key of Object.keys(value)) {
|
|
79
|
+
let item;
|
|
80
|
+
try {
|
|
81
|
+
item = value[key];
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
define(key, LOGGER_UNREADABLE_TOKEN);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
define(key, serializeLoggerValue(item, seen));
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
seen.delete(value);
|
|
61
93
|
}
|
|
62
|
-
return result;
|
|
63
94
|
}
|
|
64
95
|
//# sourceMappingURL=loggerEntryHelpers.valueSerialize.js.map
|
|
@@ -12,4 +12,15 @@ export declare function getLoggerErrorCause(error: unknown): unknown;
|
|
|
12
12
|
export declare function createLoggerTransportError(transportName: string, error: unknown): LoggerTransportError;
|
|
13
13
|
/** Creates a formatter error while preserving the original failure. */
|
|
14
14
|
export declare function createLoggerFormatterError(formatterName: string, error: unknown): LoggerFormatterError;
|
|
15
|
+
/**
|
|
16
|
+
* Rethrows collected failures: the single failure itself, or an
|
|
17
|
+
* AggregateError when more than one step failed. Does nothing when the
|
|
18
|
+
* list is empty.
|
|
19
|
+
*/
|
|
20
|
+
export declare function throwCollectedFailures(failures: readonly unknown[], message: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Runs every step even when earlier ones fail, then rethrows the
|
|
23
|
+
* collected failures via {@link throwCollectedFailures}.
|
|
24
|
+
*/
|
|
25
|
+
export declare function settleAllOrThrow(steps: readonly (() => unknown)[], message: string): Promise<void>;
|
|
15
26
|
//# sourceMappingURL=loggerError.helpers.d.ts.map
|
|
@@ -38,4 +38,25 @@ export function createLoggerFormatterError(formatterName, error) {
|
|
|
38
38
|
const message = error instanceof Error ? error.message : String(error);
|
|
39
39
|
return new LoggerFormatterError(`Logger formatter "${formatterName}" failed: ${message}`, { formatterName, cause });
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Rethrows collected failures: the single failure itself, or an
|
|
43
|
+
* AggregateError when more than one step failed. Does nothing when the
|
|
44
|
+
* list is empty.
|
|
45
|
+
*/
|
|
46
|
+
export function throwCollectedFailures(failures, message) {
|
|
47
|
+
if (failures.length === 0)
|
|
48
|
+
return;
|
|
49
|
+
if (failures.length === 1)
|
|
50
|
+
throw failures[0];
|
|
51
|
+
throw new AggregateError(failures, message);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Runs every step even when earlier ones fail, then rethrows the
|
|
55
|
+
* collected failures via {@link throwCollectedFailures}.
|
|
56
|
+
*/
|
|
57
|
+
export async function settleAllOrThrow(steps, message) {
|
|
58
|
+
const results = await Promise.allSettled(steps.map(async (step) => step()));
|
|
59
|
+
const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
60
|
+
throwCollectedFailures(failures, message);
|
|
61
|
+
}
|
|
41
62
|
//# sourceMappingURL=loggerError.helpers.js.map
|
|
@@ -17,6 +17,13 @@ export declare class LoggerFactory {
|
|
|
17
17
|
* instance is returned unless `forceNew` is enabled.
|
|
18
18
|
*/
|
|
19
19
|
create(name?: string, options?: LoggerOptions, forceNew?: boolean): Logger;
|
|
20
|
+
/**
|
|
21
|
+
* Registers an existing logger under its own name (or `name`).
|
|
22
|
+
*
|
|
23
|
+
* An adopted logger is then covered by `flushAll()`, `disposeAll()`,
|
|
24
|
+
* `getAll()` and `size` exactly like one the factory created itself.
|
|
25
|
+
*/
|
|
26
|
+
register(logger: Logger, name?: string): Logger;
|
|
20
27
|
/** Returns an existing logger. */
|
|
21
28
|
get(name: string): Logger | undefined;
|
|
22
29
|
/** Gets an existing logger or creates it. */
|
|
@@ -31,6 +31,16 @@ export class LoggerFactory {
|
|
|
31
31
|
this.loggers.set(loggerName, logger);
|
|
32
32
|
return logger;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Registers an existing logger under its own name (or `name`).
|
|
36
|
+
*
|
|
37
|
+
* An adopted logger is then covered by `flushAll()`, `disposeAll()`,
|
|
38
|
+
* `getAll()` and `size` exactly like one the factory created itself.
|
|
39
|
+
*/
|
|
40
|
+
register(logger, name) {
|
|
41
|
+
this.loggers.set(name ?? logger.name, logger);
|
|
42
|
+
return logger;
|
|
43
|
+
}
|
|
34
44
|
/** Returns an existing logger. */
|
|
35
45
|
get(name) {
|
|
36
46
|
return this.loggers.get(name);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Core logger formatter functions.
|
|
3
3
|
*/
|
|
4
|
+
import { LoggerFormatterNotFoundError } from "../loggerErrors/loggerError.base.js";
|
|
4
5
|
import { createLoggerFormatterId, isLoggerFormatterFunction, isLoggerFormatterObject, isLoggerFormatter, } from "./loggerFormatterGuard.js";
|
|
5
6
|
/**
|
|
6
7
|
* Creates a function-backed formatter.
|
|
@@ -20,7 +21,7 @@ export function createLoggerFormatter(formatter, options = {}) {
|
|
|
20
21
|
return formatter(entry, context);
|
|
21
22
|
}
|
|
22
23
|
if (typeof formatter === "string") {
|
|
23
|
-
throw new
|
|
24
|
+
throw new LoggerFormatterNotFoundError(formatter);
|
|
24
25
|
}
|
|
25
26
|
return formatter.format(entry, context);
|
|
26
27
|
},
|
|
@@ -34,7 +35,7 @@ export function formatLoggerEntry(formatter, entry, context = {}) {
|
|
|
34
35
|
return formatter(entry, context);
|
|
35
36
|
}
|
|
36
37
|
if (typeof formatter === "string") {
|
|
37
|
-
throw new
|
|
38
|
+
throw new LoggerFormatterNotFoundError(formatter);
|
|
38
39
|
}
|
|
39
40
|
return formatter.format(entry, context);
|
|
40
41
|
}
|
|
@@ -11,7 +11,7 @@ import { createTextLoggerFormatter } from "./loggerFormatterFormatters.text.js";
|
|
|
11
11
|
*/
|
|
12
12
|
export function createCompactLoggerFormatter(options = {}) {
|
|
13
13
|
return createLoggerFormatter((entry) => {
|
|
14
|
-
const level = entry.levelName.toUpperCase();
|
|
14
|
+
const level = escapeLogText(entry.levelName.toUpperCase());
|
|
15
15
|
const logger = entry.logger ? ` ${escapeLogText(entry.logger)}:` : "";
|
|
16
16
|
return `${level}${logger} ${escapeLogText(entry.message)}`;
|
|
17
17
|
}, {
|
|
@@ -12,29 +12,42 @@ function removeUndefinedValues(value, seen = new WeakSet()) {
|
|
|
12
12
|
return "[Circular]";
|
|
13
13
|
}
|
|
14
14
|
seen.add(value);
|
|
15
|
-
|
|
15
|
+
try {
|
|
16
|
+
return value.map((item) => removeUndefinedValues(item, seen));
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
seen.delete(value);
|
|
20
|
+
}
|
|
16
21
|
}
|
|
17
22
|
if (value && typeof value === "object" && !(value instanceof Date)) {
|
|
18
23
|
// Without a cycle guard this walk recursed until the stack blew,
|
|
19
24
|
// and the resulting RangeError was swallowed by dispatch — losing
|
|
20
|
-
// the log line rather than reporting the offending value.
|
|
25
|
+
// the log line rather than reporting the offending value. `seen`
|
|
26
|
+
// tracks the ancestor path only (unmarked on ascent), so an object
|
|
27
|
+
// referenced twice in one payload is kept rather than collapsing
|
|
28
|
+
// to "[Circular]" from its second occurrence on.
|
|
21
29
|
if (seen.has(value)) {
|
|
22
30
|
return "[Circular]";
|
|
23
31
|
}
|
|
24
32
|
seen.add(value);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
try {
|
|
34
|
+
const result = {};
|
|
35
|
+
for (const [key, item] of Object.entries(value)) {
|
|
36
|
+
if (item === undefined) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
Object.defineProperty(result, key, {
|
|
40
|
+
value: removeUndefinedValues(item, seen),
|
|
41
|
+
enumerable: true,
|
|
42
|
+
writable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
});
|
|
29
45
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
configurable: true,
|
|
35
|
-
});
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
seen.delete(value);
|
|
36
50
|
}
|
|
37
|
-
return result;
|
|
38
51
|
}
|
|
39
52
|
return value;
|
|
40
53
|
}
|