@ayepi/log 0.4.1 → 0.5.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 CHANGED
@@ -29,7 +29,8 @@ Builds a record from mixed arguments:
29
29
  - object args are **merged** into the record;
30
30
  - `Error` args become `error` / `additionalErrors` (serialized with `name`, `message`,
31
31
  `stack`, recursive `cause`, own props) — and **any trace context attached to the error
32
- is merged in** (so a caught error carries the context from where it was thrown);
32
+ is merged in** (so a caught error carries the context from where it was thrown); a string
33
+ `error` field becomes `{ name: 'Error', message }`;
33
34
  - emitted only if `level >= ` the configured threshold (`debug < info < warn < error`).
34
35
 
35
36
  ```ts
@@ -35,6 +35,12 @@ The first `Error` arg becomes the record's `error`; any further `Error` args bec
35
35
  `additionalErrors`. Non‑`Error` values become `{ name: 'NonError', message }` (the message
36
36
  is the string itself, or `String(value)`, guarded against unstringifiable values).
37
37
 
38
+ An object arg's `error` field lands on the same reserved key when no `Error` arg took it, and
39
+ is normalized to a `SerializedError` too: a string becomes `{ name: 'Error', message }` (so
40
+ `log.warn('job.failed', { error: 'timed out' })` renders `error=Error: timed out`), a value
41
+ that already has a string `name` and `message` is kept, and anything else is serialized as
42
+ above. With an `Error` arg present, the field's value moves aside to `error2`.
43
+
38
44
  ```ts
39
45
  const e1 = new Error('first'); (e1 as { code?: string }).code = 'E1'
40
46
  const e2 = new TypeError('second')
@@ -63,7 +63,7 @@ const cap: Transport = { name: 'cap', write: (_r, text) => { line = text } }
63
63
  function consoleTransport(opts?: ConsoleTransportOptions): Transport
64
64
 
