@zudojs/logger 1.2.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 +23 -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/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 +18 -5
- package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.sanitize.js +66 -21
- 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/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.json.js +26 -13
- 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 +9 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -55,7 +55,11 @@ that entry, and a failure from a timer-triggered flush is rethrown by the
|
|
|
55
55
|
next `flush()` or `close()`.
|
|
56
56
|
|
|
57
57
|
`transportTimeout` (default 10s) bounds every transport write, so a
|
|
58
|
-
transport that stops responding cannot hang `flush()` or `close()`.
|
|
58
|
+
transport that stops responding cannot hang `flush()` or `close()`. A
|
|
59
|
+
write that exceeds it fails with `LoggerTimeoutError`, carrying
|
|
60
|
+
`transportName` and `timeout`; other write failures are reported as
|
|
61
|
+
`LoggerTransportError` with `transportName` set, and formatter failures
|
|
62
|
+
as `LoggerFormatterError` with `formatterName` set.
|
|
59
63
|
|
|
60
64
|
`throwTransportErrors` (default `false`) rethrows transport and formatter
|
|
61
65
|
failures instead of dropping them. A synchronous transport throws from the
|
|
@@ -88,8 +92,14 @@ against `DEFAULT_LOGGER_SECRET_FIELDS` — password, passphrase, secret,
|
|
|
88
92
|
token, jwt, bearer, auth, authorization, cookie, session, sid,
|
|
89
93
|
credential, api key, private key, client secret, card number, cvv, ssn,
|
|
90
94
|
pin, otp and more — so `sessionId` and `cardNumber` are redacted while
|
|
91
|
-
`passenger` and `authorId` are not. Nested objects, arrays
|
|
92
|
-
are all covered
|
|
95
|
+
`passenger` and `authorId` are not. Nested objects, arrays, `Map`,
|
|
96
|
+
`Set` and getters are all covered — a `Map` is redacted per key and a
|
|
97
|
+
`Set` becomes an array. A getter that THROWS yields `"[Unreadable]"`
|
|
98
|
+
for that field: the rest of the entry is logged and the read failure is
|
|
99
|
+
reported through the same path as transport and formatter failures
|
|
100
|
+
(dropped by default, rethrown with `throwTransportErrors`), so a lazy
|
|
101
|
+
ORM relation can never abort the caller's log statement. Passing
|
|
102
|
+
`redact.pattern` replaces the word matcher with
|
|
93
103
|
your own RegExp (the old substring default is still exported as
|
|
94
104
|
`DEFAULT_LOGGER_SECRET_PATTERN`).
|
|
95
105
|
|
|
@@ -115,7 +125,10 @@ indented so none can start at column 0 and pass for a record. The JSON formatter
|
|
|
115
125
|
|
|
116
126
|
Metadata is normalized before serialization, so circular references
|
|
117
127
|
(`"[Circular]"`), BigInt values and functions never make a formatter
|
|
118
|
-
throw and silently drop the record.
|
|
128
|
+
throw and silently drop the record. Only a genuine back-edge becomes
|
|
129
|
+
`"[Circular]"` — the walk tracks the ancestor path, so an object
|
|
130
|
+
referenced from two places in one payload (`{ actor: user, target: user }`)
|
|
131
|
+
is logged in full both times.
|
|
119
132
|
|
|
120
133
|
## Context
|
|
121
134
|
|
|
@@ -150,6 +163,12 @@ Pass `{ colors: true }` in the formatter context to colourize the level
|
|
|
150
163
|
tag of text output. Colour codes are emitted only around the fixed level
|
|
151
164
|
name, never around user-supplied text.
|
|
152
165
|
|
|
166
|
+
A formatter returns either a string or an object
|
|
167
|
+
(`LoggerFormattedOutput`). A string becomes the payload's `message`; an
|
|
168
|
+
object is merged OVER the entry, so `createStructuredLoggerFormatter()`
|
|
169
|
+
hands the transport its structured record (with an ISO-string
|
|
170
|
+
`timestamp` and serialized metadata) rather than the raw entry.
|
|
171
|
+
|
|
153
172
|
## Use Cases
|
|
154
173
|
|
|
155
174
|
- Application logging
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import { formatLoggerEntry } from "../../../loggerFormatter/loggerFormatter.core.js";
|
|
5
5
|
import { createLoggerTransport, writeLoggerTransport, } from "../../../loggerTransport/loggerTransport.core.js";
|
|
6
6
|
import { isLoggerTransport } from "../../../loggerTransport/loggerTransportGuard.js";
|
|
7
|
-
import { LoggerFormatterError, LoggerTransportError, } from "../../../loggerErrors/loggerError.base.js";
|
|
8
|
-
import {
|
|
7
|
+
import { LoggerFormatterError, LoggerTimeoutError, LoggerTransportError, } from "../../../loggerErrors/loggerError.base.js";
|
|
8
|
+
import { createLoggerFormatterError, createLoggerTransportError, } from "../../../loggerErrors/loggerError.helpers.js";
|
|
9
9
|
/**
|
|
10
10
|
* Default formatter used when no formatter is configured.
|
|
11
11
|
*/
|
|
@@ -33,7 +33,11 @@ async function withTransportTimeout(timeoutMs, transportName, operation) {
|
|
|
33
33
|
let timer;
|
|
34
34
|
const expiry = new Promise((_resolve, reject) => {
|
|
35
35
|
timer = setTimeout(() => {
|
|
36
|
-
|
|
36
|
+
// LoggerTimeoutError carries `transportName` and `timeout`, which
|
|
37
|
+
// the bare LoggerTransportError raised here before did not — so
|
|
38
|
+
// `catch (e) { if (e instanceof LoggerTimeoutError) retry() }`
|
|
39
|
+
// could never match.
|
|
40
|
+
reject(new LoggerTimeoutError(transportName, timeoutMs));
|
|
37
41
|
}, timeoutMs);
|
|
38
42
|
// This timer bounds a write; it is not work in its own right. Left
|
|
39
43
|
// referenced it keeps the event loop alive, so a process that has
|
|
@@ -53,6 +57,50 @@ async function withTransportTimeout(timeoutMs, transportName, operation) {
|
|
|
53
57
|
void operation.catch(() => { });
|
|
54
58
|
}
|
|
55
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Builds the payload handed to a transport.
|
|
62
|
+
*
|
|
63
|
+
* `LoggerFormattedOutput` is `string | Record<string, unknown>`. A
|
|
64
|
+
* string replaces the message; an OBJECT is the formatted record and is
|
|
65
|
+
* merged over the entry, so a transport sees the formatter's fields.
|
|
66
|
+
* The object branch used to be computed and then discarded, which made
|
|
67
|
+
* `createStructuredLoggerFormatter()` pure overhead on every log call
|
|
68
|
+
* and handed the transport the raw, unformatted entry instead.
|
|
69
|
+
*/
|
|
70
|
+
function toTransportPayload(entry, formatted) {
|
|
71
|
+
if (typeof formatted === "string") {
|
|
72
|
+
return { ...entry, message: formatted };
|
|
73
|
+
}
|
|
74
|
+
if (formatted !== null &&
|
|
75
|
+
typeof formatted === "object" &&
|
|
76
|
+
!Array.isArray(formatted)) {
|
|
77
|
+
return {
|
|
78
|
+
...entry,
|
|
79
|
+
...formatted,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return entry;
|
|
83
|
+
}
|
|
84
|
+
/** Name of the configured formatter, for error reporting. */
|
|
85
|
+
function resolveFormatterName(configuration) {
|
|
86
|
+
const formatter = configuration.formatter;
|
|
87
|
+
if (typeof formatter === "string") {
|
|
88
|
+
return formatter;
|
|
89
|
+
}
|
|
90
|
+
return formatter.name ?? "formatter";
|
|
91
|
+
}
|
|
92
|
+
/** Wraps a transport failure, keeping an already-typed logger error. */
|
|
93
|
+
function toTransportError(transportName, error) {
|
|
94
|
+
return error instanceof LoggerTransportError
|
|
95
|
+
? error
|
|
96
|
+
: createLoggerTransportError(transportName, error);
|
|
97
|
+
}
|
|
98
|
+
/** Wraps a formatter failure, keeping an already-typed logger error. */
|
|
99
|
+
function toFormatterError(formatterName, error) {
|
|
100
|
+
return error instanceof LoggerFormatterError
|
|
101
|
+
? error
|
|
102
|
+
: createLoggerFormatterError(formatterName, error);
|
|
103
|
+
}
|
|
56
104
|
/**
|
|
57
105
|
* Writes to a transport.
|
|
58
106
|
*/
|
|
@@ -61,12 +109,7 @@ async function writeTransport(configuration, transport, entry, formatted) {
|
|
|
61
109
|
loggerName: configuration.name,
|
|
62
110
|
environment: configuration.environment,
|
|
63
111
|
};
|
|
64
|
-
|
|
65
|
-
const formattedEntry = { ...entry, message: formatted };
|
|
66
|
-
await writeLoggerTransport(transport.transport, formattedEntry, transportContext);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
await writeLoggerTransport(transport.transport, entry, transportContext);
|
|
112
|
+
await writeLoggerTransport(transport.transport, toTransportPayload(entry, formatted), transportContext);
|
|
70
113
|
}
|
|
71
114
|
/**
|
|
72
115
|
* Formats an entry with the configured formatter.
|
|
@@ -94,7 +137,7 @@ function writeTransportMaybeSync(configuration, transport, entry, formatted) {
|
|
|
94
137
|
loggerName: configuration.name,
|
|
95
138
|
environment: configuration.environment,
|
|
96
139
|
};
|
|
97
|
-
const payload =
|
|
140
|
+
const payload = toTransportPayload(entry, formatted);
|
|
98
141
|
const target = transport.transport;
|
|
99
142
|
return typeof target === "function"
|
|
100
143
|
? target(payload, transportContext)
|
|
@@ -109,24 +152,24 @@ export async function dispatchEntry(configuration, entry, handleError) {
|
|
|
109
152
|
formatted = formatEntry(configuration, entry);
|
|
110
153
|
}
|
|
111
154
|
catch (error) {
|
|
112
|
-
|
|
113
|
-
handleError(formatterError);
|
|
155
|
+
handleError(toFormatterError(resolveFormatterName(configuration), error));
|
|
114
156
|
return;
|
|
115
157
|
}
|
|
116
158
|
for (const transport of configuration.transports) {
|
|
159
|
+
let transportName = "transport";
|
|
117
160
|
try {
|
|
118
161
|
if (!isLoggerTransport(transport)) {
|
|
119
162
|
continue;
|
|
120
163
|
}
|
|
121
164
|
const registered = createLoggerTransport(transport);
|
|
165
|
+
transportName = registered.name;
|
|
122
166
|
if (!registered.enabled) {
|
|
123
167
|
continue;
|
|
124
168
|
}
|
|
125
169
|
await withTransportTimeout(configuration.transportTimeout, registered.name, writeTransport(configuration, registered, entry, formatted));
|
|
126
170
|
}
|
|
127
171
|
catch (error) {
|
|
128
|
-
|
|
129
|
-
handleError(transportError);
|
|
172
|
+
handleError(toTransportError(transportName, error));
|
|
130
173
|
}
|
|
131
174
|
}
|
|
132
175
|
}
|
|
@@ -153,28 +196,30 @@ export function dispatchEntrySync(configuration, entry, handleError) {
|
|
|
153
196
|
formatted = formatEntry(configuration, entry);
|
|
154
197
|
}
|
|
155
198
|
catch (error) {
|
|
156
|
-
handleError(
|
|
199
|
+
handleError(toFormatterError(resolveFormatterName(configuration), error));
|
|
157
200
|
return;
|
|
158
201
|
}
|
|
159
202
|
const pending = [];
|
|
160
203
|
for (const transport of configuration.transports) {
|
|
204
|
+
let transportName = "transport";
|
|
161
205
|
try {
|
|
162
206
|
if (!isLoggerTransport(transport)) {
|
|
163
207
|
continue;
|
|
164
208
|
}
|
|
165
209
|
const registered = createLoggerTransport(transport);
|
|
210
|
+
transportName = registered.name;
|
|
166
211
|
if (!registered.enabled) {
|
|
167
212
|
continue;
|
|
168
213
|
}
|
|
169
214
|
const result = writeTransportMaybeSync(configuration, registered, entry, formatted);
|
|
170
215
|
if (result instanceof Promise) {
|
|
171
216
|
pending.push(withTransportTimeout(configuration.transportTimeout, registered.name, result).catch((error) => {
|
|
172
|
-
handleError(
|
|
217
|
+
handleError(toTransportError(registered.name, error));
|
|
173
218
|
}));
|
|
174
219
|
}
|
|
175
220
|
}
|
|
176
221
|
catch (error) {
|
|
177
|
-
handleError(
|
|
222
|
+
handleError(toTransportError(transportName, error));
|
|
178
223
|
}
|
|
179
224
|
}
|
|
180
225
|
if (pending.length === 0) {
|
|
@@ -6,8 +6,13 @@ import type { LoggerEntry } from "../../../loggerEntry/loggerEntry.type.js";
|
|
|
6
6
|
import type { LoggerConfiguration, LogOptions } from "../../../loggerOptions/loggerOptions.type.js";
|
|
7
7
|
/**
|
|
8
8
|
* Creates a normalized log entry.
|
|
9
|
+
*
|
|
10
|
+
* @param onMetadataError - Notified when a metadata property could not
|
|
11
|
+
* be read (a throwing getter). The field is replaced with a marker
|
|
12
|
+
* and the entry is still produced, so the caller's log statement
|
|
13
|
+
* never aborts; reporting is the caller's job.
|
|
9
14
|
*/
|
|
10
15
|
export declare function createEntry(configuration: LoggerConfiguration, contextStorage: {
|
|
11
16
|
get(): import("../../../loggerContext/loggerContext.core.js").LoggerContext | undefined;
|
|
12
|
-
}, level: LoggerLevel, message: string, options: LogOptions): LoggerEntry;
|
|
17
|
+
}, level: LoggerLevel, message: string, options: LogOptions, onMetadataError?: (key: string, error: unknown) => void): LoggerEntry;
|
|
13
18
|
//# sourceMappingURL=loggerCoreMethods.entry.d.ts.map
|
|
@@ -6,8 +6,13 @@ import { createSecretMatcher, redactLogValue, LOGGER_REDACTION_TOKEN, } from "..
|
|
|
6
6
|
import { contextToLogMetadata, createLoggerContext, } from "../../../loggerContext/loggerContext.core.js";
|
|
7
7
|
/**
|
|
8
8
|
* Creates a normalized log entry.
|
|
9
|
+
*
|
|
10
|
+
* @param onMetadataError - Notified when a metadata property could not
|
|
11
|
+
* be read (a throwing getter). The field is replaced with a marker
|
|
12
|
+
* and the entry is still produced, so the caller's log statement
|
|
13
|
+
* never aborts; reporting is the caller's job.
|
|
9
14
|
*/
|
|
10
|
-
export function createEntry(configuration, contextStorage, level, message, options) {
|
|
15
|
+
export function createEntry(configuration, contextStorage, level, message, options, onMetadataError) {
|
|
11
16
|
const activeContext = configuration.inheritContext
|
|
12
17
|
? contextStorage.get()
|
|
13
18
|
: undefined;
|
|
@@ -29,7 +34,7 @@ export function createEntry(configuration, contextStorage, level, message, optio
|
|
|
29
34
|
// path can bypass it — including nested objects, arrays and getters.
|
|
30
35
|
const isSecret = createSecretMatcher(configuration.redact);
|
|
31
36
|
const replacement = configuration.redact.replacement ?? LOGGER_REDACTION_TOKEN;
|
|
32
|
-
const metadata = redactLogValue(rawMetadata, isSecret, replacement);
|
|
37
|
+
const metadata = redactLogValue(rawMetadata, isSecret, replacement, undefined, onMetadataError);
|
|
33
38
|
const context = options.context
|
|
34
39
|
? createLoggerContext({
|
|
35
40
|
parent: activeContext,
|
|
@@ -46,7 +51,7 @@ export function createEntry(configuration, contextStorage, level, message, optio
|
|
|
46
51
|
context: context
|
|
47
52
|
? {
|
|
48
53
|
...context.identifiers,
|
|
49
|
-
metadata: redactLogValue(context.metadata, isSecret, replacement),
|
|
54
|
+
metadata: redactLogValue(context.metadata, isSecret, replacement, undefined, onMetadataError),
|
|
50
55
|
}
|
|
51
56
|
: undefined,
|
|
52
57
|
source: options.source,
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* ZudojsLogger level methods.
|
|
3
3
|
*/
|
|
4
4
|
import { LoggerLevel, shouldLog, } from "../../../loggerLevel/loggerLevel.type.js";
|
|
5
|
-
import { LoggerConfigurationError } from "../../../loggerErrors/loggerError.base.js";
|
|
5
|
+
import { InvalidLoggerEntryError, LoggerConfigurationError, } from "../../../loggerErrors/loggerError.base.js";
|
|
6
|
+
import { toLoggerError } from "../../../loggerErrors/loggerError.helpers.js";
|
|
6
7
|
import { createEntry } from "./loggerCoreMethods.entry.js";
|
|
7
8
|
import { dispatchEntrySync } from "./loggerCoreMethods.dispatch.js";
|
|
8
9
|
/**
|
|
@@ -17,7 +18,24 @@ export function logAtLevel(ctx, level, message, options = {}) {
|
|
|
17
18
|
if (typeof message !== "string") {
|
|
18
19
|
throw new LoggerConfigurationError("Logger message must be a string.");
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
+
// Entry construction reads caller-supplied metadata, including
|
|
22
|
+
// getters. A throwing accessor used to propagate straight out of
|
|
23
|
+
// logger.info(...) and abort the caller — and only when the level
|
|
24
|
+
// let the call through, so the same code was a silent no-op at one
|
|
25
|
+
// log level and a crash at another. The offending field becomes a
|
|
26
|
+
// marker and the failure is reported like every other infrastructure
|
|
27
|
+
// failure, AFTER the line has been dispatched.
|
|
28
|
+
const metadataFailures = [];
|
|
29
|
+
let entry;
|
|
30
|
+
try {
|
|
31
|
+
entry = createEntry(ctx.configuration, ctx.contextStorage, level, message, options, (key, error) => {
|
|
32
|
+
metadataFailures.push(new InvalidLoggerEntryError(`Failed to read log metadata field "${key}": ${toLoggerError(error).message}`, { cause: error }));
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
ctx.handleInfrastructureError(new InvalidLoggerEntryError(`Failed to build log entry: ${toLoggerError(error).message}`, { cause: error }));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
21
39
|
// Dispatch is asynchronous. It used to be fired and forgotten, so
|
|
22
40
|
// flush() and close() could return while entries were still in
|
|
23
41
|
// flight — messages were lost on process exit. Registering the
|
|
@@ -26,5 +44,8 @@ export function logAtLevel(ctx, level, message, options = {}) {
|
|
|
26
44
|
if (dispatch) {
|
|
27
45
|
ctx.trackDispatch(dispatch);
|
|
28
46
|
}
|
|
47
|
+
for (const failure of metadataFailures) {
|
|
48
|
+
ctx.handleInfrastructureError(failure);
|
|
49
|
+
}
|
|
29
50
|
}
|
|
30
51
|
//# sourceMappingURL=loggerCoreMethods.level.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
|
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
* Replacement token written in place of a redacted value.
|
|
13
13
|
*/
|
|
14
14
|
export declare const LOGGER_REDACTION_TOKEN = "[REDACTED]";
|
|
15
|
+
/**
|
|
16
|
+
* Replacement token written in place of a property whose getter threw.
|
|
17
|
+
*
|
|
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]";
|
|
15
23
|
/**
|
|
16
24
|
* Legacy substring pattern for secret field names.
|
|
17
25
|
*
|
|
@@ -64,10 +72,15 @@ export declare function createSecretMatcher(options?: LoggerRedactionOptions): (
|
|
|
64
72
|
/**
|
|
65
73
|
* Recursively replaces secret-named fields with a redaction token.
|
|
66
74
|
*
|
|
67
|
-
* Nesting, arrays and getters are all covered: the walk
|
|
68
|
-
* every enumerable own property, and a getter is read
|
|
69
|
-
* before the value can reach a transport.
|
|
70
|
-
*
|
|
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.
|
|
71
84
|
*/
|
|
72
|
-
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;
|
|
73
86
|
//# sourceMappingURL=loggerEntryHelpers.sanitize.d.ts.map
|
|
@@ -19,6 +19,14 @@ const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g;
|
|
|
19
19
|
* Replacement token written in place of a redacted value.
|
|
20
20
|
*/
|
|
21
21
|
export const LOGGER_REDACTION_TOKEN = "[REDACTED]";
|
|
22
|
+
/**
|
|
23
|
+
* Replacement token written in place of a property whose getter threw.
|
|
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]";
|
|
22
30
|
/**
|
|
23
31
|
* Legacy substring pattern for secret field names.
|
|
24
32
|
*
|
|
@@ -85,15 +93,32 @@ export function createSecretMatcher(options = {}) {
|
|
|
85
93
|
: configured;
|
|
86
94
|
return (key) => exact.has(key.toLowerCase()) || pattern.test(key);
|
|
87
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
|
+
}
|
|
88
108
|
/**
|
|
89
109
|
* Recursively replaces secret-named fields with a redaction token.
|
|
90
110
|
*
|
|
91
|
-
* Nesting, arrays and getters are all covered: the walk
|
|
92
|
-
* every enumerable own property, and a getter is read
|
|
93
|
-
* before the value can reach a transport.
|
|
94
|
-
*
|
|
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.
|
|
95
120
|
*/
|
|
96
|
-
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) {
|
|
97
122
|
if (value === null || typeof value !== "object") {
|
|
98
123
|
return value;
|
|
99
124
|
}
|
|
@@ -104,23 +129,43 @@ export function redactLogValue(value, isSecret, replacement = LOGGER_REDACTION_T
|
|
|
104
129
|
return "[Circular]";
|
|
105
130
|
}
|
|
106
131
|
seen.add(value);
|
|
107
|
-
|
|
108
|
-
|
|
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;
|
|
109
166
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// defineProperty, never assignment: a "__proto__" key coming from
|
|
113
|
-
// JSON.parse of untrusted input would otherwise reach the inherited
|
|
114
|
-
// setter and replace this object's prototype.
|
|
115
|
-
Object.defineProperty(result, key, {
|
|
116
|
-
value: isSecret(key)
|
|
117
|
-
? replacement
|
|
118
|
-
: redactLogValue(item, isSecret, replacement, seen),
|
|
119
|
-
enumerable: true,
|
|
120
|
-
writable: true,
|
|
121
|
-
configurable: true,
|
|
122
|
-
});
|
|
167
|
+
finally {
|
|
168
|
+
seen.delete(value);
|
|
123
169
|
}
|
|
124
|
-
return result;
|
|
125
170
|
}
|
|
126
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
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Lower numeric values represent more severe messages.
|
|
5
5
|
* Higher numeric values represent more verbose messages.
|
|
6
6
|
*/
|
|
7
|
+
import { InvalidLoggerLevelError } from "../loggerErrors/loggerError.base.js";
|
|
7
8
|
export var LoggerLevel;
|
|
8
9
|
(function (LoggerLevel) {
|
|
9
10
|
LoggerLevel[LoggerLevel["FATAL"] = 0] = "FATAL";
|
|
@@ -29,7 +30,7 @@ export function loggerLevelToName(level) {
|
|
|
29
30
|
case LoggerLevel.TRACE:
|
|
30
31
|
return "trace";
|
|
31
32
|
default:
|
|
32
|
-
throw new
|
|
33
|
+
throw new InvalidLoggerLevelError(level);
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
/** Converts a logger level name into its enum value. */
|
|
@@ -50,7 +51,7 @@ export function loggerLevelFromName(name) {
|
|
|
50
51
|
case "trace":
|
|
51
52
|
return LoggerLevel.TRACE;
|
|
52
53
|
default:
|
|
53
|
-
throw new
|
|
54
|
+
throw new InvalidLoggerLevelError(name);
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
/** Checks whether a value is a valid LoggerLevel. */
|
|
@@ -16,6 +16,16 @@ export declare class LoggerManager {
|
|
|
16
16
|
constructor(options?: LoggerOptions);
|
|
17
17
|
/** Initializes the logger manager. Initialization is idempotent. */
|
|
18
18
|
initialize(options?: LoggerOptions): Logger;
|
|
19
|
+
/**
|
|
20
|
+
* Adopts an existing logger as the manager's default logger.
|
|
21
|
+
*
|
|
22
|
+
* The logger is REGISTERED with the underlying factory, so it is
|
|
23
|
+
* covered by flush(), close(), getAll() and size. Assigning it to the
|
|
24
|
+
* private default field alone (as createLoggerManagerFromLogger used
|
|
25
|
+
* to) left the registry empty, making flush()/close() no-ops for the
|
|
26
|
+
* only logger the manager owned.
|
|
27
|
+
*/
|
|
28
|
+
adopt(logger: Logger): Logger;
|
|
19
29
|
/** Returns the default application logger. Lazily initializes when necessary. */
|
|
20
30
|
getLogger(): Logger;
|
|
21
31
|
/** Returns a named logger. Named loggers are managed by the underlying factory. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LoggerDisposedError } from "../loggerErrors/loggerError.base.js";
|
|
1
2
|
import { createLogger } from "../loggerCore/core/loggerCore.core.js";
|
|
2
3
|
import { createDefaultLogger } from "../loggerCore/helpers/loggerCore.helper.js";
|
|
3
4
|
import { createLoggerFactory } from "../loggerFactory/loggerFactory.core.js";
|
|
@@ -19,7 +20,7 @@ export class LoggerManager {
|
|
|
19
20
|
/** Initializes the logger manager. Initialization is idempotent. */
|
|
20
21
|
initialize(options = {}) {
|
|
21
22
|
if (this.closed)
|
|
22
|
-
throw new
|
|
23
|
+
throw new LoggerDisposedError("LoggerManager");
|
|
23
24
|
if (this.initialized && this.defaultLogger)
|
|
24
25
|
return this.defaultLogger;
|
|
25
26
|
const logger = this.factory.create(options.name ?? "zudojs", options);
|
|
@@ -27,10 +28,27 @@ export class LoggerManager {
|
|
|
27
28
|
this.initialized = true;
|
|
28
29
|
return logger;
|
|
29
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Adopts an existing logger as the manager's default logger.
|
|
33
|
+
*
|
|
34
|
+
* The logger is REGISTERED with the underlying factory, so it is
|
|
35
|
+
* covered by flush(), close(), getAll() and size. Assigning it to the
|
|
36
|
+
* private default field alone (as createLoggerManagerFromLogger used
|
|
37
|
+
* to) left the registry empty, making flush()/close() no-ops for the
|
|
38
|
+
* only logger the manager owned.
|
|
39
|
+
*/
|
|
40
|
+
adopt(logger) {
|
|
41
|
+
if (this.closed)
|
|
42
|
+
throw new LoggerDisposedError("LoggerManager");
|
|
43
|
+
this.factory.register(logger);
|
|
44
|
+
this.defaultLogger = logger;
|
|
45
|
+
this.initialized = true;
|
|
46
|
+
return logger;
|
|
47
|
+
}
|
|
30
48
|
/** Returns the default application logger. Lazily initializes when necessary. */
|
|
31
49
|
getLogger() {
|
|
32
50
|
if (this.closed)
|
|
33
|
-
throw new
|
|
51
|
+
throw new LoggerDisposedError("LoggerManager");
|
|
34
52
|
if (!this.defaultLogger)
|
|
35
53
|
return this.initialize();
|
|
36
54
|
return this.defaultLogger;
|
|
@@ -38,13 +56,13 @@ export class LoggerManager {
|
|
|
38
56
|
/** Returns a named logger. Named loggers are managed by the underlying factory. */
|
|
39
57
|
get(name, options = {}) {
|
|
40
58
|
if (this.closed)
|
|
41
|
-
throw new
|
|
59
|
+
throw new LoggerDisposedError("LoggerManager");
|
|
42
60
|
return this.factory.getOrCreate(name, options);
|
|
43
61
|
}
|
|
44
62
|
/** Creates a new logger even when another logger with the same name already exists. */
|
|
45
63
|
create(name, options = {}) {
|
|
46
64
|
if (this.closed)
|
|
47
|
-
throw new
|
|
65
|
+
throw new LoggerDisposedError("LoggerManager");
|
|
48
66
|
return this.factory.create(name, options, true);
|
|
49
67
|
}
|
|
50
68
|
/** Checks whether a named logger exists. */
|
|
@@ -99,7 +117,7 @@ export class LoggerManager {
|
|
|
99
117
|
/** Provides direct access to the underlying factory. */
|
|
100
118
|
getFactory() {
|
|
101
119
|
if (this.closed)
|
|
102
|
-
throw new
|
|
120
|
+
throw new LoggerDisposedError("LoggerManager");
|
|
103
121
|
return this.factory;
|
|
104
122
|
}
|
|
105
123
|
}
|
|
@@ -120,8 +138,7 @@ export function createManagedDefaultLogger(name = "zudojs") {
|
|
|
120
138
|
/** Creates a logger manager from an existing logger. */
|
|
121
139
|
export function createLoggerManagerFromLogger(logger) {
|
|
122
140
|
const manager = new LoggerManager();
|
|
123
|
-
manager
|
|
124
|
-
manager["initialized"] = true;
|
|
141
|
+
manager.adopt(logger);
|
|
125
142
|
return manager;
|
|
126
143
|
}
|
|
127
144
|
/** Returns a logger from a manager or creates a fallback logger when no manager is supplied. */
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { createLoggerTransport, writeLoggerTransport, } from "../loggerTransport.core.js";
|
|
5
5
|
import { closeLoggerTransport, flushLoggerTransport, } from "../loggerTransportHelpers/loggerTransportHelpers.js";
|
|
6
6
|
import { throwCollectedFailures } from "../../loggerErrors/loggerError.helpers.js";
|
|
7
|
+
import { LoggerTransportClosedError } from "../../loggerErrors/loggerError.base.js";
|
|
7
8
|
/**
|
|
8
9
|
* Creates a transport that buffers entries before forwarding
|
|
9
10
|
* them to another transport.
|
|
@@ -14,6 +15,7 @@ export function createBufferedLoggerTransport(transport, options = {}) {
|
|
|
14
15
|
const flushInterval = options.flushInterval ?? 0;
|
|
15
16
|
let timer;
|
|
16
17
|
let deferredFailure;
|
|
18
|
+
let closed = false;
|
|
17
19
|
// Each entry is written on its own: one failing write used to abort the
|
|
18
20
|
// loop after the whole batch had already been spliced out, losing every
|
|
19
21
|
// entry behind it. Only the entries that actually failed are dropped.
|
|
@@ -80,6 +82,12 @@ export function createBufferedLoggerTransport(transport, options = {}) {
|
|
|
80
82
|
name: options.name ?? "buffered",
|
|
81
83
|
enabled: options.enabled ?? true,
|
|
82
84
|
async write(entry) {
|
|
85
|
+
// A write after close() used to be buffered and then silently
|
|
86
|
+
// dropped: nothing drains the buffer again. Refuse it instead so
|
|
87
|
+
// the caller learns the entry was not accepted.
|
|
88
|
+
if (closed) {
|
|
89
|
+
throw new LoggerTransportClosedError(options.name ?? "buffered");
|
|
90
|
+
}
|
|
83
91
|
buffer.push(entry);
|
|
84
92
|
if (buffer.length >= maxSize) {
|
|
85
93
|
await drain();
|
|
@@ -90,6 +98,7 @@ export function createBufferedLoggerTransport(transport, options = {}) {
|
|
|
90
98
|
},
|
|
91
99
|
flush,
|
|
92
100
|
async close() {
|
|
101
|
+
closed = true;
|
|
93
102
|
if (timer) {
|
|
94
103
|
clearTimeout(timer);
|
|
95
104
|
timer = undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/logger",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Structured logging with transports, log levels, and context propagation for Zudojs applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"node": ">=24.0.0"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@zudojs/errors": "1.
|
|
31
|
+
"@zudojs/errors": "1.2.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"typescript": "7.0.2",
|