@zudojs/logger 1.2.0 → 1.4.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.
Files changed (46) hide show
  1. package/README.md +74 -5
  2. package/dist/loggerCore/core/loggerCore.context.d.ts +4 -4
  3. package/dist/loggerCore/core/loggerCore.core.d.ts +4 -4
  4. package/dist/loggerCore/core/loggerCore.core.js +8 -8
  5. package/dist/loggerCore/core/loggerCore.type.d.ts +14 -4
  6. package/dist/loggerCore/helpers/loggerCore.helper.d.ts +2 -2
  7. package/dist/loggerCore/helpers/loggerCoreMethods/index.d.ts +1 -1
  8. package/dist/loggerCore/helpers/loggerCoreMethods/index.js +1 -1
  9. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.child.d.ts +2 -2
  10. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.dispatch.js +71 -25
  11. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.entry.d.ts +6 -1
  12. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.entry.js +8 -3
  13. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.level.d.ts +7 -0
  14. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.level.js +35 -2
  15. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.lifecycle.d.ts +3 -3
  16. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.lifecycle.js +8 -6
  17. package/dist/loggerEntry/loggerEntryCreate.js +2 -1
  18. package/dist/loggerEntry/loggerEntryHelpers/index.d.ts +1 -1
  19. package/dist/loggerEntry/loggerEntryHelpers/index.js +1 -1
  20. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.interfaces.d.ts +11 -1
  21. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.sanitize.d.ts +18 -5
  22. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.sanitize.js +66 -21
  23. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.serialize.js +2 -1
  24. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.valueSerialize.d.ts +11 -2
  25. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.valueSerialize.js +53 -20
  26. package/dist/loggerFactory/loggerFactory.core.d.ts +9 -2
  27. package/dist/loggerFactory/loggerFactory.core.js +10 -0
  28. package/dist/loggerFormatter/loggerFormatter.core.js +3 -2
  29. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.json.js +26 -13
  30. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.metadata.d.ts +5 -2
  31. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.metadata.js +12 -5
  32. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.text.js +8 -4
  33. package/dist/loggerLevel/loggerLevel.type.d.ts +13 -0
  34. package/dist/loggerLevel/loggerLevel.type.js +17 -2
  35. package/dist/loggerManager/loggerManager.core.d.ts +10 -0
  36. package/dist/loggerManager/loggerManager.core.js +24 -7
  37. package/dist/loggerOptions/loggerOptions.type.d.ts +14 -3
  38. package/dist/loggerOptions/loggerOptions.type.js +7 -3
  39. package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.buffered.js +9 -0
  40. package/dist/loggerTransport/loggerTransportConsole/loggerTransportConsole.core.d.ts +3 -2
  41. package/dist/loggerTransport/loggerTransportConsole/loggerTransportConsole.core.js +9 -4
  42. package/dist/loggerTransport/loggerTransportHelpers/index.d.ts +1 -1
  43. package/dist/loggerTransport/loggerTransportHelpers/index.js +1 -1
  44. package/dist/loggerTransport/loggerTransportHelpers/loggerTransportHelpers.d.ts +15 -0
  45. package/dist/loggerTransport/loggerTransportHelpers/loggerTransportHelpers.js +22 -0
  46. package/package.json +4 -4
package/README.md CHANGED
@@ -34,13 +34,56 @@ await logger.flush(); // drain in-flight writes before exit
34
34
  (5). A logger emits every message at or below its configured level;
35
35
  `level` defaults to `info`.
36
36
 
37
+ Wherever a level is configured (the `level` option, `setLevel()` and
38
+ `child({ level })`) it may be the enum value or its name in any case:
39
+
40
+ ```typescript
41
+ createLogger({ level: "error" }); // same as LoggerLevel.ERROR
42
+ logger.setLevel("DEBUG");
43
+ logger.child({ level: LoggerLevel.TRACE });
44
+ createLogger({ level: process.env.LOG_LEVEL as LoggerLevelName });
45
+ ```
46
+
47
+ The type is `LoggerLevelLike` (`LoggerLevel | LoggerLevelName | Uppercase<LoggerLevelName>`);
48
+ `"warning"` and `"information"` are accepted as aliases at runtime, and
49
+ `resolveLoggerLevel(value)` converts one to the enum. An unknown name
50
+ throws (`InvalidLoggerLevelError` from the options and `child`,
51
+ `LoggerConfigurationError` from `setLevel`) rather than leaving a logger
52
+ that silently emits nothing. A custom `Logger` implementation keeps
53
+ compiling (`ChildLoggerOptions.level` is still the enum; `child()` accepts
54
+ `ChildLoggerOptionsInput`), but may now be handed a name in `setLevel()` or
55
+ `child()`: pass it through `resolveLoggerLevel()`.
56
+
37
57
  ## Transports
