@zudojs/logger 0.1.0 → 1.1.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 (167) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -8
  3. package/dist/loggerCore/core/loggerCore.context.d.ts +9 -2
  4. package/dist/loggerCore/core/loggerCore.context.js +12 -6
  5. package/dist/loggerCore/core/loggerCore.core.d.ts +10 -0
  6. package/dist/loggerCore/core/loggerCore.core.js +52 -1
  7. package/dist/loggerCore/helpers/loggerCore.helper.d.ts +7 -2
  8. package/dist/loggerCore/helpers/loggerCore.helper.js +10 -9
  9. package/dist/loggerCore/helpers/loggerCoreMethods/index.d.ts +1 -1
  10. package/dist/loggerCore/helpers/loggerCoreMethods/index.js +1 -1
  11. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.child.js +3 -1
  12. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.dispatch.d.ts +11 -0
  13. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.dispatch.js +121 -11
  14. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.entry.js +18 -2
  15. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.level.js +9 -2
  16. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.lifecycle.js +33 -0
  17. package/dist/loggerEntry/loggerEntry.core.js +0 -1
  18. package/dist/loggerEntry/loggerEntryHelpers/index.d.ts +2 -0
  19. package/dist/loggerEntry/loggerEntryHelpers/index.js +1 -0
  20. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.sanitize.d.ts +69 -0
  21. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.sanitize.js +113 -0
  22. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.serialize.d.ts +5 -1
  23. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.serialize.js +9 -5
  24. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.valueSerialize.js +9 -1
  25. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.context.js +15 -3
  26. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.js +3 -2
  27. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.json.js +19 -3
  28. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.metadata.js +14 -6
  29. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.text.js +31 -4
  30. package/dist/loggerOptions/loggerOptions.type.d.ts +12 -1
  31. package/dist/loggerOptions/loggerOptions.type.js +4 -1
  32. package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.buffered.js +4 -0
  33. package/dist/loggerTransport/loggerTransportHelpers/loggerTransportHelpers.js +4 -3
  34. package/package.json +23 -12
  35. package/dist/.tsbuildinfo +0 -1
  36. package/dist/index.d.ts.map +0 -1
  37. package/dist/index.js.map +0 -1
  38. package/dist/loggerContext/index.d.ts.map +0 -1
  39. package/dist/loggerContext/index.js.map +0 -1
  40. package/dist/loggerContext/loggerContext.core.d.ts.map +0 -1
  41. package/dist/loggerContext/loggerContext.core.js.map +0 -1
  42. package/dist/loggerContext/loggerContext.type.d.ts.map +0 -1
  43. package/dist/loggerContext/loggerContext.type.js.map +0 -1
  44. package/dist/loggerContext/loggerContextCreate.d.ts.map +0 -1
  45. package/dist/loggerContext/loggerContextCreate.js.map +0 -1
  46. package/dist/loggerContext/loggerContextSerialize.d.ts.map +0 -1
  47. package/dist/loggerContext/loggerContextSerialize.js.map +0 -1
  48. package/dist/loggerContext/loggerContextStorage.d.ts.map +0 -1
  49. package/dist/loggerContext/loggerContextStorage.js.map +0 -1
  50. package/dist/loggerCore/core/index.d.ts.map +0 -1
  51. package/dist/loggerCore/core/index.js.map +0 -1
  52. package/dist/loggerCore/core/loggerCore.context.d.ts.map +0 -1
  53. package/dist/loggerCore/core/loggerCore.context.js.map +0 -1
  54. package/dist/loggerCore/core/loggerCore.core.d.ts.map +0 -1
  55. package/dist/loggerCore/core/loggerCore.core.js.map +0 -1
  56. package/dist/loggerCore/core/loggerCore.type.d.ts.map +0 -1
  57. package/dist/loggerCore/core/loggerCore.type.js.map +0 -1
  58. package/dist/loggerCore/helpers/index.d.ts.map +0 -1
  59. package/dist/loggerCore/helpers/index.js.map +0 -1
  60. package/dist/loggerCore/helpers/loggerCore.helper.d.ts.map +0 -1
  61. package/dist/loggerCore/helpers/loggerCore.helper.js.map +0 -1
  62. package/dist/loggerCore/helpers/loggerCoreHelpers.d.ts.map +0 -1
  63. package/dist/loggerCore/helpers/loggerCoreHelpers.js.map +0 -1
  64. package/dist/loggerCore/helpers/loggerCoreMethods/index.d.ts.map +0 -1
  65. package/dist/loggerCore/helpers/loggerCoreMethods/index.js.map +0 -1
  66. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.child.d.ts.map +0 -1
  67. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.child.js.map +0 -1
  68. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.dispatch.d.ts.map +0 -1
  69. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.dispatch.js.map +0 -1
  70. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.entry.d.ts.map +0 -1
  71. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.entry.js.map +0 -1
  72. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.level.d.ts.map +0 -1
  73. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.level.js.map +0 -1
  74. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.lifecycle.d.ts.map +0 -1
  75. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.lifecycle.js.map +0 -1
  76. package/dist/loggerCore/helpers/loggerCoreMethods.logLevels.d.ts.map +0 -1
  77. package/dist/loggerCore/helpers/loggerCoreMethods.logLevels.js.map +0 -1
  78. package/dist/loggerCore/helpers/loggerCoreMethods.loggerProps.d.ts.map +0 -1
  79. package/dist/loggerCore/helpers/loggerCoreMethods.loggerProps.js.map +0 -1
  80. package/dist/loggerCore/index.d.ts.map +0 -1
  81. package/dist/loggerCore/index.js.map +0 -1
  82. package/dist/loggerEntry/index.d.ts.map +0 -1
  83. package/dist/loggerEntry/index.js.map +0 -1
  84. package/dist/loggerEntry/loggerEntry.core.d.ts.map +0 -1
  85. package/dist/loggerEntry/loggerEntry.core.js.map +0 -1
  86. package/dist/loggerEntry/loggerEntry.type.d.ts.map +0 -1
  87. package/dist/loggerEntry/loggerEntry.type.js.map +0 -1
  88. package/dist/loggerEntry/loggerEntryCreate.d.ts.map +0 -1
  89. package/dist/loggerEntry/loggerEntryCreate.js.map +0 -1
  90. package/dist/loggerEntry/loggerEntryHelpers/index.d.ts.map +0 -1
  91. package/dist/loggerEntry/loggerEntryHelpers/index.js.map +0 -1
  92. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.interfaces.d.ts.map +0 -1
  93. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.interfaces.js.map +0 -1
  94. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.serialize.d.ts.map +0 -1
  95. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.serialize.js.map +0 -1
  96. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.types.d.ts.map +0 -1
  97. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.types.js.map +0 -1
  98. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.valueSerialize.d.ts.map +0 -1
  99. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.valueSerialize.js.map +0 -1
  100. package/dist/loggerEntry/loggerEntrySerialize.d.ts.map +0 -1
  101. package/dist/loggerEntry/loggerEntrySerialize.js.map +0 -1
  102. package/dist/loggerErrors/index.d.ts.map +0 -1
  103. package/dist/loggerErrors/index.js.map +0 -1
  104. package/dist/loggerErrors/loggerError.base.d.ts.map +0 -1
  105. package/dist/loggerErrors/loggerError.base.js.map +0 -1
  106. package/dist/loggerErrors/loggerError.helpers.d.ts.map +0 -1
  107. package/dist/loggerErrors/loggerError.helpers.js.map +0 -1
  108. package/dist/loggerFactory/index.d.ts.map +0 -1
  109. package/dist/loggerFactory/index.js.map +0 -1
  110. package/dist/loggerFactory/loggerFactory.core.d.ts.map +0 -1
  111. package/dist/loggerFactory/loggerFactory.core.js.map +0 -1
  112. package/dist/loggerFormatter/index.d.ts.map +0 -1
  113. package/dist/loggerFormatter/index.js.map +0 -1
  114. package/dist/loggerFormatter/loggerFormatter.core.d.ts.map +0 -1
  115. package/dist/loggerFormatter/loggerFormatter.core.js.map +0 -1
  116. package/dist/loggerFormatter/loggerFormatter.type.d.ts.map +0 -1
  117. package/dist/loggerFormatter/loggerFormatter.type.js.map +0 -1
  118. package/dist/loggerFormatter/loggerFormatterFormatters/index.d.ts.map +0 -1
  119. package/dist/loggerFormatter/loggerFormatterFormatters/index.js.map +0 -1
  120. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.context.d.ts.map +0 -1
  121. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.context.js.map +0 -1
  122. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.d.ts.map +0 -1
  123. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.js.map +0 -1
  124. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.json.d.ts.map +0 -1
  125. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.json.js.map +0 -1
  126. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.metadata.d.ts.map +0 -1
  127. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.metadata.js.map +0 -1
  128. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.text.d.ts.map +0 -1
  129. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.text.js.map +0 -1
  130. package/dist/loggerFormatter/loggerFormatterGuard.d.ts.map +0 -1
  131. package/dist/loggerFormatter/loggerFormatterGuard.js.map +0 -1
  132. package/dist/loggerLevel/index.d.ts.map +0 -1
  133. package/dist/loggerLevel/index.js.map +0 -1
  134. package/dist/loggerLevel/loggerLevel.type.d.ts.map +0 -1
  135. package/dist/loggerLevel/loggerLevel.type.js.map +0 -1
  136. package/dist/loggerManager/index.d.ts.map +0 -1
  137. package/dist/loggerManager/index.js.map +0 -1
  138. package/dist/loggerManager/loggerManager.core.d.ts.map +0 -1
  139. package/dist/loggerManager/loggerManager.core.js.map +0 -1
  140. package/dist/loggerOptions/index.d.ts.map +0 -1
  141. package/dist/loggerOptions/index.js.map +0 -1
  142. package/dist/loggerOptions/loggerOptions.type.d.ts.map +0 -1
  143. package/dist/loggerOptions/loggerOptions.type.js.map +0 -1
  144. package/dist/loggerTransport/index.d.ts.map +0 -1
  145. package/dist/loggerTransport/index.js.map +0 -1
  146. package/dist/loggerTransport/loggerTransport.core.d.ts.map +0 -1
  147. package/dist/loggerTransport/loggerTransport.core.js.map +0 -1
  148. package/dist/loggerTransport/loggerTransport.registry.d.ts.map +0 -1
  149. package/dist/loggerTransport/loggerTransport.registry.js.map +0 -1
  150. package/dist/loggerTransport/loggerTransport.type.d.ts.map +0 -1
  151. package/dist/loggerTransport/loggerTransport.type.js.map +0 -1
  152. package/dist/loggerTransport/loggerTransportComposite/index.d.ts.map +0 -1
  153. package/dist/loggerTransport/loggerTransportComposite/index.js.map +0 -1
  154. package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.buffered.d.ts.map +0 -1
  155. package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.buffered.js.map +0 -1
  156. package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.d.ts.map +0 -1
  157. package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.js.map +0 -1
  158. package/dist/loggerTransport/loggerTransportConsole/index.d.ts.map +0 -1
  159. package/dist/loggerTransport/loggerTransportConsole/index.js.map +0 -1
  160. package/dist/loggerTransport/loggerTransportConsole/loggerTransportConsole.core.d.ts.map +0 -1
  161. package/dist/loggerTransport/loggerTransportConsole/loggerTransportConsole.core.js.map +0 -1
  162. package/dist/loggerTransport/loggerTransportGuard.d.ts.map +0 -1
  163. package/dist/loggerTransport/loggerTransportGuard.js.map +0 -1
  164. package/dist/loggerTransport/loggerTransportHelpers/index.d.ts.map +0 -1
  165. package/dist/loggerTransport/loggerTransportHelpers/index.js.map +0 -1
  166. package/dist/loggerTransport/loggerTransportHelpers/loggerTransportHelpers.d.ts.map +0 -1
  167. package/dist/loggerTransport/loggerTransportHelpers/loggerTransportHelpers.js.map +0 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zudojs Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # @zudojs/logger