65
65
  interface ConsoleTransportOptions {
66
- /** The console to write through — should be the original (pre-interception) console to avoid recursion. */
66
+ /** The console to write through (default the global console); its methods are bound when the transport is made. */
67
67
  readonly console?: ConsoleLike
68
68
  /** Map a record level to a console method (default: error→error, warn→warn, debug→debug, else→log). */
69
69
  readonly method?: (level: Level) => keyof ConsoleLike
@@ -82,6 +82,10 @@ Writes the pre‑formatted `text` to the level‑mapped console method. The defa
82
82
  method mapping is `error → error`, `warn → warn`, `debug → debug`, everything else →
83
83
  `log`.
84
84
 
85
+ The console's methods are bound when `consoleTransport()` is called, so a transport made
86
+ before `interceptConsole()` keeps writing to the original methods afterwards, and no line
87
+ loops back into the logger.
88
+
85
89
  The default logger's default transport is a `consoleTransport` bound to the **captured
86
90
  original** console (taken before any interception is installed), so logs aren't recursed
87
91
  even when console interception is on. If `globalThis.console` is absent it falls back to a
package/dist/index.cjs CHANGED
@@ -33,9 +33,12 @@ const noopConsole = {
33
33
  };
34
34
  const globalConsole = () => globalThis.console ?? noopConsole;
35
35
  const defaultMethod = (level) => level === "error" ? "error" : level === "warn" ? "warn" : level === "debug" ? "debug" : "log";
36
- /** A {@link Transport} that writes the formatted line to a console. */
36
+ /**
37
+ * A {@link Transport} that writes the formatted line to a console. The console's methods are captured when the
38
+ * transport is made, so make it before intercepting the console.
39
+ */
37
40
  function consoleTransport(opts = {}) {
38
- const target = opts.console ?? globalConsole();
41
+ const target = captureConsole(opts.console ?? globalConsole());
39
42
  const pick = opts.method ?? defaultMethod;
40
43
  return {
41
44
  name: "console",
package/dist/index.d.cts CHANGED
@@ -12,12 +12,18 @@ interface ConsoleLike {
12
12
  }
13
13
  /** Options for {@link consoleTransport}. */
14
14
  interface ConsoleTransportOptions {
15
- /** The console to **write through** — should be the original (pre-interception) console to avoid recursion. */
15
+ /**
16
+ * The console to write through (default the global `console`). Its methods are bound when the transport is made,
17
+ * so a later `interceptConsole()` that replaces them never feeds a line back into the logger.
18
+ */
16
19
  readonly console?: ConsoleLike;
17
20
  /** Map a record level to a console method (default: error→error, warn→warn, debug→debug, else→log). */
18
21
  readonly method?: (level: Level) => keyof ConsoleLike;
19
22
  }
20
- /** A {@link Transport} that writes the formatted line to a console. */
23
+ /**
24
+ * A {@link Transport} that writes the formatted line to a console. The console's methods are captured when the
25
+ * transport is made, so make it before intercepting the console.
26
+ */
21
27
  declare function consoleTransport(opts?: ConsoleTransportOptions): Transport;
22
28
  /** Configuration for {@link createLogger}. */
23
29
  interface LoggerConfig {
package/dist/index.d.ts CHANGED
@@ -12,12 +12,18 @@ interface ConsoleLike {
12
12
  }
13
13
  /** Options for {@link consoleTransport}. */
14
14
  interface ConsoleTransportOptions {
15
- /** The console to **write through** — should be the original (pre-interception) console to avoid recursion. */
15
+ /**
16
+ * The console to write through (default the global `console`). Its methods are bound when the transport is made,
17
+ * so a later `interceptConsole()` that replaces them never feeds a line back into the logger.
18
+ */
16
19
  readonly console?: ConsoleLike;
17
20
  /** Map a record level to a console method (default: error→error, warn→warn, debug→debug, else→log). */
18
21
  readonly method?: (level: Level) => keyof ConsoleLike;
19
22
  }
20
- /** A {@link Transport} that writes the formatted line to a console. */
23
+ /**
24
+ * A {@link Transport} that writes the formatted line to a console. The console's methods are captured when the
25
+ * transport is made, so make it before intercepting the console.
26
+ */
21
27
  declare function consoleTransport(opts?: ConsoleTransportOptions): Transport;
22
28
  /** Configuration for {@link createLogger}. */
23
29
  interface LoggerConfig {
package/dist/index.js CHANGED
@@ -32,9 +32,12 @@ const noopConsole = {
32
32
  };
33
33
  const globalConsole = () => globalThis.console ?? noopConsole;
34
34
  const defaultMethod = (level) => level === "error" ? "error" : level === "warn" ? "warn" : level === "debug" ? "debug" : "log";
35
- /** A {@link Transport} that writes the formatted line to a console. */
35
+ /**
36
+ * A {@link Transport} that writes the formatted line to a console. The console's methods are captured when the
37
+ * transport is made, so make it before intercepting the console.
38
+ */
36
39
  function consoleTransport(opts = {}) {
37
- const target = opts.console ?? globalConsole();
40
+ const target = captureConsole(opts.console ?? globalConsole());
38
41
  const pick = opts.method ?? defaultMethod;
39
42
  return {
40
43
  name: "console",
package/dist/internal.cjs CHANGED
@@ -269,8 +269,27 @@ function buildRecord(level, args, opts) {
269
269
  record = merge(record, getContext());
270
270
  for (const o of objects) record = merge(record, o);
271
271
  for (const c of errorContexts) record = merge(record, c);
272
+ if (record.error !== void 0) record.error = asSerializedError(record.error);
272
273
  return record;
273
274
  }
275
+ /** Whether `v` already has the {@link SerializedError} shape (a string `name` and `message`). */
276
+ function isSerializedError(v) {
277
+ if (v === null || typeof v !== "object") return false;
278
+ const o = v;
279
+ return typeof o.name === "string" && typeof o.message === "string";
280
+ }
281
+ /**
282
+ * Normalize whatever landed on the reserved `error` key (an `Error` arg, or a field such as
283
+ * `{ error: 'timed out' }`) to a {@link SerializedError}: a string becomes `{ name: 'Error', message }`,
284
+ * an already serialized shape is kept, and anything else goes through {@link serializeError}.
285
+ */
286
+ function asSerializedError(v) {
287
+ if (typeof v === "string") return {
288
+ name: "Error",
289
+ message: v
290
+ };
291
+ return !(v instanceof Error) && isSerializedError(v) ? v : serializeError(v);
292
+ }
274
293
  const renderValue = (v) => {
275
294
  if (typeof v === "string") return v;
276
295
  if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
package/dist/internal.js CHANGED
@@ -269,8 +269,27 @@ function buildRecord(level, args, opts) {
269
269
  record = merge(record, getContext());
270
270
  for (const o of objects) record = merge(record, o);
271
271
  for (const c of errorContexts) record = merge(record, c);
272
+ if (record.error !== void 0) record.error = asSerializedError(record.error);
272
273
  return record;
273
274
  }
275
+ /** Whether `v` already has the {@link SerializedError} shape (a string `name` and `message`). */
276
+ function isSerializedError(v) {
277
+ if (v === null || typeof v !== "object") return false;
278
+ const o = v;
279
+ return typeof o.name === "string" && typeof o.message === "string";
280
+ }
281
+ /**
282
+ * Normalize whatever landed on the reserved `error` key (an `Error` arg, or a field such as
283
+ * `{ error: 'timed out' }`) to a {@link SerializedError}: a string becomes `{ name: 'Error', message }`,
284
+ * an already serialized shape is kept, and anything else goes through {@link serializeError}.
285
+ */
286
+ function asSerializedError(v) {
287
+ if (typeof v === "string") return {
288
+ name: "Error",
289
+ message: v
290
+ };
291
+ return !(v instanceof Error) && isSerializedError(v) ? v : serializeError(v);
292
+ }
274
293
  const renderValue = (v) => {
275
294
  if (typeof v === "string") return v;
276
295
  if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ayepi/log",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Structured logging for ayepi — AsyncLocalStorage trace context, console interception, console/file transports, error serialization, middleware",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -68,7 +68,7 @@
68
68
  "node": ">=18"
69
69
  },
70
70
  "peerDependencies": {
71
- "@ayepi/core": "^0.4.1"
71
+ "@ayepi/core": "^0.5.0"
72
72
  },
73
73
  "peerDependenciesMeta": {
74
74
  "@ayepi/core": {
@@ -80,8 +80,8 @@
80
80
  "publint": "^0.3.0",
81
81
  "tsdown": "^0.12.0",
82
82
  "vitest": "^2.1.8",
83
- "zod": "^4.4.3",
84
- "@ayepi/core": "0.4.1"
83
+ "zod": "^4.6.7",
84
+ "@ayepi/core": "0.5.0"
85
85
  },
86
86
  "keywords": [
87
87
  "ayepi",