38
58
 
39
59
  A transport is either a `{ name, enabled, write, flush?, close? }`
40
60
  object or a `(entry, context) => void | Promise<void>` function. When no
41
61
  transport is configured the logger writes to the console.
42
62
 
43
- Built in: `createConsoleLoggerTransport`, and the composites
63
+ ### What a transport receives
64
+
65
+ `entry.message` is the message exactly as it was logged. The logger's
66
+ formatter renders the whole record into `entry.formatted`:
67
+
68
+ - a text formatter (the default): the line with timestamp, level, logger
69
+ name, message, metadata and, if enabled, the stack;
70
+ - the JSON formatter: its JSON string;
71
+ - an object formatter such as `createStructuredLoggerFormatter()`: its
72
+ record is merged over the entry, and `entry.formatted` is that record as
73
+ one line of JSON (cycles become `"[Circular]"`, BigInt a string).
74
+
75
+ A transport that prints text should print `entry.formatted ?? entry.message`
76
+ (or call `formatTransportLine(entry)`, which falls back to the entry as one
77
+ JSON line); one that ships records can ignore `formatted`.
78
+
79
+ > **Changed in this release.** A string formatter's output used to replace
80
+ > `entry.message`, so a custom transport printing `entry.message` got the
81
+ > formatted line and could not recover the raw message. It now receives
82
+ > the raw message; switch such a transport to
83
+ > `entry.formatted ?? entry.message` to keep printing the formatted line.
84
+
85
+ Built in: `createConsoleLoggerTransport` (prints one line per record:
86
+ `entry.formatted`, or the entry as JSON), and the composites
44
87
  `createMultiLoggerTransport`, `createConditionalLoggerTransport` and
45
88
  `createBufferedLoggerTransport`. File and HTTP transports are not
46
89
  included — implement the `LoggerTransport` interface for those.
@@ -55,7 +98,11 @@ that entry, and a failure from a timer-triggered flush is rethrown by the
55
98
  next `flush()` or `close()`.
56
99
 
57
100
  `transportTimeout` (default 10s) bounds every transport write, so a
58
- transport that stops responding cannot hang `flush()` or `close()`.
101
+ transport that stops responding cannot hang `flush()` or `close()`. A
102
+ write that exceeds it fails with `LoggerTimeoutError`, carrying
103
+ `transportName` and `timeout`; other write failures are reported as
104
+ `LoggerTransportError` with `transportName` set, and formatter failures
105
+ as `LoggerFormatterError` with `formatterName` set.
59
106
 
60
107
  `throwTransportErrors` (default `false`) rethrows transport and formatter
61
108
  failures instead of dropping them. A synchronous transport throws from the
@@ -88,8 +135,14 @@ against `DEFAULT_LOGGER_SECRET_FIELDS` — password, passphrase, secret,
88
135
  token, jwt, bearer, auth, authorization, cookie, session, sid,
89
136
  credential, api key, private key, client secret, card number, cvv, ssn,
90
137
  pin, otp and more — so `sessionId` and `cardNumber` are redacted while
91
- `passenger` and `authorId` are not. Nested objects, arrays and getters
92
- are all covered. Passing `redact.pattern` replaces the word matcher with
138
+ `passenger` and `authorId` are not. Nested objects, arrays, `Map`,
139
+ `Set` and getters are all covered — a `Map` is redacted per key and a
140
+ `Set` becomes an array. A getter that THROWS yields `"[Unreadable]"`
141
+ for that field: the rest of the entry is logged and the read failure is
142
+ reported through the same path as transport and formatter failures
143
+ (dropped by default, rethrown with `throwTransportErrors`), so a lazy
144
+ ORM relation can never abort the caller's log statement. Passing
145
+ `redact.pattern` replaces the word matcher with
93
146
  your own RegExp (the old substring default is still exported as
94
147
  `DEFAULT_LOGGER_SECRET_PATTERN`).
95
148
 