2
2
 
3
- Structured logging with transports, log levels, and context propagation for Zudojs applications.
3
+ Structured logging with transports, formatters, log levels, secret
4
+ redaction, and context propagation for Zudojs applications.
4
5
 
5
6
  ## Installation
6
7
 
@@ -17,16 +18,105 @@ const logger = createLogger({ name: "api" });
17
18
 
18
19
  logger.info("Server started", { port: 3000, env: "production" });
19
20
  logger.error("Connection failed", { error: err.message });
21
+
22
+ await logger.flush(); // drain in-flight writes before exit
23
+ ```
24
+
25
+ ## Levels
26
+
27
+ `fatal` (0), `error` (1), `warn` (2), `info` (3), `debug` (4), `trace`
28
+ (5). A logger emits every message at or below its configured level;
29
+ `level` defaults to `info`.
30
+
31
+ ## Transports
32
+
33
+ A transport is either a `{ name, enabled, write, flush?, close? }`
34
+ object or a `(entry, context) => void | Promise<void>` function. When no
35
+ transport is configured the logger writes to the console.
36
+
37
+ Built in: `createConsoleLoggerTransport`, and the composites
38
+ `createMultiLoggerTransport`, `createConditionalLoggerTransport` and
39
+ `createBufferedLoggerTransport`. File and HTTP transports are not
40
+ included — implement the `LoggerTransport` interface for those.
41
+
42
+ `transportTimeout` (default 10s) bounds every transport write, so a
43
+ transport that stops responding cannot hang `flush()` or `close()`.
44
+
45
+ `throwTransportErrors` (default `false`) rethrows transport and formatter
46
+ failures instead of dropping them. A synchronous transport throws from the
47
+ log call itself; a failure from an asynchronous transport (or with
48
+ `asynchronous: true`) cannot, so it is rethrown by the next `flush()` or
49
+ `close()`, which still flush and close the transports first.
50
+
51
+ ## Flushing
52
+
53
+ Dispatch completes synchronously when every transport is synchronous.
54
+ With an asynchronous transport — or with `asynchronous: true`, which
55
+ always defers so the caller stays off the transport's critical path —
56
+ writes are in flight until drained. `flush()` and `close()` drain them,
57
+ so nothing is lost at exit.
58
+
59
+ ## Secret redaction
60
+
61
+ Redaction is **on by default**. Metadata and context fields whose NAME
62
+ looks like a secret — password, secret, token, api key, private key,
63
+ credential, authorization, cookie — are replaced with `"[REDACTED]"`
64
+ before the entry reaches any formatter or transport. Nested objects,
65
+ arrays and getters are all covered.
66
+
67
+ ```typescript
68
+ logger.info("login", { user: "alice", password: "hunter2" });
69
+ // metadata: { user: "alice", password: "[REDACTED]" }
70
+
71
+ createLogger({ redact: { keys: ["ssn"], replacement: "***" } });
72
+ createLogger({ redact: { enabled: false } }); // opt out
73
+ ```
74
+
75
+ ## Log injection
76
+
77
+ Text-shaped formatters escape control characters in the message, the
78
+ logger name, metadata keys and values, context values and source
79
+ locations. A newline or ANSI escape inside attacker-supplied text
80
+ becomes `\n` / `\u001b` rather than forging an extra log record or
81
+ driving the operator's terminal. The JSON formatter relies on
82
+ `JSON.stringify`, which escapes the same characters.
83
+
84
+ Metadata is normalized before serialization, so circular references
85
+ (`"[Circular]"`), BigInt values and functions never make a formatter
86
+ throw and silently drop the record.
87
+
88
+ ## Context
89
+
90
+ ```typescript
91
+ import { createLoggerContext, withLoggerContext } from "@zudojs/logger";
92
+
93
+ const scoped = logger.withContext(
94
+ createLoggerContext({ requestId: "req-1", metadata: { tenant: "acme" } }),
95
+ );
96
+ scoped.info("handled"); // metadata carries requestId and tenant
97
+
98
+ withLoggerContext(logger, createLoggerContext({ traceId }), (scoped) => {
99
+ scoped.info("inside the trace");
100
+ });
20
101
  ```
21
102
 
22
- ## Features
103
+ Per-call context is merged into the entry's metadata the same way:
104
+ `logger.log(LoggerLevel.INFO, "handled", { context: { tenant: "acme" } })`.
105
+ The entry's `context` also carries the identifiers (`requestId`, `traceId`,
106
+ ...) of the active context for transports that read them directly.
107
+
108
+ Child loggers inherit name, level, formatter, transports, metadata and
109
+ redaction settings: `logger.child({ name: "api.db" })`.
110
+
111
+ ## Formatters
112
+
113
+ `createTextLoggerFormatter`, `createJsonLoggerFormatter`,
114
+ `createCompactLoggerFormatter`, `createDevelopmentLoggerFormatter`,
115
+ `createProductionLoggerFormatter`, `createStructuredLoggerFormatter`.
23
116
 
24
- - Structured JSON logging
25
- - Log levels: debug, info, warn, error
26
- - Multiple transports (console, file, HTTP)
27
- - Child loggers with inherited context
28
- - Correlation ID propagation
29
- - Sensitive data redaction
117
+ Pass `{ colors: true }` in the formatter context to colourize the level
118
+ tag of text output. Colour codes are emitted only around the fixed level
119
+ name, never around user-supplied text.
30
120
 
31
121
  ## Use Cases
32
122
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import type { LoggerLevel } from "../../loggerLevel/loggerLevel.type.js";
5
5
  import type { LogMetadata } from "../../loggerEntry/loggerEntry.type.js";
6
- import type { LoggerContext } from "../../loggerContext/loggerContext.core.js";
6
+ import type { LoggerContext, LoggerContextStorage } from "../../loggerContext/loggerContext.core.js";
7
7
  import type { ChildLoggerOptions, LogOptions } from "../../loggerOptions/loggerOptions.type.js";
8
8
  import type { Logger } from "./loggerCore.type.js";
9
9
  /**
@@ -12,7 +12,14 @@ import type { Logger } from "./loggerCore.type.js";
12
12
  export declare class ContextLogger implements Logger {
13
13
  private readonly logger;
14
14
  private readonly context;
15
- constructor(logger: Logger, context: LoggerContext);
15
+ private readonly storage;
16
+ /**
17
+ * @param storage The context storage the WRAPPED logger reads from.
18
+ * This used to be created fresh inside `run()`, so the scoped
19
+ * context was written into a throwaway stack and never reached the
20
+ * entry — withContext() silently produced context-free logs.
21
+ */
22
+ constructor(logger: Logger, context: LoggerContext, storage: LoggerContextStorage);
16
23
  get name(): string;
17
24
  get level(): LoggerLevel;
18
25
  get enabled(): boolean;
@@ -2,16 +2,23 @@
2
2
  * ContextLogger wrapper implementation.
3
3
  */
4
4
  import { createLoggerContext } from "../../loggerContext/loggerContext.core.js";
5
- import { createLoggerContextStorage } from "../../loggerContext/loggerContextStorage.js";
6
5
  /**
7
6
  * Logger wrapper that provides scoped context.
8
7
  */
9
8
  export class ContextLogger {
10
9
  logger;
11
10
  context;
12
- constructor(logger, context) {
11
+ storage;
12
+ /**
13
+ * @param storage The context storage the WRAPPED logger reads from.
14
+ * This used to be created fresh inside `run()`, so the scoped
15
+ * context was written into a throwaway stack and never reached the
16
+ * entry — withContext() silently produced context-free logs.
17
+ */
18
+ constructor(logger, context, storage) {
13
19
  this.logger = logger;
14
20
  this.context = context;
21
+ this.storage = storage;
15
22
  }
16
23
  get name() {
17
24
  return this.logger.name;
@@ -44,14 +51,14 @@ export class ContextLogger {
44
51
  this.run(() => this.logger.log(level, message, options));
45
52
  }
46
53
  child(options) {
47
- return new ContextLogger(this.logger.child(options), this.context);
54
+ return new ContextLogger(this.logger.child(options), this.context, this.storage);
48
55
  }
49
56
  withContext(context) {
50
57
  return new ContextLogger(this.logger, createLoggerContext({
51
58
  parent: this.context,
52
59
  ...context.identifiers,
53
60
  metadata: context.metadata,
54
- }));
61
+ }), this.storage);
55
62
  }
56
63
  setLevel(level) {
57
64
  this.logger.setLevel(level);
@@ -69,8 +76,7 @@ export class ContextLogger {
69
76
  return this.logger.close();
70
77
  }
71
78
  run(callback) {
72
- const storage = createLoggerContextStorage();
73
- storage.run(this.context, callback);
79
+ this.storage.run(this.context, callback);
74
80
  }
75
81
  }
76
82
  //# sourceMappingURL=loggerCore.context.js.map
@@ -19,6 +19,10 @@ export interface ZudojsLoggerContext {
19
19
  markDisposed(): void;
20
20
  updateConfiguration(config: LoggerConfiguration): void;
21
21
  createChildLogger(options: LoggerOptions): Logger;
22
+ /** Registers an in-flight dispatch so it can be awaited later. */
23
+ trackDispatch(dispatch: Promise<void>): void;
24
+ /** Resolves once every registered dispatch has settled. */
25
+ drainDispatches(): Promise<void>;
22
26
  }
23
27
  /**
24
28
  * Logger implementation.
@@ -27,6 +31,10 @@ export declare class ZudojsLogger implements Logger, ZudojsLoggerContext {
27
31
  private _configuration;
28
32
  private readonly _contextStorage;
29
33
  private _disposed;
34
+ private _closing;
35
+ private readonly _pending;
36
+ private readonly _dispatchFailures;
37
+ private _droppedFailures;
30
38
  constructor(options?: LoggerOptions, contextStorage?: LoggerContextStorage);
31
39
  get configuration(): LoggerConfiguration;
32
40
  get contextStorage(): LoggerContextStorage;
@@ -54,6 +62,8 @@ export declare class ZudojsLogger implements Logger, ZudojsLoggerContext {
54
62
  markDisposed(): void;
55
63
  updateConfiguration(config: LoggerConfiguration): void;
56
64
  createChildLogger(options: LoggerOptions): Logger;
65
+ trackDispatch(dispatch: Promise<void>): void;
66
+ drainDispatches(): Promise<void>;
57
67
  }
58
68
  /**
59
69
  * Creates a Zudojs logger.
@@ -7,6 +7,8 @@ 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
8
  import { logAtLevel, childLogger, withContextLogger, setLoggerLevel, enableLogger, disableLogger, flushLogger, closeLogger, } from "../helpers/loggerCoreMethods/index.js";
9
9
  import { getLoggerName, getLoggerLevel, getLoggerEnabled, } from "../helpers/loggerCoreMethods.loggerProps.js";
10
+ /** Upper bound on dispatch failures retained between two flushes. */
11
+ const MAX_RETAINED_DISPATCH_FAILURES = 32;
10
12
  /**
11
13
  * Logger implementation.
12
14
  */
@@ -14,6 +16,10 @@ export class ZudojsLogger {
14
16
  _configuration;
15
17
  _contextStorage;
16
18
  _disposed = false;
19
+ _closing;
20
+ _pending = new Set();
21
+ _dispatchFailures = [];
22
+ _droppedFailures = 0;
17
23
  constructor(options = {}, contextStorage) {
18
24
  this._configuration = resolveLoggerOptions(options);
19
25
  this._contextStorage = contextStorage ?? createLoggerContextStorage();
@@ -74,7 +80,14 @@ export class ZudojsLogger {
74
80
  return flushLogger(this);
75
81
  }
76
82
  close() {
77
- return closeLogger(this);
83
+ // Concurrent callers share one closure: two overlapping close() calls
84
+ // used to drain and close every transport twice.
85
+ if (this._closing)
86
+ return this._closing;
87
+ this._closing = closeLogger(this).finally(() => {
88
+ this._closing = undefined;
89
+ });
90
+ return this._closing;
78
91
  }
79
92
  assertActive() {
80
93
  assertActiveHelper(this._disposed, this._configuration.name);
@@ -97,6 +110,44 @@ export class ZudojsLogger {
97
110
  createChildLogger(options) {
98
111
  return new ZudojsLogger(options, this._contextStorage);
99
112
  }
113
+ trackDispatch(dispatch) {
114
+ // A dispatch rejects only when handleError threw — i.e. when
115
+ // `throwTransportErrors` is on and an asynchronous transport failed.
116
+ // Nothing can throw from the log call that started it, so the failure
117
+ // is kept (bounded) and surfaced by the next flush()/close(); it was
118
+ // previously swallowed outright, which made the option a no-op for
119
+ // every asynchronous transport.
120
+ const tracked = dispatch
121
+ .then(() => { }, (error) => {
122
+ if (!this._configuration.throwTransportErrors)
123
+ return;
124
+ if (this._dispatchFailures.length < MAX_RETAINED_DISPATCH_FAILURES) {
125
+ this._dispatchFailures.push(error);
126
+ }
127
+ else {
128
+ this._droppedFailures += 1;
129
+ }
130
+ })
131
+ .finally(() => {
132
+ this._pending.delete(tracked);
133
+ });
134
+ this._pending.add(tracked);
135
+ }
136
+ async drainDispatches() {
137
+ // A dispatch can start further dispatches (a transport that logs),
138
+ // so drain until the set is genuinely empty.
139
+ while (this._pending.size > 0) {
140
+ await Promise.all([...this._pending]);
141
+ }
142
+ if (this._dispatchFailures.length === 0)
143
+ return;
144
+ const failures = this._dispatchFailures.splice(0);
145
+ const dropped = this._droppedFailures;
146
+ this._droppedFailures = 0;
147
+ if (failures.length === 1 && dropped === 0)
148
+ throw failures[0];
149
+ throw new AggregateError(failures, `${failures.length + dropped} log dispatch(es) failed since the last flush.`);
150
+ }
100
151
  }
101
152
  /**
102
153
  * Creates a Zudojs logger.
@@ -14,9 +14,14 @@ export declare function createChildLogger(parent: Logger, options?: ChildLoggerO
14
14
  */
15
15
  export declare function logError(logger: Logger, error: Error, message?: string, metadata?: LogMetadata): void;
16
16
  /**
17
- * Creates a logger context and executes a callback inside it.
17
+ * Creates a context-scoped logger and runs a callback with it.
18
+ *
19
+ * The scoped logger is passed to the callback. Both branches of the
20
+ * previous implementation were identical and simply discarded the
21
+ * scoped logger, so the context never applied to anything the callback
22
+ * logged.
18
23
  */
19
- export declare function withLoggerContext<T>(logger: Logger, context: LoggerContext, callback: () => T): T;
24
+ export declare function withLoggerContext<T>(logger: Logger, context: LoggerContext, callback: (scoped: Logger) => T): T;
20
25
  /**
21
26
  * Creates a default application logger.
22
27
  */
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Logger helper functions.
3
3
  */
4
+ import { LoggerLevel } from "../../loggerLevel/loggerLevel.type.js";
4
5
  import { createTextLoggerFormatter } from "../../loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.text.js";
5
6
  import { createConsoleLoggerTransport } from "../../loggerTransport/loggerTransport.registry.js";
6
- import { ContextLogger } from "../core/loggerCore.context.js";
7
7
  import { createLogger } from "../core/loggerCore.core.js";
8
8
  /**
9
9
  * Creates a child logger.
@@ -18,20 +18,21 @@ export function logError(logger, error, message, metadata) {
18
18
  if (!logger.enabled) {
19
19
  return;
20
20
  }
21
- logger.log(1, message ?? error.message, {
21
+ logger.log(LoggerLevel.ERROR, message ?? error.message, {
22
22
  metadata,
23
23
  error,
24
24
  });
25
25
  }
26
26
  /**
27
- * Creates a logger context and executes a callback inside it.
27
+ * Creates a context-scoped logger and runs a callback with it.
28
+ *
29
+ * The scoped logger is passed to the callback. Both branches of the
30
+ * previous implementation were identical and simply discarded the
31
+ * scoped logger, so the context never applied to anything the callback
32
+ * logged.
28
33
  */
29
34
  export function withLoggerContext(logger, context, callback) {
30
- const scoped = logger.withContext(context);
31
- if (scoped instanceof ContextLogger) {
32
- return callback();
33
- }
34
- return callback();
35
+ return callback(logger.withContext(context));
35
36
  }
36
37
  /**
37
38
  * Creates a default application logger.
@@ -39,7 +40,7 @@ export function withLoggerContext(logger, context, callback) {
39
40
  export function createDefaultLogger(name = "zudojs") {
40
41
  return createLogger({
41
42
  name,
42
- level: 3,
43
+ level: LoggerLevel.INFO,
43
44
  formatter: createTextLoggerFormatter(),
44
45
  transports: [createConsoleLoggerTransport()],
45
46
  });
@@ -4,7 +4,7 @@
4
4
  * Logger class helper methods.
5
5
  */
6
6
  export { createEntry } from "./loggerCoreMethods.entry.js";
7
- export { dispatchEntry } from "./loggerCoreMethods.dispatch.js";
7
+ export { dispatchEntry, dispatchEntrySync, } from "./loggerCoreMethods.dispatch.js";
8
8
  export { logAtLevel } 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";
@@ -4,7 +4,7 @@
4
4
  * Logger class helper methods.
5
5
  */
6
6
  export { createEntry } from "./loggerCoreMethods.entry.js";
7
- export { dispatchEntry } from "./loggerCoreMethods.dispatch.js";
7
+ export { dispatchEntry, dispatchEntrySync, } from "./loggerCoreMethods.dispatch.js";
8
8
  export { logAtLevel } 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";
@@ -17,6 +17,8 @@ export function childLogger(ctx, options = {}) {
17
17
  export function withContextLogger(ctx, context) {
18
18
  ctx.assertActive();
19
19
  const child = childLogger(ctx);
20
- return new ContextLogger(child, context);
20
+ // The child shares the parent's context storage, so the wrapper must
21
+ // scope THAT storage for the context to reach created entries.
22
+ return new ContextLogger(child, context, ctx.contextStorage);
21
23
  }
22
24
  //# sourceMappingURL=loggerCoreMethods.child.js.map
@@ -7,4 +7,15 @@ import type { LoggerConfiguration } from "../../../loggerOptions/loggerOptions.t
7
7
  * Dispatches an entry to the configured transports.
8
8
  */
9
9
  export declare function dispatchEntry(configuration: LoggerConfiguration, entry: LoggerEntry, handleError: (error: Error) => void): Promise<void>;
10
+ /**
11
+ * Dispatches an entry, completing synchronously where it can.
12
+ *
13
+ * Returns `undefined` when every configured transport finished
14
+ * synchronously (nothing to await), otherwise a promise covering the
15
+ * asynchronous remainder. `asynchronous: true` skips the fast path
16
+ * entirely and always defers, keeping the caller off the transport's
17
+ * critical path — the option was previously stored and never read, so
18
+ * both settings behaved identically.
19
+ */
20
+ export declare function dispatchEntrySync(configuration: LoggerConfiguration, entry: LoggerEntry, handleError: (error: Error) => void): void | Promise<void>;
10
21
  //# sourceMappingURL=loggerCoreMethods.dispatch.d.ts.map
@@ -18,6 +18,41 @@ function defaultFormat(configuration, entry) {
18
18
  environment: configuration.environment,
19
19
  });
20
20
  }
21
+ /**
22
+ * Bounds a transport write with the configured transport timeout.
23
+ *
24
+ * `transportTimeout` was accepted, validated and stored but never read,
25
+ * so a transport whose write() never settled blocked flush() and
26
+ * close() forever. A non-positive timeout disables the bound.
27
+ */
28
+ async function withTransportTimeout(timeoutMs, transportName, operation) {
29
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
30
+ await operation;
31
+ return;
32
+ }
33
+ let timer;
34
+ const expiry = new Promise((_resolve, reject) => {
35
+ timer = setTimeout(() => {
36
+ reject(new LoggerTransportError(`Transport "${transportName}" did not complete within ${timeoutMs}ms.`));
37
+ }, timeoutMs);
38
+ // This timer bounds a write; it is not work in its own right. Left
39
+ // referenced it keeps the event loop alive, so a process that has
40
+ // finished but logged through an async transport hangs until the
41
+ // timeout elapses. Every other timer in the framework is unref'd
42
+ // for the same reason.
43
+ timer.unref?.();
44
+ });
45
+ try {
46
+ await Promise.race([operation, expiry]);
47
+ }
48
+ finally {
49
+ if (timer !== undefined) {
50
+ clearTimeout(timer);
51
+ }
52
+ // The abandoned write must never surface as an unhandled rejection.
53
+ void operation.catch(() => { });
54
+ }
55
+ }
21
56
  /**
22
57
  * Writes to a transport.
23
58
  */
@@ -33,22 +68,45 @@ async function writeTransport(configuration, transport, entry, formatted) {
33
68
  }
34
69
  await writeLoggerTransport(transport.transport, entry, transportContext);
35
70
  }
71
+ /**
72
+ * Formats an entry with the configured formatter.
73
+ */
74
+ function formatEntry(configuration, entry) {
75
+ const formatter = configuration.formatter;
76
+ if (typeof formatter === "string") {
77
+ return defaultFormat(configuration, entry);
78
+ }
79
+ return formatLoggerEntry(formatter, entry, {
80
+ loggerName: configuration.name,
81
+ environment: configuration.environment,
82
+ });
83
+ }
84
+ /**
85
+ * Writes an entry through one transport WITHOUT forcing a microtask.
86
+ *
87
+ * `writeLoggerTransport` is `async`, so awaiting it always defers by at
88
+ * least one microtask even for a fully synchronous transport. Calling
89
+ * the transport directly lets a synchronous console/array transport
90
+ * complete inline, which is what `asynchronous: false` promises.
91
+ */
92
+ function writeTransportMaybeSync(configuration, transport, entry, formatted) {
93
+ const transportContext = {
94
+ loggerName: configuration.name,
95
+ environment: configuration.environment,
96
+ };
97
+ const payload = typeof formatted === "string" ? { ...entry, message: formatted } : entry;
98
+ const target = transport.transport;
99
+ return typeof target === "function"
100
+ ? target(payload, transportContext)
101
+ : target.write(payload, transportContext);
102
+ }
36
103
  /**
37
104
  * Dispatches an entry to the configured transports.
38
105
  */
39
106
  export async function dispatchEntry(configuration, entry, handleError) {
40
- const formatter = configuration.formatter;
41
107
  let formatted;
42
108
  try {
43
- if (typeof formatter === "string") {
44
- formatted = defaultFormat(configuration, entry);
45
- }
46
- else {
47
- formatted = formatLoggerEntry(formatter, entry, {
48
- loggerName: configuration.name,
49
- environment: configuration.environment,
50
- });
51
- }
109
+ formatted = formatEntry(configuration, entry);
52
110
  }
53
111
  catch (error) {
54
112
  const formatterError = new LoggerFormatterError(`Failed to format log entry: ${toLoggerError(error).message}`, { cause: error });
@@ -64,7 +122,7 @@ export async function dispatchEntry(configuration, entry, handleError) {
64
122
  if (!registered.enabled) {
65
123
  continue;
66
124
  }
67
- await writeTransport(configuration, registered, entry, formatted);
125
+ await withTransportTimeout(configuration.transportTimeout, registered.name, writeTransport(configuration, registered, entry, formatted));
68
126
  }
69
127
  catch (error) {
70
128
  const transportError = new LoggerTransportError(`Failed to write log entry: ${toLoggerError(error).message}`, { cause: error });
@@ -72,4 +130,56 @@ export async function dispatchEntry(configuration, entry, handleError) {
72
130
  }
73
131
  }
74
132
  }
133
+ /**
134
+ * Dispatches an entry, completing synchronously where it can.
135
+ *
136
+ * Returns `undefined` when every configured transport finished
137
+ * synchronously (nothing to await), otherwise a promise covering the
138
+ * asynchronous remainder. `asynchronous: true` skips the fast path
139
+ * entirely and always defers, keeping the caller off the transport's
140
+ * critical path — the option was previously stored and never read, so
141
+ * both settings behaved identically.
142
+ */
143
+ export function dispatchEntrySync(configuration, entry, handleError) {
144
+ if (configuration.asynchronous) {
145
+ // `dispatchEntry` runs synchronously up to its first await, so a
146
+ // synchronous transport would still execute inline. Hop a
147
+ // microtask first so `asynchronous: true` genuinely keeps the
148
+ // caller off the transport's critical path.
149
+ return Promise.resolve().then(() => dispatchEntry(configuration, entry, handleError));
150
+ }
151
+ let formatted;
152
+ try {
153
+ formatted = formatEntry(configuration, entry);
154
+ }
155
+ catch (error) {
156
+ handleError(new LoggerFormatterError(`Failed to format log entry: ${toLoggerError(error).message}`, { cause: error }));
157
+ return;
158
+ }
159
+ const pending = [];
160
+ for (const transport of configuration.transports) {
161
+ try {
162
+ if (!isLoggerTransport(transport)) {
163
+ continue;
164
+ }
165
+ const registered = createLoggerTransport(transport);
166
+ if (!registered.enabled) {
167
+ continue;
168
+ }
169
+ const result = writeTransportMaybeSync(configuration, registered, entry, formatted);
170
+ if (result instanceof Promise) {
171
+ pending.push(withTransportTimeout(configuration.transportTimeout, registered.name, result).catch((error) => {
172
+ handleError(new LoggerTransportError(`Failed to write log entry: ${toLoggerError(error).message}`, { cause: error }));
173
+ }));
174
+ }
175
+ }
176
+ catch (error) {
177
+ handleError(new LoggerTransportError(`Failed to write log entry: ${toLoggerError(error).message}`, { cause: error }));
178
+ }
179
+ }
180
+ if (pending.length === 0) {
181
+ return;
182
+ }
183
+ return Promise.all(pending).then(() => undefined);
184
+ }
75
185
  //# sourceMappingURL=loggerCoreMethods.dispatch.js.map
@@ -2,6 +2,7 @@
2
2
  * Logger entry creation for dispatch.
3
3
  */
4
4
  import { createLoggerEntry } from "../../../loggerEntry/loggerEntry.core.js";
5
+ import { createSecretMatcher, redactLogValue, LOGGER_REDACTION_TOKEN, } from "../../../loggerEntry/loggerEntryHelpers/loggerEntryHelpers.sanitize.js";
5
6
  import { contextToLogMetadata, createLoggerContext, } from "../../../loggerContext/loggerContext.core.js";
6
7
  /**
7
8
  * Creates a normalized log entry.
@@ -13,11 +14,22 @@ export function createEntry(configuration, contextStorage, level, message, optio
13
14
  const contextMetadata = activeContext
14
15
  ? contextToLogMetadata(activeContext)
15
16
  : {};
16
- const metadata = {
17
+ // Per-call `options.context` flows into metadata exactly like the ambient
18
+ // context does. It used to reach only `entry.context.metadata`, which the
19
+ // text formatters never print, so `log(level, msg, { context })` silently
20
+ // dropped the data from every text-shaped line.
21
+ const rawMetadata = {
17
22
  ...configuration.metadata,
18
23
  ...contextMetadata,
24
+ ...(options.context ?? {}),
19
25
  ...(options.metadata ?? {}),
20
26
  };
27
+ // Redaction is applied HERE, before the entry is frozen, so every
28
+ // formatter and every transport sees the already-masked value and no
29
+ // path can bypass it — including nested objects, arrays and getters.
30
+ const isSecret = createSecretMatcher(configuration.redact);
31
+ const replacement = configuration.redact.replacement ?? LOGGER_REDACTION_TOKEN;
32
+ const metadata = redactLogValue(rawMetadata, isSecret, replacement);
21
33
  const context = options.context
22
34
  ? createLoggerContext({
23
35
  parent: activeContext,
@@ -28,9 +40,13 @@ export function createEntry(configuration, contextStorage, level, message, optio
28
40
  level,
29
41
  message,
30
42
  metadata,
43
+ // `LoggerEntryContext` declares the correlation identifiers, but they
44
+ // were never copied here, so a transport reading `entry.context.requestId`
45
+ // always saw `undefined`.
31
46
  context: context
32
47
  ? {
33
- metadata: context.metadata,
48
+ ...context.identifiers,
49
+ metadata: redactLogValue(context.metadata, isSecret, replacement),
34
50
  }
35
51
  : undefined,
36
52
  source: options.source,