@zudojs/logger 1.3.0 → 1.4.1

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 (34) hide show
  1. package/README.md +61 -6
  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 +18 -17
  11. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.level.d.ts +7 -0
  12. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.level.js +12 -0
  13. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.lifecycle.d.ts +3 -3
  14. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.lifecycle.js +8 -6
  15. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.interfaces.d.ts +11 -1
  16. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.valueSerialize.d.ts +7 -3
  17. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.valueSerialize.js +15 -13
  18. package/dist/loggerFactory/loggerFactory.core.d.ts +2 -2
  19. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.metadata.d.ts +5 -2
  20. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.metadata.js +12 -5
  21. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.text.js +8 -4
  22. package/dist/loggerLevel/loggerLevel.type.d.ts +13 -0
  23. package/dist/loggerLevel/loggerLevel.type.js +14 -0
  24. package/dist/loggerOptions/loggerOptions.redact.d.ts +16 -0
  25. package/dist/loggerOptions/loggerOptions.redact.js +22 -0
  26. package/dist/loggerOptions/loggerOptions.type.d.ts +17 -5
  27. package/dist/loggerOptions/loggerOptions.type.js +9 -4
  28. package/dist/loggerTransport/loggerTransportConsole/loggerTransportConsole.core.d.ts +3 -2
  29. package/dist/loggerTransport/loggerTransportConsole/loggerTransportConsole.core.js +9 -4
  30. package/dist/loggerTransport/loggerTransportHelpers/index.d.ts +1 -1
  31. package/dist/loggerTransport/loggerTransportHelpers/index.js +1 -1
  32. package/dist/loggerTransport/loggerTransportHelpers/loggerTransportHelpers.d.ts +15 -0
  33. package/dist/loggerTransport/loggerTransportHelpers/loggerTransportHelpers.js +22 -0
  34. 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.
@@ -108,9 +151,14 @@ logger.info("login", { user: "alice", password: "hunter2" });
108
151
  // metadata: { user: "alice", password: "[REDACTED]" }
109
152
 
110
153
  createLogger({ redact: { keys: ["ssn"], replacement: "***" } });