@@ -115,7 +168,10 @@ indented so none can start at column 0 and pass for a record. The JSON formatter
115
168
 
116
169
  Metadata is normalized before serialization, so circular references
117
170
  (`"[Circular]"`), BigInt values and functions never make a formatter
118
- throw and silently drop the record.
171
+ throw and silently drop the record. Only a genuine back-edge becomes
172
+ `"[Circular]"` — the walk tracks the ancestor path, so an object
173
+ referenced from two places in one payload (`{ actor: user, target: user }`)
174
+ is logged in full both times.
119
175
 
120
176
  ## Context
121
177
 
@@ -150,6 +206,19 @@ Pass `{ colors: true }` in the formatter context to colourize the level
150
206
  tag of text output. Colour codes are emitted only around the fixed level
151
207
  name, never around user-supplied text.
152
208
 
209
+ A formatter returns either a string or an object
210
+ (`LoggerFormattedOutput`). A string becomes the payload's `formatted`
211
+ line (`message` stays the raw message); an object is merged OVER the
212
+ entry, so `createStructuredLoggerFormatter()` hands the transport its
213
+ structured record (with an ISO-string `timestamp` and serialized
214
+ metadata) rather than the raw entry, and `formatted` is the record as one
215
+ JSON line. See "What a transport receives".
216
+
217
+ `includeStackTrace: false` on the text formatter leaves every stack out:
218
+ an entry's error prints as `error={"name":"Error","message":"..."}`, and
219
+ an `Error` inside metadata is serialized without its stack, so no frame
220
+ (and no absolute file path) reaches the log.
221
+
153
222
  ## Use Cases
154
223
 
155
224
  - Application logging
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * ContextLogger wrapper implementation.
3
3
  */
4
- import type { LoggerLevel } from "../../loggerLevel/loggerLevel.type.js";
4
+ import type { LoggerLevel, LoggerLevelLike } from "../../loggerLevel/loggerLevel.type.js";
5
5
  import type { LogMetadata } from "../../loggerEntry/loggerEntry.type.js";
6
6
  import type { LoggerContext, LoggerContextStorage } from "../../loggerContext/loggerContext.core.js";
7
- import type { ChildLoggerOptions, LogOptions } from "../../loggerOptions/loggerOptions.type.js";
7
+ import type { ChildLoggerOptionsInput, LogOptions } from "../../loggerOptions/loggerOptions.type.js";
8
8
  import type { Logger } from "./loggerCore.type.js";
9
9
  /**
10
10
  * Logger wrapper that provides scoped context.
@@ -30,9 +30,9 @@ export declare class ContextLogger implements Logger {
30
30
  debug(message: string, metadata?: LogMetadata): void;
31
31
  trace(message: string, metadata?: LogMetadata): void;
32
32
  log(level: LoggerLevel, message: string, options?: LogOptions): void;
33
- child(options?: ChildLoggerOptions): Logger;
33
+ child(options?: ChildLoggerOptionsInput): Logger;
34
34
  withContext(context: LoggerContext): Logger;
35
- setLevel(level: LoggerLevel): void;
35
+ setLevel(level: LoggerLevelLike): void;
36
36
  enable(): void;
37
37
  disable(): void;
38
38
  flush(): Promise<void>;
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Core logger implementation.
3
3
  */
4
- import { LoggerLevel } from "../../loggerLevel/loggerLevel.type.js";
4
+ import { LoggerLevel, type LoggerLevelLike } from "../../loggerLevel/loggerLevel.type.js";
5
5
  import type { LogMetadata } from "../../loggerEntry/loggerEntry.type.js";
6
6
  import type { LoggerContext, LoggerContextStorage } from "../../loggerContext/loggerContext.core.js";
7
- import type { ChildLoggerOptions, LoggerConfiguration, LoggerOptions, LogOptions } from "../../loggerOptions/loggerOptions.type.js";
7
+ import type { ChildLoggerOptionsInput, LoggerConfiguration, LoggerOptions, LogOptions } from "../../loggerOptions/loggerOptions.type.js";
8
8
  import type { Logger } from "./loggerCore.type.js";
9
9
  /**
10
10
  * Internal context passed to extracted methods.
@@ -54,9 +54,9 @@ export declare class ZudojsLogger implements Logger, ZudojsLoggerContext {
54
54
  debug(message: string, metadata?: LogMetadata): void;
55
55
  trace(message: string, metadata?: LogMetadata): void;
56
56
  log(level: LoggerLevel, message: string, options?: LogOptions): void;
57
- child(options?: ChildLoggerOptions): Logger;
57
+ child(options?: ChildLoggerOptionsInput): Logger;
58
58
  withContext(context: LoggerContext): Logger;
59
- setLevel(level: LoggerLevel): void;
59
+ setLevel(level: LoggerLevelLike): void;
60
60
  enable(): void;
61
61
  disable(): void;
62
62
  flush(): Promise<void>;
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Core logger implementation.
3
3
  */
4
- import { LoggerLevel } from "../../loggerLevel/loggerLevel.type.js";
4
+ import { LoggerLevel, } from "../../loggerLevel/loggerLevel.type.js";
5
5
  import { createLoggerContextStorage } from "../../loggerContext/loggerContextStorage.js";
6
6
  import { resolveLoggerOptions } from "../../loggerOptions/loggerOptions.type.js";
7
7
  import { normalizeConfiguration, assertActive as assertActiveHelper, assertMutable as assertMutableHelper, handleInfrastructureError as handleInfrastructureErrorHelper, } from "../helpers/loggerCoreHelpers.js";
8
- import { logAtLevel, childLogger, withContextLogger, setLoggerLevel, enableLogger, disableLogger, flushLogger, closeLogger, } from "../helpers/loggerCoreMethods/index.js";
8
+ import { logAtLevel, levelOptions, childLogger, withContextLogger, setLoggerLevel, enableLogger, disableLogger, flushLogger, closeLogger, } from "../helpers/loggerCoreMethods/index.js";
9
9
  import { getLoggerName, getLoggerLevel, getLoggerEnabled, } from "../helpers/loggerCoreMethods.loggerProps.js";
10
10
  /** Upper bound on dispatch failures retained between two flushes. */
11
11
  const MAX_RETAINED_DISPATCH_FAILURES = 32;
@@ -48,22 +48,22 @@ export class ZudojsLogger {
48
48
  return getLoggerEnabled(this);
49
49
  }
50
50
  fatal(message, metadata) {
51
- logAtLevel(this, LoggerLevel.FATAL, message, { metadata });
51
+ logAtLevel(this, LoggerLevel.FATAL, message, levelOptions(metadata));
52
52
  }
53
53
  error(message, metadata) {
54
- logAtLevel(this, LoggerLevel.ERROR, message, { metadata });
54
+ logAtLevel(this, LoggerLevel.ERROR, message, levelOptions(metadata));
55
55
  }
56
56
  warn(message, metadata) {
57
- logAtLevel(this, LoggerLevel.WARN, message, { metadata });
57
+ logAtLevel(this, LoggerLevel.WARN, message, levelOptions(metadata));
58
58
  }
59
59
  info(message, metadata) {
60
- logAtLevel(this, LoggerLevel.INFO, message, { metadata });
60
+ logAtLevel(this, LoggerLevel.INFO, message, levelOptions(metadata));
61
61
  }
62
62
  debug(message, metadata) {
63
- logAtLevel(this, LoggerLevel.DEBUG, message, { metadata });
63
+ logAtLevel(this, LoggerLevel.DEBUG, message, levelOptions(metadata));
64
64
  }
65
65
  trace(message, metadata) {
66
- logAtLevel(this, LoggerLevel.TRACE, message, { metadata });
66
+ logAtLevel(this, LoggerLevel.TRACE, message, levelOptions(metadata));
67
67
  }