111
- createLogger({ redact: { enabled: false } }); // opt out
154
+ createLogger({ redact: false }); // opt out
155
+ createLogger({ redact: { enabled: false } }); // same as redact: false
112
156
  ```
113
157
 
158
+ `redact` takes a boolean or an options object (`enabled`, `keys`, `pattern`,
159
+ `replacement`). `true` or omitting it keeps the default. Child loggers inherit
160
+ the parent's setting.
161
+
114
162
  ## Log injection
115
163
 
116
164
  Text-shaped formatters escape control characters (C0, DEL, C1 and
@@ -164,10 +212,17 @@ tag of text output. Colour codes are emitted only around the fixed level
164
212
  name, never around user-supplied text.
165
213
 
166
214
  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.
215
+ (`LoggerFormattedOutput`). A string becomes the payload's `formatted`
216
+ line (`message` stays the raw message); an object is merged OVER the
217
+ entry, so `createStructuredLoggerFormatter()` hands the transport its
218
+ structured record (with an ISO-string `timestamp` and serialized
219
+ metadata) rather than the raw entry, and `formatted` is the record as one
220
+ JSON line. See "What a transport receives".
221
+
222
+ `includeStackTrace: false` on the text formatter leaves every stack out:
223
+ an entry's error prints as `error={"name":"Error","message":"..."}`, and
224
+ an `Error` inside metadata is serialized without its stack, so no frame
225
+ (and no absolute file path) reaches the log.
171
226
 
172
227
  ## Use Cases
173
228
 
@@ -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,6 +4,7 @@
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 { toJsonLogLine } from "../../../loggerTransport/loggerTransportHelpers/loggerTransportHelpers.js";
7
8
  import { LoggerFormatterError, LoggerTimeoutError, LoggerTransportError, } from "../../../loggerErrors/loggerError.base.js";
8
9
  import { createLoggerFormatterError, createLoggerTransportError, } from "../../../loggerErrors/loggerError.helpers.js";
9
10
  /**
@@ -60,16 +61,16 @@ async function withTransportTimeout(timeoutMs, transportName, operation) {
60
61
  /**
61
62
  * Builds the payload handed to a transport.
62
63
  *
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.
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.
69
70
  */
70
71
  function toTransportPayload(entry, formatted) {
71
72
  if (typeof formatted === "string") {
72
- return { ...entry, message: formatted };
73
+ return { ...entry, formatted };
73
74
  }
74
75
  if (formatted !== null &&
75
76
  typeof formatted === "object" &&
@@ -77,6 +78,7 @@ function toTransportPayload(entry, formatted) {
77
78
  return {
78
79
  ...entry,
79
80
  ...formatted,
81
+ formatted: toJsonLogLine(formatted),
80
82
  };
81
83
  }
82
84
  return entry;
@@ -104,12 +106,12 @@ function toFormatterError(formatterName, error) {
104
106
  /**
105
107
  * Writes to a transport.
106
108
  */
107
- async function writeTransport(configuration, transport, entry, formatted) {
109
+ async function writeTransport(configuration, transport, payload) {
108
110
  const transportContext = {
109
111
  loggerName: configuration.name,
110
112
  environment: configuration.environment,
111
113
  };
112
- await writeLoggerTransport(transport.transport, toTransportPayload(entry, formatted), transportContext);
114
+ await writeLoggerTransport(transport.transport, payload, transportContext);
113
115
  }
114
116
  /**
115
117
  * Formats an entry with the configured formatter.
@@ -132,12 +134,11 @@ function formatEntry(configuration, entry) {
132
134
  * the transport directly lets a synchronous console/array transport
133
135
  * complete inline, which is what `asynchronous: false` promises.
134
136
  */
135
- function writeTransportMaybeSync(configuration, transport, entry, formatted) {
137
+ function writeTransportMaybeSync(configuration, transport, payload) {
136
138
  const transportContext = {
137
139
  loggerName: configuration.name,
138
140
  environment: configuration.environment,
139
141
  };
140
- const payload = toTransportPayload(entry, formatted);
141
142
  const target = transport.transport;
142
143
  return typeof target === "function"
143
144
  ? target(payload, transportContext)
@@ -147,9 +148,9 @@ function writeTransportMaybeSync(configuration, transport, entry, formatted) {
147
148
  * Dispatches an entry to the configured transports.
148
149
  */
149
150
  export async function dispatchEntry(configuration, entry, handleError) {
150
- let formatted;
151
+ let payload;
151
152
  try {
152
- formatted = formatEntry(configuration, entry);
153
+ payload = toTransportPayload(entry, formatEntry(configuration, entry));
153
154
  }
154
155
  catch (error) {
155
156
  handleError(toFormatterError(resolveFormatterName(configuration), error));
@@ -166,7 +167,7 @@ export async function dispatchEntry(configuration, entry, handleError) {
166
167
  if (!registered.enabled) {
167
168
  continue;
168
169
  }
169
- await withTransportTimeout(configuration.transportTimeout, registered.name, writeTransport(configuration, registered, entry, formatted));
170
+ await withTransportTimeout(configuration.transportTimeout, registered.name, writeTransport(configuration, registered, payload));
170
171
  }
171
172
  catch (error) {
172
173
  handleError(toTransportError(transportName, error));
@@ -191,9 +192,9 @@ export function dispatchEntrySync(configuration, entry, handleError) {
191
192
  // caller off the transport's critical path.
192
193
  return Promise.resolve().then(() => dispatchEntry(configuration, entry, handleError));
193
194
  }
194
- let formatted;
195
+ let payload;
195
196
  try {
196
- formatted = formatEntry(configuration, entry);
197
+ payload = toTransportPayload(entry, formatEntry(configuration, entry));
197
198
  }
198
199
  catch (error) {
199
200
  handleError(toFormatterError(resolveFormatterName(configuration), error));
@@ -211,7 +212,7 @@ export function dispatchEntrySync(configuration, entry, handleError) {
211
212
  if (!registered.enabled) {
212
213
  continue;
213
214
  }
214
- const result = writeTransportMaybeSync(configuration, registered, entry, formatted);
215
+ const result = writeTransportMaybeSync(configuration, registered, payload);
215
216
  if (result instanceof Promise) {
216
217
  pending.push(withTransportTimeout(configuration.transportTimeout, registered.name, result).catch((error) => {
217
218
  handleError(toTransportError(registered.name, error));
@@ -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
  */
@@ -6,6 +6,18 @@ import { InvalidLoggerEntryError, LoggerConfigurationError, } from "../../../log
6
6
  import { toLoggerError } from "../../../loggerErrors/loggerError.helpers.js";
7
7
  import { createEntry } from "./loggerCoreMethods.entry.js";
8
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
+ }
9
21
  /**
10
22
  * Level logging methods extracted from ZudojsLogger.
11
23
  */
@@ -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
  */
@@ -1,25 +1,27 @@
1
1
  /**
2
2
  * ZudojsLogger lifecycle methods.
3
3
  */
4
- import { LoggerLevel } from "../../../loggerLevel/loggerLevel.type.js";
4
+ import { resolveLoggerLevel, } from "../../../loggerLevel/loggerLevel.type.js";
5
5
  import { createLoggerTransport } from "../../../loggerTransport/loggerTransport.core.js";
6
6
  import { isLoggerTransport } from "../../../loggerTransport/loggerTransportGuard.js";
7
7
  import { LoggerConfigurationError } from "../../../loggerErrors/loggerError.base.js";
8
8
  import { throwCollectedFailures } from "../../../loggerErrors/loggerError.helpers.js";
9
9
  /**
10
- * Sets the logger level.
10
+ * Sets the logger level from an enum value or a case-insensitive name.
11
11
  */
12
12
  export function setLoggerLevel(ctx, level) {
13
13
  ctx.assertActive();
14
- if (!Number.isInteger(level) ||
15
- level < LoggerLevel.FATAL ||
16
- level > LoggerLevel.TRACE) {
14
+ let resolved;
15
+ try {
16
+ resolved = resolveLoggerLevel(level);
17
+ }
18
+ catch {
17
19
  throw new LoggerConfigurationError(`Invalid logger level: ${String(level)}.`);
18
20
  }
19
21
  ctx.assertMutable();
20
22
  ctx.updateConfiguration({
21
23
  ...ctx.configuration,
22
- level,
24
+ level: resolved,
23
25
  });
24
26
  }
25
27
  /**
@@ -20,9 +20,19 @@ export interface LoggerEntry {
20
20
  */
21
21
  readonly levelName: LoggerLevelName;
22
22
  /**
23
- * Human-readable message.
23
+ * The message as the caller logged it. A transport receives it unchanged;
24
+ * the formatter's rendering of the whole record is in `formatted`.
24
25
  */
25
26
  readonly message: string;
27
+ /**
28
+ * The record rendered as one line by the logger's formatter: the string a
29
+ * text or JSON formatter returned, or the JSON line of the record an
30
+ * object formatter (`createStructuredLoggerFormatter`) returned. Set on
31
+ * entries a logger hands to its transports. Line-oriented transports
32
+ * print `entry.formatted ?? entry.message`; `message` is always the raw
33
+ * message the caller logged.
34
+ */
35
+ readonly formatted?: string;
26
36
  /**
27
37
  * Structured metadata.
28
38
  */
@@ -3,19 +3,23 @@
3
3
  */
4
4
  /**
5
5
  * Serializes an error-like object into a plain object.
6
+ *
7
+ * @param includeStack - `false` leaves the stack out (its frames carry
8
+ * absolute file paths). Defaults to `true`.
6
9
  */
7
10
  export declare function serializeLoggerError(error: {
8
11
  name?: string;
9
12
  message: string;
10
13
  stack?: string;
11
- }): Record<string, unknown>;
14
+ }, includeStack?: boolean): Record<string, unknown>;
12
15
  /**
13
16
  * Converts arbitrary values into safer serializable values.
14
17
  *
15
18
  * `seen` tracks the ANCESTOR PATH only, so only a genuine back-edge
16
19
  * becomes "[Circular]"; `Map` and `Set` keep their contents; and a
17
20
  * property whose getter throws becomes "[Unreadable]" rather than
18
- * taking the whole log line down.
21
+ * taking the whole log line down. `includeErrorStack: false` leaves the
22
+ * stack out of every Error found in the value.
19
23
  */
20
- export declare function serializeLoggerValue(value: unknown, seen?: WeakSet<object>): unknown;
24
+ export declare function serializeLoggerValue(value: unknown, seen?: WeakSet<object>, includeErrorStack?: boolean): unknown;
21
25
  //# sourceMappingURL=loggerEntryHelpers.valueSerialize.d.ts.map
@@ -4,13 +4,14 @@
4
4
  import { LOGGER_UNREADABLE_TOKEN } from "./loggerEntryHelpers.sanitize.js";
5
5
  /**
6
6
  * Serializes an error-like object into a plain object.
7
+ *
8
+ * @param includeStack - `false` leaves the stack out (its frames carry
9
+ * absolute file paths). Defaults to `true`.
7
10
  */
8
- export function serializeLoggerError(error) {
9
- return {
10
- name: error.name,
11
- message: error.message,
12
- stack: error.stack,
13
- };
11
+ export function serializeLoggerError(error, includeStack = true) {
12
+ return includeStack
13
+ ? { name: error.name, message: error.message, stack: error.stack }
14
+ : { name: error.name, message: error.message };
14
15
  }
15
16
  /**
16
17
  * Converts arbitrary values into safer serializable values.
@@ -18,9 +19,10 @@ export function serializeLoggerError(error) {
18
19
  * `seen` tracks the ANCESTOR PATH only, so only a genuine back-edge
19
20
  * becomes "[Circular]"; `Map` and `Set` keep their contents; and a
20
21
  * property whose getter throws becomes "[Unreadable]" rather than
21
- * taking the whole log line down.
22
+ * taking the whole log line down. `includeErrorStack: false` leaves the
23
+ * stack out of every Error found in the value.
22
24
  */
23
- export function serializeLoggerValue(value, seen = new WeakSet()) {
25
+ export function serializeLoggerValue(value, seen = new WeakSet(), includeErrorStack = true) {
24
26
  if (value === null ||
25
27
  value === undefined ||
26
28
  typeof value === "string" ||
@@ -35,7 +37,7 @@ export function serializeLoggerValue(value, seen = new WeakSet()) {
35
37
  return value.toISOString();
36
38
  }
37
39
  if (value instanceof Error) {
38
- return serializeLoggerError(value);
40
+ return serializeLoggerError(value, includeErrorStack);
39
41
  }
40
42
  if (typeof value === "function") {
41
43
  return `[Function ${value.name || "anonymous"}]`;
@@ -52,10 +54,10 @@ export function serializeLoggerValue(value, seen = new WeakSet()) {
52
54
  seen.add(value);
53
55
  try {
54
56
  if (Array.isArray(value)) {
55
- return value.map((item) => serializeLoggerValue(item, seen));
57
+ return value.map((item) => serializeLoggerValue(item, seen, includeErrorStack));
56
58
  }
57
59
  if (value instanceof Set) {
58
- return Array.from(value, (item) => serializeLoggerValue(item, seen));
60
+ return Array.from(value, (item) => serializeLoggerValue(item, seen, includeErrorStack));
59
61
  }
60
62
  const result = {};
61
63
  // defineProperty, never assignment: a "__proto__" key from an
@@ -71,7 +73,7 @@ export function serializeLoggerValue(value, seen = new WeakSet()) {
71
73
  };
72
74
  if (value instanceof Map) {
73
75
  for (const [key, item] of value.entries()) {
74
- define(typeof key === "string" ? key : String(key), serializeLoggerValue(item, seen));
76
+ define(typeof key === "string" ? key : String(key), serializeLoggerValue(item, seen, includeErrorStack));
75
77
  }
76
78
  return result;
77
79
  }
@@ -84,7 +86,7 @@ export function serializeLoggerValue(value, seen = new WeakSet()) {
84
86
  define(key, LOGGER_UNREADABLE_TOKEN);
85
87
  continue;
86
88
  }
87
- define(key, serializeLoggerValue(item, seen));
89
+ define(key, serializeLoggerValue(item, seen, includeErrorStack));
88
90
  }
89
91
  return result;
90
92
  }
@@ -1,5 +1,5 @@
1
1
  import type { Logger } from "../loggerCore/core/loggerCore.type.js";
2
- import type { LoggerOptions, ChildLoggerOptions } from "../loggerOptions/loggerOptions.type.js";
2
+ import type { LoggerOptions, ChildLoggerOptionsInput } from "../loggerOptions/loggerOptions.type.js";
3
3
  /**
4
4
  * Factory responsible for creating and managing Zudojs loggers.
5
5
  *
@@ -29,7 +29,7 @@ export declare class LoggerFactory {
29
29
  /** Gets an existing logger or creates it. */
30
30
  getOrCreate(name: string, options?: LoggerOptions): Logger;
31
31
  /** Creates a child logger from an existing logger. */
32
- child(parent: Logger, options?: ChildLoggerOptions): Logger;
32
+ child(parent: Logger, options?: ChildLoggerOptionsInput): Logger;
33
33
  /**
34
34
  * Removes a logger from the factory registry.
35
35
  *
@@ -3,12 +3,15 @@
3
3
  */
4
4
  /**
5
5
  * Formats metadata as key=value pairs.
6
+ *
7
+ * @param includeStackTrace - `false` leaves the stack out of any Error in
8
+ * the metadata, as the text formatter's option of the same name promises.
6
9
  */
7
- export declare function formatMetadata(metadata: Record<string, unknown>, separator: string): string;
10
+ export declare function formatMetadata(metadata: Record<string, unknown>, separator: string, includeStackTrace?: boolean): string;
8
11
  /**
9
12
  * Formats an arbitrary metadata value.
10
13
  */
11
- export declare function formatValue(value: unknown): string;
14
+ export declare function formatValue(value: unknown, includeStackTrace?: boolean): string;
12
15
  /**
13
16
  * Formats an Error.
14
17
  *
@@ -5,19 +5,22 @@ import { serializeLoggerError, serializeLoggerValue, } from "../../loggerEntry/l
5
5
  import { escapeLogText } from "../../loggerEntry/loggerEntryHelpers/loggerEntryHelpers.sanitize.js";
6
6
  /**
7
7
  * Formats metadata as key=value pairs.
8
+ *
9
+ * @param includeStackTrace - `false` leaves the stack out of any Error in
10
+ * the metadata, as the text formatter's option of the same name promises.
8
11
  */
9
- export function formatMetadata(metadata, separator) {
12
+ export function formatMetadata(metadata, separator, includeStackTrace = true) {
10
13
  return (Object.entries(metadata)
11
14
  .filter(([, value]) => value !== undefined)
12
15
  // Metadata KEYS are as attacker-influenceable as values (a header
13
16
  // name, a form field) and were previously interpolated raw.
14
- .map(([key, value]) => `${escapeLogText(key)}=${formatValue(value)}`)
17
+ .map(([key, value]) => `${escapeLogText(key)}=${formatValue(value, includeStackTrace)}`)
15
18
  .join(separator));
16
19
  }
17
20
  /**
18
21
  * Formats an arbitrary metadata value.
19
22
  */
20
- export function formatValue(value) {
23
+ export function formatValue(value, includeStackTrace = true) {
21
24
  if (value === null) {
22
25
  return "null";
23
26
  }
@@ -31,7 +34,8 @@ export function formatValue(value) {
31
34
  return escapeLogText(value);
32
35
  }
33
36
  if (typeof value === "object") {
34
- return escapeLogText(JSON.stringify(serializeLoggerValue(value)));
37
+ const serialized = serializeLoggerValue(value, new WeakSet(), includeStackTrace);
38
+ return escapeLogText(JSON.stringify(serialized));
35
39
  }
36
40
  return escapeLogText(String(value));
37
41
  }
@@ -79,7 +83,10 @@ export function formatError(error, includeStackTrace) {
79
83
  }
80
84
  return `\n${lines.join("\n")}`;
81
85
  }
82
- const serialized = serializeLoggerError(error);
86
+ // Name and message only: the fallback used to serialize the stack as
87
+ // well, so `includeStackTrace: false` still printed every frame (and the
88
+ // absolute paths in them) inside the JSON.
89
+ const serialized = serializeLoggerError(error, false);
83
90
  return `error=${escapeLogText(JSON.stringify(serialized))}`;
84
91
  }
85
92
  //# sourceMappingURL=loggerFormatterFormatters.metadata.js.map
@@ -65,12 +65,16 @@ export function createTextLoggerFormatter(options = {}) {
65
65
  }
66
66
  }
67
67
  if (includeMetadata && Object.keys(entry.metadata).length > 0) {
68
- parts.push(formatMetadata(entry.metadata, metadataSeparator));
68
+ parts.push(formatMetadata(entry.metadata, metadataSeparator, includeStackTrace));
69
69
  }
70
- if (entry.error) {
71
- parts.push(formatError(entry.error, includeStackTrace));
70
+ const line = parts.join(" ");
71
+ if (!entry.error) {
72
+ return line;
72
73
  }
73
- return parts.join(" ");
74
+ // A stack starts on its own line; joining it with " " left a trailing
75
+ // space after the message.
76
+ const error = formatError(entry.error, includeStackTrace);
77
+ return error.startsWith("\n") ? `${line}${error}` : `${line} ${error}`;
74
78
  }, { name: options.name ?? "text" });
75
79
  }
76
80
  //# sourceMappingURL=loggerFormatterFormatters.text.js.map
@@ -14,10 +14,23 @@ export declare enum LoggerLevel {
14
14
  }
15
15
  /** String representation of supported logger levels. */
16
16
  export type LoggerLevelName = "fatal" | "error" | "warn" | "info" | "debug" | "trace";
17
+ /**
18
+ * A level as configuration accepts it: the enum value, or its name in any
19
+ * case (`"error"`, `"ERROR"`). `"warning"` and `"information"` are accepted
20
+ * at runtime as aliases of `"warn"` and `"info"`.
21
+ */
22
+ export type LoggerLevelLike = LoggerLevel | LoggerLevelName | Uppercase<LoggerLevelName>;
17
23
  /** Converts a logger level into its canonical name. */
18
24
  export declare function loggerLevelToName(level: LoggerLevel): LoggerLevelName;
19
25
  /** Converts a logger level name into its enum value. */
20
26
  export declare function loggerLevelFromName(name: LoggerLevelName | string): LoggerLevel;
27
+ /**
28
+ * Resolves a configured level (enum value or case-insensitive name) to its
29
+ * enum value.
30
+ *
31
+ * @throws InvalidLoggerLevelError when the value is neither.
32
+ */
33
+ export declare function resolveLoggerLevel(level: LoggerLevelLike): LoggerLevel;
21
34
  /** Checks whether a value is a valid LoggerLevel. */
22
35
  export declare function isLoggerLevel(value: unknown): value is LoggerLevel;
23
36
  /** Checks whether a value is a valid logger level name. */
@@ -54,6 +54,20 @@ export function loggerLevelFromName(name) {
54
54
  throw new InvalidLoggerLevelError(name);
55
55
  }
56
56
  }
57
+ /**
58
+ * Resolves a configured level (enum value or case-insensitive name) to its
59
+ * enum value.
60
+ *
61
+ * @throws InvalidLoggerLevelError when the value is neither.
62
+ */
63
+ export function resolveLoggerLevel(level) {
64
+ const value = level;
65
+ if (isLoggerLevel(value))
66
+ return value;
67
+ if (typeof value === "string")
68
+ return loggerLevelFromName(value.trim());
69
+ throw new InvalidLoggerLevelError(value);
70
+ }
57
71
  /** Checks whether a value is a valid LoggerLevel. */
58
72
  export function isLoggerLevel(value) {
59
73
  return (typeof value === "number" &&
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Normalization of the `redact` logger option.
3
+ */
4
+ import type { LoggerRedactionOptions } from "../loggerEntry/loggerEntryHelpers/loggerEntryHelpers.sanitize.js";
5
+ /**
6
+ * Resolves the `redact` option into frozen redaction settings.
7
+ *
8
+ * `false` turns redaction off and `true` (or omitting it) keeps the default.
9
+ * An object is used as given. Spreading `false` into an object yields `{}`,
10
+ * which is the default, so a boolean must be mapped explicitly.
11
+ *
12
+ * @param redact - The `redact` value passed to `createLogger`.
13
+ * @returns The normalized redaction settings.
14
+ */
15
+ export declare function resolveRedactionOptions(redact: boolean | LoggerRedactionOptions | undefined): LoggerRedactionOptions;
16
+ //# sourceMappingURL=loggerOptions.redact.d.ts.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Normalization of the `redact` logger option.
3
+ */
4
+ /**
5
+ * Resolves the `redact` option into frozen redaction settings.
6
+ *
7
+ * `false` turns redaction off and `true` (or omitting it) keeps the default.
8
+ * An object is used as given. Spreading `false` into an object yields `{}`,
9
+ * which is the default, so a boolean must be mapped explicitly.
10
+ *
11
+ * @param redact - The `redact` value passed to `createLogger`.
12
+ * @returns The normalized redaction settings.
13
+ */
14
+ export function resolveRedactionOptions(redact) {
15
+ if (redact === false)
16
+ return Object.freeze({ enabled: false });
17
+ if (redact === true || redact === undefined || redact === null) {
18
+ return Object.freeze({});
19
+ }
20
+ return Object.freeze({ ...redact });
21
+ }
22
+ //# sourceMappingURL=loggerOptions.redact.js.map
@@ -1,4 +1,4 @@
1
- import { LoggerLevel } from "../loggerLevel/loggerLevel.type.js";
1
+ import { LoggerLevel, type LoggerLevelLike } from "../loggerLevel/loggerLevel.type.js";
2
2
  import type { LoggerContextData } from "../loggerContext/loggerContext.core.js";
3
3
  import type { LoggerFormatterLike } from "../loggerFormatter/loggerFormatter.type.js";
4
4
  import type { LoggerTransportLike } from "../loggerTransport/loggerTransport.type.js";
@@ -6,7 +6,8 @@ import type { LoggerRedactionOptions } from "../loggerEntry/loggerEntryHelpers/l
6
6
  /** Options used to configure a Zudojs logger. */
7
7
  export interface LoggerOptions {
8
8
  readonly name?: string;
9
- readonly level?: LoggerLevel;
9
+ /** Threshold: `LoggerLevel.WARN`, or a name such as `"warn"` / `"WARN"`. */
10
+ readonly level?: LoggerLevelLike;
10
11
  readonly environment?: string;
11
12
  readonly metadata?: LoggerContextData;
12
13
  readonly formatter?: LoggerFormatterLike;
@@ -23,9 +24,10 @@ export interface LoggerOptions {
23
24
  * Redaction is ON by default: fields whose NAME looks like a secret
24
25
  * (password, token, api key, credential, authorization, cookie) are
25
26
  * replaced with "[REDACTED]" before an entry reaches a formatter.
26
- * Pass `{ enabled: false }` to opt out.
27
+ * Pass `false` (or `{ enabled: false }`) to opt out, or an options object
28
+ * to customize the matched keys, pattern and replacement.
27
29
  */
28
- readonly redact?: LoggerRedactionOptions;
30
+ readonly redact?: boolean | LoggerRedactionOptions;
29
31
  }
30
32
  /** Options used when creating a child logger. */
31
33
  export interface ChildLoggerOptions {
@@ -33,6 +35,16 @@ export interface ChildLoggerOptions {
33
35
  readonly metadata?: LoggerContextData;
34
36
  readonly level?: LoggerLevel;
35
37
  }
38
+ /**
39
+ * What `Logger.child()` accepts: `ChildLoggerOptions` whose `level` may also
40
+ * be a name (`"debug"`, `"DEBUG"`). `ChildLoggerOptions` itself keeps the
41
+ * enum, so a custom logger that reads `options.level` still compiles; one
42
+ * that may be handed a name should pass it through `resolveLoggerLevel()`.
43
+ */
44
+ export interface ChildLoggerOptionsInput extends Omit<ChildLoggerOptions, "level"> {
45
+ /** Threshold: `LoggerLevel.DEBUG`, or a name such as `"debug"`. */
46
+ readonly level?: LoggerLevelLike;
47
+ }
36
48
  /** Options for a single log operation. */
37
49
  export interface LogOptions {
38
50
  readonly metadata?: LoggerContextData;
@@ -74,5 +86,5 @@ export declare function resolveLoggerOptions(options?: LoggerOptions): LoggerCon
74
86
  /** Merges two logger option objects. Values from `override` take precedence. */
75
87
  export declare function mergeLoggerOptions(base: LoggerOptions, override: LoggerOptions): LoggerOptions;
76
88
  /** Creates options for a child logger. */
77
- export declare function createChildLoggerOptions(parent: LoggerConfiguration, options?: ChildLoggerOptions): LoggerOptions;
89
+ export declare function createChildLoggerOptions(parent: LoggerConfiguration, options?: ChildLoggerOptionsInput): LoggerOptions;
78
90
  //# sourceMappingURL=loggerOptions.type.d.ts.map
@@ -1,4 +1,5 @@
1
- import { LoggerLevel } from "../loggerLevel/loggerLevel.type.js";
1
+ import { LoggerLevel, resolveLoggerLevel, } from "../loggerLevel/loggerLevel.type.js";
2
+ import { resolveRedactionOptions } from "./loggerOptions.redact.js";
2
3
  /** Default logger configuration values. */
3
4
  export const DEFAULT_LOGGER_OPTIONS = {
4
5
  enabled: true,
@@ -29,7 +30,9 @@ export function resolveLoggerOptions(options = {}) {
29
30
  validateLoggerOptions(options);
30
31
  return Object.freeze({
31
32
  name: options.name ?? "zudojs",
32
- level: options.level ?? LoggerLevel.INFO,
33
+ level: options.level === undefined
34
+ ? LoggerLevel.INFO
35
+ : resolveLoggerLevel(options.level),
33
36
  environment: options.environment,
34
37
  metadata: Object.freeze({ ...(options.metadata ?? {}) }),
35
38
  formatter: options.formatter ?? "text",
@@ -41,7 +44,7 @@ export function resolveLoggerOptions(options = {}) {
41
44
  transportTimeout: options.transportTimeout ?? DEFAULT_LOGGER_OPTIONS.transportTimeout,
42
45
  inheritContext: options.inheritContext ?? DEFAULT_LOGGER_OPTIONS.inheritContext,
43
46
  mutable: options.mutable ?? DEFAULT_LOGGER_OPTIONS.mutable,
44
- redact: Object.freeze({ ...(options.redact ?? {}) }),
47
+ redact: resolveRedactionOptions(options.redact),
45
48
  });
46
49
  }
47
50
  /** Merges two logger option objects. Values from `override` take precedence. */
@@ -57,7 +60,9 @@ export function mergeLoggerOptions(base, override) {
57
60
  export function createChildLoggerOptions(parent, options = {}) {
58
61
  return {
59
62
  name: options.name ?? parent.name,
60
- level: options.level ?? parent.level,
63
+ level: options.level === undefined
64
+ ? parent.level
65
+ : resolveLoggerLevel(options.level),
61
66
  environment: parent.environment,
62
67
  metadata: { ...parent.metadata, ...(options.metadata ?? {}) },
63
68
  formatter: parent.formatter,
@@ -5,8 +5,9 @@ import type { LoggerTransportOptions, RegisteredLoggerTransport } from "../logge
5
5
  /**
6
6
  * Creates a simple console transport.
7
7
  *
8
- * Uses the standard console methods rather than depending on
9
- * Node.js-specific APIs.
8
+ * Prints one line per record through the standard console methods (no
9
+ * Node.js-specific APIs): `entry.formatted` when the logger's formatter
10
+ * produced it, otherwise the entry as a single JSON line.
10
11
  */
11
12
  export declare function createConsoleLoggerTransport(options?: LoggerTransportOptions): RegisteredLoggerTransport;
12
13
  //# sourceMappingURL=loggerTransportConsole.core.d.ts.map
@@ -2,19 +2,24 @@
2
2
  * Console logger transport.
3
3
  */
4
4
  import { createLoggerTransport } from "../loggerTransport.core.js";
5
- import { serializeTransportEntry } from "../loggerTransportHelpers/loggerTransportHelpers.js";
5
+ import { formatTransportLine } from "../loggerTransportHelpers/loggerTransportHelpers.js";
6
6
  /**
7
7
  * Creates a simple console transport.
8
8
  *
9
- * Uses the standard console methods rather than depending on
10
- * Node.js-specific APIs.
9
+ * Prints one line per record through the standard console methods (no
10
+ * Node.js-specific APIs): `entry.formatted` when the logger's formatter
11
+ * produced it, otherwise the entry as a single JSON line.
11
12
  */
12
13
  export function createConsoleLoggerTransport(options = {}) {
13
14
  const transport = {
14
15
  name: options.name ?? "console",
15
16
  enabled: options.enabled ?? true,
16
17
  write(entry) {
17
- const payload = serializeTransportEntry(entry);
18
+ // One line per record: the formatter's line (text or JSON), or the
19
+ // entry as JSON. Printing the record object showed the timestamp and
20
+ // level twice beside a text line, and spread a structured record over
21
+ // several lines of console output.
22
+ const payload = formatTransportLine(entry);
18
23
  switch (entry.levelName) {
19
24
  case "fatal":
20
25
  case "error":
@@ -3,5 +3,5 @@
3
3
  *
4
4
  * Transport helper functions.
5
5
  */
6
- export { serializeTransportEntry, closeLoggerTransport, flushLoggerTransport, } from "./loggerTransportHelpers.js";
6
+ export { serializeTransportEntry, toJsonLogLine, formatTransportLine, closeLoggerTransport, flushLoggerTransport, } from "./loggerTransportHelpers.js";
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -3,5 +3,5 @@
3
3
  *
4
4
  * Transport helper functions.
5
5
  */
6
- export { serializeTransportEntry, closeLoggerTransport, flushLoggerTransport, } from "./loggerTransportHelpers.js";
6
+ export { serializeTransportEntry, toJsonLogLine, formatTransportLine, closeLoggerTransport, flushLoggerTransport, } from "./loggerTransportHelpers.js";
7
7
  //# sourceMappingURL=index.js.map
@@ -7,6 +7,21 @@ import type { LoggerTransportLike } from "../loggerTransport.type.js";
7
7
  * Converts a LoggerEntry into a console-friendly object.
8
8
  */
9
9
  export declare function serializeTransportEntry(entry: LoggerEntry): Record<string, unknown>;
10
+ /**
11
+ * Renders a record as one line of JSON.
12
+ *
13
+ * Values go through `serializeLoggerValue` first, so a cycle becomes
14
+ * `"[Circular]"`, a BigInt its decimal string and an Error its name,
15
+ * message and stack. `JSON.stringify` escapes `\n`; U+2028 and U+2029,
16
+ * which it leaves raw and some viewers break lines on, are escaped too.
17
+ */
18
+ export declare function toJsonLogLine(record: unknown): string;
19
+ /**
20
+ * The text a line-oriented transport (console, file, stream) prints for an
21
+ * entry: the formatter's line when there is one, otherwise the entry as a
22
+ * single JSON line.
23
+ */
24
+ export declare function formatTransportLine(entry: LoggerEntry): string;
10
25
  /**
11
26
  * Safely closes a transport.
12
27
  */
@@ -42,6 +42,28 @@ export function serializeTransportEntry(entry) {
42
42
  : {}),
43
43
  };
44
44
  }
45
+ /**
46
+ * Renders a record as one line of JSON.
47
+ *
48
+ * Values go through `serializeLoggerValue` first, so a cycle becomes
49
+ * `"[Circular]"`, a BigInt its decimal string and an Error its name,
50
+ * message and stack. `JSON.stringify` escapes `\n`; U+2028 and U+2029,
51
+ * which it leaves raw and some viewers break lines on, are escaped too.
52
+ */
53
+ export function toJsonLogLine(record) {
54
+ const json = JSON.stringify(serializeLoggerValue(record)) ?? "null";
55
+ return json.replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
56
+ }
57
+ /**
58
+ * The text a line-oriented transport (console, file, stream) prints for an
59
+ * entry: the formatter's line when there is one, otherwise the entry as a
60
+ * single JSON line.
61
+ */
62
+ export function formatTransportLine(entry) {
63
+ return typeof entry.formatted === "string"
64
+ ? entry.formatted
65
+ : toJsonLogLine(serializeTransportEntry(entry));
66
+ }
45
67
  /**
46
68
  * Safely closes a transport.
47
69
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/logger",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Structured logging with transports, log levels, and context propagation for Zudojs applications.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -28,11 +28,11 @@
28
28
  "node": ">=24.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@zudojs/errors": "1.2.0"
31
+ "@zudojs/errors": "1.3.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "typescript": "7.0.2",
35
- "vitest": "^4.1.11"
35
+ "vitest": "^5.0.1"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public"
@@ -43,7 +43,7 @@
43
43
  "structured-logs",
44
44
  "transports"
45
45
  ],
46
- "homepage": "https://github.com/oyinlola-tech/zudo#readme",
46
+ "homepage": "https://zudojs.oyinlola.site/docs/packages-logger",
47
47
  "bugs": {
48
48
  "url": "https://github.com/oyinlola-tech/zudo/issues"
49
49
  },