68
68
  log(level, message, options = {}) {
69
69
  logAtLevel(this, level, message, options);
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Logger core types and interfaces.
3
3
  */
4
- import type { LoggerLevel } from "../../loggerLevel/loggerLevel.type.js";
4
+ import type { LoggerLevel, LoggerLevelLike } from "../../loggerLevel/loggerLevel.type.js";
5
5
  import type { LogMetadata } from "../../loggerEntry/loggerEntry.type.js";
6
6
  import type { LoggerContext } from "../../loggerContext/loggerContext.core.js";
7
- import type { ChildLoggerOptions, LogOptions } from "../../loggerOptions/loggerOptions.type.js";
7
+ import type { ChildLoggerOptionsInput, LogOptions } from "../../loggerOptions/loggerOptions.type.js";
8
8
  /**
9
9
  * Main Zudojs logger contract.
10
10
  */
@@ -19,9 +19,19 @@ export interface Logger {
19
19
  debug(message: string, metadata?: LogMetadata): void;
20
20
  trace(message: string, metadata?: LogMetadata): void;
21
21
  log(level: LoggerLevel, message: string, options?: LogOptions): void;
22
- child(options?: ChildLoggerOptions): Logger;
22
+ /**
23
+ * Creates a child logger. `level` may be a name (`"debug"`); an
24
+ * implementation declared with `ChildLoggerOptions` still satisfies this
25
+ * interface and can resolve a name with `resolveLoggerLevel()`.
26
+ */
27
+ child(options?: ChildLoggerOptionsInput): Logger;
23
28
  withContext(context: LoggerContext): Logger;
24
- setLevel(level: LoggerLevel): void;
29
+ /**
30
+ * Sets the threshold. Accepts `LoggerLevel.ERROR` or a name such as
31
+ * `"error"` (any case). An implementation declared with a `LoggerLevel`
32
+ * parameter still satisfies this interface.
33
+ */
34
+ setLevel(level: LoggerLevelLike): void;
25
35
  enable(): void;
26
36
  disable(): void;
27
37
  flush(): Promise<void>;
@@ -3,12 +3,12 @@
3
3
  */
4
4
  import type { LogMetadata } from "../../loggerEntry/loggerEntry.type.js";
5
5
  import type { LoggerContext } from "../../loggerContext/loggerContext.core.js";
6
- import type { ChildLoggerOptions } from "../../loggerOptions/loggerOptions.type.js";
6
+ import type { ChildLoggerOptionsInput } from "../../loggerOptions/loggerOptions.type.js";
7
7
  import type { Logger } from "../core/loggerCore.type.js";
8
8
  /**
9
9
  * Creates a child logger.
10
10
  */
11
- export declare function createChildLogger(parent: Logger, options?: ChildLoggerOptions): Logger;
11
+ export declare function createChildLogger(parent: Logger, options?: ChildLoggerOptionsInput): Logger;
12
12
  /**
13
13
  * Creates a logger specifically for an error.
14
14
  */
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export { createEntry } from "./loggerCoreMethods.entry.js";
7
7
  export { dispatchEntry, dispatchEntrySync, } from "./loggerCoreMethods.dispatch.js";
8
- export { logAtLevel } from "./loggerCoreMethods.level.js";
8
+ export { logAtLevel, levelOptions } from "./loggerCoreMethods.level.js";
9
9
  export { childLogger, withContextLogger } from "./loggerCoreMethods.child.js";
10
10
  export { setLoggerLevel, enableLogger, disableLogger, flushLogger, closeLogger, } from "./loggerCoreMethods.lifecycle.js";
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export { createEntry } from "./loggerCoreMethods.entry.js";
7
7
  export { dispatchEntry, dispatchEntrySync, } from "./loggerCoreMethods.dispatch.js";
8
- export { logAtLevel } from "./loggerCoreMethods.level.js";
8
+ export { logAtLevel, levelOptions } from "./loggerCoreMethods.level.js";
9
9
  export { childLogger, withContextLogger } from "./loggerCoreMethods.child.js";
10
10
  export { setLoggerLevel, enableLogger, disableLogger, flushLogger, closeLogger, } from "./loggerCoreMethods.lifecycle.js";
11
11
  //# sourceMappingURL=index.js.map
@@ -2,13 +2,13 @@
2
2
  * ZudojsLogger child and context methods.
3
3
  */
4
4
  import type { LoggerContext } from "../../../loggerContext/loggerContext.core.js";
5
- import type { ChildLoggerOptions } from "../../../loggerOptions/loggerOptions.type.js";
5
+ import type { ChildLoggerOptionsInput } from "../../../loggerOptions/loggerOptions.type.js";
6
6
  import type { Logger } from "../../core/loggerCore.type.js";
7
7
  import type { ZudojsLoggerContext } from "../../core/loggerCore.core.js";
8
8
  /**
9
9
  * Creates a child logger.
10
10
  */
11
- export declare function childLogger(ctx: ZudojsLoggerContext, options?: ChildLoggerOptions): Logger;
11
+ export declare function childLogger(ctx: ZudojsLoggerContext, options?: ChildLoggerOptionsInput): Logger;
12
12
  /**
13
13
  * Creates a logger with scoped context.
14
14
  */
@@ -4,8 +4,9 @@
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 { toLoggerError } from "../../../loggerErrors/loggerError.helpers.js";
7
+ import { toJsonLogLine } from "../../../loggerTransport/loggerTransportHelpers/loggerTransportHelpers.js";
8
+ import { LoggerFormatterError, LoggerTimeoutError, LoggerTransportError, } from "../../../loggerErrors/loggerError.base.js";
9
+ import { createLoggerFormatterError, createLoggerTransportError, } from "../../../loggerErrors/loggerError.helpers.js";
9
10
  /**
10
11
  * Default formatter used when no formatter is configured.
11
12
  */
@@ -33,7 +34,11 @@ async function withTransportTimeout(timeoutMs, transportName, operation) {
33
34
  let timer;
34
35
  const expiry = new Promise((_resolve, reject) => {
35
36
  timer = setTimeout(() => {
36
- reject(new LoggerTransportError(`Transport "${transportName}" did not complete within ${timeoutMs}ms.`));
37
+ // LoggerTimeoutError carries `transportName` and `timeout`, which
38
+ // the bare LoggerTransportError raised here before did not — so
39
+ // `catch (e) { if (e instanceof LoggerTimeoutError) retry() }`
40
+ // could never match.
41
+ reject(new LoggerTimeoutError(transportName, timeoutMs));
37
42
  }, timeoutMs);
38
43
  // This timer bounds a write; it is not work in its own right. Left
39
44
  // referenced it keeps the event loop alive, so a process that has
@@ -53,20 +58,60 @@ async function withTransportTimeout(timeoutMs, transportName, operation) {
53
58
  void operation.catch(() => { });
54
59
  }
55
60
  }
61
+ /**
62
+ * Builds the payload handed to a transport.
63
+ *
64
+ * `LoggerFormattedOutput` is `string | Record<string, unknown>`. A string
65
+ * is the formatted line and is carried in `formatted`; `message` stays the
66
+ * caller's raw message. (It used to be replaced by the line, so a transport
67
+ * could not get the message back without parsing it out again.) An OBJECT
68
+ * is the formatted record: it is merged over the entry, so a transport sees
69
+ * the formatter's fields, and `formatted` is that record as one JSON line.
70
+ */
71
+ function toTransportPayload(entry, formatted) {
72
+ if (typeof formatted === "string") {
73
+ return { ...entry, formatted };
74
+ }
75
+ if (formatted !== null &&
76
+ typeof formatted === "object" &&
77
+ !Array.isArray(formatted)) {
78
+ return {
79
+ ...entry,
80
+ ...formatted,
81
+ formatted: toJsonLogLine(formatted),
82
+ };
83
+ }
84
+ return entry;
85
+ }
86
+ /** Name of the configured formatter, for error reporting. */
87
+ function resolveFormatterName(configuration) {
88
+ const formatter = configuration.formatter;
89
+ if (typeof formatter === "string") {
90
+ return formatter;
91
+ }
92
+ return formatter.name ?? "formatter";
93
+ }
94
+ /** Wraps a transport failure, keeping an already-typed logger error. */
95
+ function toTransportError(transportName, error) {
96
+ return error instanceof LoggerTransportError
97
+ ? error
98
+ : createLoggerTransportError(transportName, error);
99
+ }
100
+ /** Wraps a formatter failure, keeping an already-typed logger error. */
101
+ function toFormatterError(formatterName, error) {
102
+ return error instanceof LoggerFormatterError
103
+ ? error
104
+ : createLoggerFormatterError(formatterName, error);
105
+ }
56
106
  /**
57
107
  * Writes to a transport.
58
108
  */
59
- async function writeTransport(configuration, transport, entry, formatted) {
109
+ async function writeTransport(configuration, transport, payload) {
60
110
  const transportContext = {
61
111
  loggerName: configuration.name,
62
112
  environment: configuration.environment,
63
113
  };
64
- if (typeof formatted === "string") {
65
- const formattedEntry = { ...entry, message: formatted };
66
- await writeLoggerTransport(transport.transport, formattedEntry, transportContext);
67
- return;
68
- }
69
- await writeLoggerTransport(transport.transport, entry, transportContext);
114
+ await writeLoggerTransport(transport.transport, payload, transportContext);
70
115
  }
71
116
  /**
72
117
  * Formats an entry with the configured formatter.
@@ -89,12 +134,11 @@ function formatEntry(configuration, entry) {
89
134
  * the transport directly lets a synchronous console/array transport
90
135
  * complete inline, which is what `asynchronous: false` promises.
91
136
  */
92
- function writeTransportMaybeSync(configuration, transport, entry, formatted) {
137
+ function writeTransportMaybeSync(configuration, transport, payload) {
93
138
  const transportContext = {
94
139
  loggerName: configuration.name,
95
140
  environment: configuration.environment,
96
141
  };
97
- const payload = typeof formatted === "string" ? { ...entry, message: formatted } : entry;
98
142
  const target = transport.transport;
99
143
  return typeof target === "function"
100
144
  ? target(payload, transportContext)
@@ -104,29 +148,29 @@ function writeTransportMaybeSync(configuration, transport, entry, formatted) {
104
148
  * Dispatches an entry to the configured transports.
105
149
  */
106
150
  export async function dispatchEntry(configuration, entry, handleError) {
107
- let formatted;
151
+ let payload;
108
152
  try {
109
- formatted = formatEntry(configuration, entry);
153
+ payload = toTransportPayload(entry, formatEntry(configuration, entry));
110
154
  }
111
155
  catch (error) {
112
- const formatterError = new LoggerFormatterError(`Failed to format log entry: ${toLoggerError(error).message}`, { cause: error });
113
- handleError(formatterError);
156
+ handleError(toFormatterError(resolveFormatterName(configuration), error));
114
157
  return;
115
158
  }
116
159
  for (const transport of configuration.transports) {
160
+ let transportName = "transport";
117
161
  try {
118
162
  if (!isLoggerTransport(transport)) {
119
163
  continue;
120
164
  }
121
165
  const registered = createLoggerTransport(transport);
166
+ transportName = registered.name;
122
167
  if (!registered.enabled) {
123
168
  continue;
124
169
  }
125
- await withTransportTimeout(configuration.transportTimeout, registered.name, writeTransport(configuration, registered, entry, formatted));
170
+ await withTransportTimeout(configuration.transportTimeout, registered.name, writeTransport(configuration, registered, payload));
126
171
  }
127
172
  catch (error) {
128
- const transportError = new LoggerTransportError(`Failed to write log entry: ${toLoggerError(error).message}`, { cause: error });
129
- handleError(transportError);
173
+ handleError(toTransportError(transportName, error));
130
174
  }
131
175
  }
132
176
  }
@@ -148,33 +192,35 @@ export function dispatchEntrySync(configuration, entry, handleError) {
148
192
  // caller off the transport's critical path.
149
193
  return Promise.resolve().then(() => dispatchEntry(configuration, entry, handleError));
150
194
  }
151
- let formatted;
195
+ let payload;
152
196
  try {
153
- formatted = formatEntry(configuration, entry);
197
+ payload = toTransportPayload(entry, formatEntry(configuration, entry));
154
198
  }
155
199
  catch (error) {
156
- handleError(new LoggerFormatterError(`Failed to format log entry: ${toLoggerError(error).message}`, { cause: error }));
200
+ handleError(toFormatterError(resolveFormatterName(configuration), error));
157
201
  return;
158
202
  }
159
203
  const pending = [];
160
204
  for (const transport of configuration.transports) {
205
+ let transportName = "transport";
161
206
  try {
162
207
  if (!isLoggerTransport(transport)) {
163
208
  continue;
164
209
  }
165
210
  const registered = createLoggerTransport(transport);
211
+ transportName = registered.name;
166
212
  if (!registered.enabled) {
167
213
  continue;
168
214
  }
169
- const result = writeTransportMaybeSync(configuration, registered, entry, formatted);
215
+ const result = writeTransportMaybeSync(configuration, registered, payload);
170
216
  if (result instanceof Promise) {
171
217
  pending.push(withTransportTimeout(configuration.transportTimeout, registered.name, result).catch((error) => {
172
- handleError(new LoggerTransportError(`Failed to write log entry: ${toLoggerError(error).message}`, { cause: error }));
218
+ handleError(toTransportError(registered.name, error));
173
219
  }));
174
220
  }
175
221
  }
176
222
  catch (error) {
177
- handleError(new LoggerTransportError(`Failed to write log entry: ${toLoggerError(error).message}`, { cause: error }));
223
+ handleError(toTransportError(transportName, error));
178
224
  }
179
225
  }
180
226
  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,
@@ -4,6 +4,13 @@
4
4
  import { LoggerLevel } from "../../../loggerLevel/loggerLevel.type.js";
5
5
  import type { LogOptions } from "../../../loggerOptions/loggerOptions.type.js";
6
6
  import type { ZudojsLoggerContext } from "../../core/loggerCore.core.js";
7
+ /**
8
+ * Turns the second argument of `logger.error(message, x)` and friends into
9
+ * log options. An `Error` becomes the entry's `error` (with its stack)
10
+ * instead of being read as metadata, where it has no enumerable fields and
11
+ * vanished without trace.
12
+ */
13
+ export declare function levelOptions(metadata: LogOptions["metadata"] | undefined): LogOptions;
7
14
  /**
8
15
  * Level logging methods extracted from ZudojsLogger.
9
16
  */
@@ -2,9 +2,22 @@
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";
9
+ /**
10
+ * Turns the second argument of `logger.error(message, x)` and friends into
11
+ * log options. An `Error` becomes the entry's `error` (with its stack)
12
+ * instead of being read as metadata, where it has no enumerable fields and
13
+ * vanished without trace.
14
+ */
15
+ export function levelOptions(metadata) {
16
+ // The static type says metadata, but JavaScript callers (and code typed
17
+ // loosely) routinely pass the caught error here.
18
+ const value = metadata;
19
+ return value instanceof Error ? { error: value } : { metadata };
20
+ }
8
21
  /**
9
22
  * Level logging methods extracted from ZudojsLogger.
10
23
  */
@@ -17,7 +30,24 @@ export function logAtLevel(ctx, level, message, options = {}) {
17
30
  if (typeof message !== "string") {
18
31
  throw new LoggerConfigurationError("Logger message must be a string.");
19
32
  }
20
- const entry = createEntry(ctx.configuration, ctx.contextStorage, level, message, options);
33
+ // Entry construction reads caller-supplied metadata, including
34
+ // getters. A throwing accessor used to propagate straight out of
35
+ // logger.info(...) and abort the caller — and only when the level
36
+ // let the call through, so the same code was a silent no-op at one
37
+ // log level and a crash at another. The offending field becomes a
38
+ // marker and the failure is reported like every other infrastructure
39
+ // failure, AFTER the line has been dispatched.
40
+ const metadataFailures = [];
41
+ let entry;
42
+ try {
43
+ entry = createEntry(ctx.configuration, ctx.contextStorage, level, message, options, (key, error) => {
44
+ metadataFailures.push(new InvalidLoggerEntryError(`Failed to read log metadata field "${key}": ${toLoggerError(error).message}`, { cause: error }));
45
+ });
46
+ }
47
+ catch (error) {
48
+ ctx.handleInfrastructureError(new InvalidLoggerEntryError(`Failed to build log entry: ${toLoggerError(error).message}`, { cause: error }));
49
+ return;
50
+ }
21
51
  // Dispatch is asynchronous. It used to be fired and forgotten, so
22
52
  // flush() and close() could return while entries were still in
23
53
  // flight — messages were lost on process exit. Registering the
@@ -26,5 +56,8 @@ export function logAtLevel(ctx, level, message, options = {}) {
26
56
  if (dispatch) {
27
57
  ctx.trackDispatch(dispatch);
28
58
  }
59
+ for (const failure of metadataFailures) {
60
+ ctx.handleInfrastructureError(failure);
61
+ }
29
62
  }
30
63
  //# sourceMappingURL=loggerCoreMethods.level.js.map
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * ZudojsLogger lifecycle methods.
3
3
  */
4
- import { LoggerLevel } from "../../../loggerLevel/loggerLevel.type.js";
4
+ import { type LoggerLevelLike } from "../../../loggerLevel/loggerLevel.type.js";
5
5
  import type { ZudojsLoggerContext } from "../../core/loggerCore.core.js";
6
6
  /**
7
- * Sets the logger level.
7
+ * Sets the logger level from an enum value or a case-insensitive name.
8
8
  */
9
- export declare function setLoggerLevel(ctx: ZudojsLoggerContext, level: LoggerLevel): void;
9
+ export declare function setLoggerLevel(ctx: ZudojsLoggerContext, level: LoggerLevelLike): void;
10
10
  /**
11
11
  * Enables the logger.
12
12
  */