@zudojs/logger 0.0.1 → 1.0.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 (165) hide show
  1. package/README.md +87 -8
  2. package/dist/loggerCore/core/loggerCore.context.d.ts +9 -2
  3. package/dist/loggerCore/core/loggerCore.context.js +12 -6
  4. package/dist/loggerCore/core/loggerCore.core.d.ts +7 -0
  5. package/dist/loggerCore/core/loggerCore.core.js +19 -0
  6. package/dist/loggerCore/helpers/loggerCore.helper.d.ts +7 -2
  7. package/dist/loggerCore/helpers/loggerCore.helper.js +10 -9
  8. package/dist/loggerCore/helpers/loggerCoreMethods/index.d.ts +1 -1
  9. package/dist/loggerCore/helpers/loggerCoreMethods/index.js +1 -1
  10. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.child.js +3 -1
  11. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.dispatch.d.ts +11 -0
  12. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.dispatch.js +121 -11
  13. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.entry.js +9 -2
  14. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.level.js +9 -2
  15. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.lifecycle.js +8 -0
  16. package/dist/loggerEntry/loggerEntry.core.js +0 -1
  17. package/dist/loggerEntry/loggerEntryHelpers/index.d.ts +2 -0
  18. package/dist/loggerEntry/loggerEntryHelpers/index.js +1 -0
  19. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.sanitize.d.ts +69 -0
  20. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.sanitize.js +107 -0
  21. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.serialize.d.ts +5 -1
  22. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.serialize.js +9 -5
  23. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.valueSerialize.js +9 -1
  24. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.context.js +9 -3
  25. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.js +3 -2
  26. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.json.js +19 -3
  27. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.metadata.js +14 -6
  28. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.text.js +31 -4
  29. package/dist/loggerOptions/loggerOptions.type.d.ts +12 -1
  30. package/dist/loggerOptions/loggerOptions.type.js +4 -1
  31. package/dist/loggerTransport/loggerTransportHelpers/loggerTransportHelpers.js +4 -3
  32. package/package.json +12 -5
  33. package/dist/.tsbuildinfo +0 -1
  34. package/dist/index.d.ts.map +0 -1
  35. package/dist/index.js.map +0 -1
  36. package/dist/loggerContext/index.d.ts.map +0 -1
  37. package/dist/loggerContext/index.js.map +0 -1
  38. package/dist/loggerContext/loggerContext.core.d.ts.map +0 -1
  39. package/dist/loggerContext/loggerContext.core.js.map +0 -1
  40. package/dist/loggerContext/loggerContext.type.d.ts.map +0 -1
  41. package/dist/loggerContext/loggerContext.type.js.map +0 -1
  42. package/dist/loggerContext/loggerContextCreate.d.ts.map +0 -1
  43. package/dist/loggerContext/loggerContextCreate.js.map +0 -1
  44. package/dist/loggerContext/loggerContextSerialize.d.ts.map +0 -1
  45. package/dist/loggerContext/loggerContextSerialize.js.map +0 -1
  46. package/dist/loggerContext/loggerContextStorage.d.ts.map +0 -1
  47. package/dist/loggerContext/loggerContextStorage.js.map +0 -1
  48. package/dist/loggerCore/core/index.d.ts.map +0 -1
  49. package/dist/loggerCore/core/index.js.map +0 -1
  50. package/dist/loggerCore/core/loggerCore.context.d.ts.map +0 -1
  51. package/dist/loggerCore/core/loggerCore.context.js.map +0 -1
  52. package/dist/loggerCore/core/loggerCore.core.d.ts.map +0 -1
  53. package/dist/loggerCore/core/loggerCore.core.js.map +0 -1
  54. package/dist/loggerCore/core/loggerCore.type.d.ts.map +0 -1
  55. package/dist/loggerCore/core/loggerCore.type.js.map +0 -1
  56. package/dist/loggerCore/helpers/index.d.ts.map +0 -1
  57. package/dist/loggerCore/helpers/index.js.map +0 -1
  58. package/dist/loggerCore/helpers/loggerCore.helper.d.ts.map +0 -1
  59. package/dist/loggerCore/helpers/loggerCore.helper.js.map +0 -1
  60. package/dist/loggerCore/helpers/loggerCoreHelpers.d.ts.map +0 -1
  61. package/dist/loggerCore/helpers/loggerCoreHelpers.js.map +0 -1
  62. package/dist/loggerCore/helpers/loggerCoreMethods/index.d.ts.map +0 -1
  63. package/dist/loggerCore/helpers/loggerCoreMethods/index.js.map +0 -1
  64. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.child.d.ts.map +0 -1
  65. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.child.js.map +0 -1
  66. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.dispatch.d.ts.map +0 -1
  67. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.dispatch.js.map +0 -1
  68. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.entry.d.ts.map +0 -1
  69. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.entry.js.map +0 -1
  70. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.level.d.ts.map +0 -1
  71. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.level.js.map +0 -1
  72. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.lifecycle.d.ts.map +0 -1
  73. package/dist/loggerCore/helpers/loggerCoreMethods/loggerCoreMethods.lifecycle.js.map +0 -1
  74. package/dist/loggerCore/helpers/loggerCoreMethods.logLevels.d.ts.map +0 -1
  75. package/dist/loggerCore/helpers/loggerCoreMethods.logLevels.js.map +0 -1
  76. package/dist/loggerCore/helpers/loggerCoreMethods.loggerProps.d.ts.map +0 -1
  77. package/dist/loggerCore/helpers/loggerCoreMethods.loggerProps.js.map +0 -1
  78. package/dist/loggerCore/index.d.ts.map +0 -1
  79. package/dist/loggerCore/index.js.map +0 -1
  80. package/dist/loggerEntry/index.d.ts.map +0 -1
  81. package/dist/loggerEntry/index.js.map +0 -1
  82. package/dist/loggerEntry/loggerEntry.core.d.ts.map +0 -1
  83. package/dist/loggerEntry/loggerEntry.core.js.map +0 -1
  84. package/dist/loggerEntry/loggerEntry.type.d.ts.map +0 -1
  85. package/dist/loggerEntry/loggerEntry.type.js.map +0 -1
  86. package/dist/loggerEntry/loggerEntryCreate.d.ts.map +0 -1
  87. package/dist/loggerEntry/loggerEntryCreate.js.map +0 -1
  88. package/dist/loggerEntry/loggerEntryHelpers/index.d.ts.map +0 -1
  89. package/dist/loggerEntry/loggerEntryHelpers/index.js.map +0 -1
  90. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.interfaces.d.ts.map +0 -1
  91. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.interfaces.js.map +0 -1
  92. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.serialize.d.ts.map +0 -1
  93. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.serialize.js.map +0 -1
  94. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.types.d.ts.map +0 -1
  95. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.types.js.map +0 -1
  96. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.valueSerialize.d.ts.map +0 -1
  97. package/dist/loggerEntry/loggerEntryHelpers/loggerEntryHelpers.valueSerialize.js.map +0 -1
  98. package/dist/loggerEntry/loggerEntrySerialize.d.ts.map +0 -1
  99. package/dist/loggerEntry/loggerEntrySerialize.js.map +0 -1
  100. package/dist/loggerErrors/index.d.ts.map +0 -1
  101. package/dist/loggerErrors/index.js.map +0 -1
  102. package/dist/loggerErrors/loggerError.base.d.ts.map +0 -1
  103. package/dist/loggerErrors/loggerError.base.js.map +0 -1
  104. package/dist/loggerErrors/loggerError.helpers.d.ts.map +0 -1
  105. package/dist/loggerErrors/loggerError.helpers.js.map +0 -1
  106. package/dist/loggerFactory/index.d.ts.map +0 -1
  107. package/dist/loggerFactory/index.js.map +0 -1
  108. package/dist/loggerFactory/loggerFactory.core.d.ts.map +0 -1
  109. package/dist/loggerFactory/loggerFactory.core.js.map +0 -1
  110. package/dist/loggerFormatter/index.d.ts.map +0 -1
  111. package/dist/loggerFormatter/index.js.map +0 -1
  112. package/dist/loggerFormatter/loggerFormatter.core.d.ts.map +0 -1
  113. package/dist/loggerFormatter/loggerFormatter.core.js.map +0 -1
  114. package/dist/loggerFormatter/loggerFormatter.type.d.ts.map +0 -1
  115. package/dist/loggerFormatter/loggerFormatter.type.js.map +0 -1
  116. package/dist/loggerFormatter/loggerFormatterFormatters/index.d.ts.map +0 -1
  117. package/dist/loggerFormatter/loggerFormatterFormatters/index.js.map +0 -1
  118. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.context.d.ts.map +0 -1
  119. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.context.js.map +0 -1
  120. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.d.ts.map +0 -1
  121. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.js.map +0 -1
  122. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.json.d.ts.map +0 -1
  123. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.json.js.map +0 -1
  124. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.metadata.d.ts.map +0 -1
  125. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.metadata.js.map +0 -1
  126. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.text.d.ts.map +0 -1
  127. package/dist/loggerFormatter/loggerFormatterFormatters/loggerFormatterFormatters.text.js.map +0 -1
  128. package/dist/loggerFormatter/loggerFormatterGuard.d.ts.map +0 -1
  129. package/dist/loggerFormatter/loggerFormatterGuard.js.map +0 -1
  130. package/dist/loggerLevel/index.d.ts.map +0 -1
  131. package/dist/loggerLevel/index.js.map +0 -1
  132. package/dist/loggerLevel/loggerLevel.type.d.ts.map +0 -1
  133. package/dist/loggerLevel/loggerLevel.type.js.map +0 -1
  134. package/dist/loggerManager/index.d.ts.map +0 -1
  135. package/dist/loggerManager/index.js.map +0 -1
  136. package/dist/loggerManager/loggerManager.core.d.ts.map +0 -1
  137. package/dist/loggerManager/loggerManager.core.js.map +0 -1
  138. package/dist/loggerOptions/index.d.ts.map +0 -1
  139. package/dist/loggerOptions/index.js.map +0 -1
  140. package/dist/loggerOptions/loggerOptions.type.d.ts.map +0 -1
  141. package/dist/loggerOptions/loggerOptions.type.js.map +0 -1
  142. package/dist/loggerTransport/index.d.ts.map +0 -1
  143. package/dist/loggerTransport/index.js.map +0 -1
  144. package/dist/loggerTransport/loggerTransport.core.d.ts.map +0 -1
  145. package/dist/loggerTransport/loggerTransport.core.js.map +0 -1
  146. package/dist/loggerTransport/loggerTransport.registry.d.ts.map +0 -1
  147. package/dist/loggerTransport/loggerTransport.registry.js.map +0 -1
  148. package/dist/loggerTransport/loggerTransport.type.d.ts.map +0 -1
  149. package/dist/loggerTransport/loggerTransport.type.js.map +0 -1
  150. package/dist/loggerTransport/loggerTransportComposite/index.d.ts.map +0 -1
  151. package/dist/loggerTransport/loggerTransportComposite/index.js.map +0 -1
  152. package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.buffered.d.ts.map +0 -1
  153. package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.buffered.js.map +0 -1
  154. package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.d.ts.map +0 -1
  155. package/dist/loggerTransport/loggerTransportComposite/loggerTransportComposite.js.map +0 -1
  156. package/dist/loggerTransport/loggerTransportConsole/index.d.ts.map +0 -1
  157. package/dist/loggerTransport/loggerTransportConsole/index.js.map +0 -1
  158. package/dist/loggerTransport/loggerTransportConsole/loggerTransportConsole.core.d.ts.map +0 -1
  159. package/dist/loggerTransport/loggerTransportConsole/loggerTransportConsole.core.js.map +0 -1
  160. package/dist/loggerTransport/loggerTransportGuard.d.ts.map +0 -1
  161. package/dist/loggerTransport/loggerTransportGuard.js.map +0 -1
  162. package/dist/loggerTransport/loggerTransportHelpers/index.d.ts.map +0 -1
  163. package/dist/loggerTransport/loggerTransportHelpers/index.js.map +0 -1
  164. package/dist/loggerTransport/loggerTransportHelpers/loggerTransportHelpers.d.ts.map +0 -1
  165. package/dist/loggerTransport/loggerTransportHelpers/loggerTransportHelpers.js.map +0 -1
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,94 @@ 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
+ ## Flushing
46
+
47
+ Dispatch completes synchronously when every transport is synchronous.
48
+ With an asynchronous transport — or with `asynchronous: true`, which
49
+ always defers so the caller stays off the transport's critical path —
50
+ writes are in flight until drained. `flush()` and `close()` drain them,
51
+ so nothing is lost at exit.
52
+
53
+ ## Secret redaction
54
+
55
+ Redaction is **on by default**. Metadata and context fields whose NAME
56
+ looks like a secret — password, secret, token, api key, private key,
57
+ credential, authorization, cookie — are replaced with `"[REDACTED]"`
58
+ before the entry reaches any formatter or transport. Nested objects,
59
+ arrays and getters are all covered.
60
+
61
+ ```typescript
62
+ logger.info("login", { user: "alice", password: "hunter2" });
63
+ // metadata: { user: "alice", password: "[REDACTED]" }
64
+
65
+ createLogger({ redact: { keys: ["ssn"], replacement: "***" } });
66
+ createLogger({ redact: { enabled: false } }); // opt out
20
67
  ```
21
68
 
22
- ## Features
69
+ ## Log injection
70
+
71
+ Text-shaped formatters escape control characters in the message, the
72
+ logger name, metadata keys and values, context values and source
73
+ locations. A newline or ANSI escape inside attacker-supplied text
74
+ becomes `\n` / `` rather than forging an extra log record or
75
+ driving the operator's terminal. The JSON formatter relies on
76
+ `JSON.stringify`, which escapes the same characters.
77
+
78
+ Metadata is normalized before serialization, so circular references
79
+ (`"[Circular]"`), BigInt values and functions never make a formatter
80
+ throw and silently drop the record.
81
+
82
+ ## Context
83
+
84
+ ```typescript
85
+ import { createLoggerContext, withLoggerContext } from "@zudojs/logger";
86
+
87
+ const scoped = logger.withContext(
88
+ createLoggerContext({ requestId: "req-1", metadata: { tenant: "acme" } }),
89
+ );
90
+ scoped.info("handled"); // metadata carries requestId and tenant
91
+
92
+ withLoggerContext(logger, createLoggerContext({ traceId }), (scoped) => {
93
+ scoped.info("inside the trace");
94
+ });
95
+ ```
96
+
97
+ Child loggers inherit name, level, formatter, transports, metadata and
98
+ redaction settings: `logger.child({ name: "api.db" })`.
99
+
100
+ ## Formatters
101
+
102
+ `createTextLoggerFormatter`, `createJsonLoggerFormatter`,
103
+ `createCompactLoggerFormatter`, `createDevelopmentLoggerFormatter`,
104
+ `createProductionLoggerFormatter`, `createStructuredLoggerFormatter`.
23
105
 
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
106
+ Pass `{ colors: true }` in the formatter context to colourize the level
107
+ tag of text output. Colour codes are emitted only around the fixed level
108
+ name, never around user-supplied text.
30
109
 
31
110
  ## Use Cases
32
111
 
@@ -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,7 @@ export declare class ZudojsLogger implements Logger, ZudojsLoggerContext {
27
31
  private _configuration;
28
32
  private readonly _contextStorage;
29
33
  private _disposed;
34
+ private readonly _pending;
30
35
  constructor(options?: LoggerOptions, contextStorage?: LoggerContextStorage);
31
36
  get configuration(): LoggerConfiguration;
32
37
  get contextStorage(): LoggerContextStorage;
@@ -54,6 +59,8 @@ export declare class ZudojsLogger implements Logger, ZudojsLoggerContext {
54
59
  markDisposed(): void;
55
60
  updateConfiguration(config: LoggerConfiguration): void;
56
61
  createChildLogger(options: LoggerOptions): Logger;
62
+ trackDispatch(dispatch: Promise<void>): void;
63
+ drainDispatches(): Promise<void>;
57
64
  }
58
65
  /**
59
66
  * Creates a Zudojs logger.
@@ -14,6 +14,7 @@ export class ZudojsLogger {
14
14
  _configuration;
15
15
  _contextStorage;
16
16
  _disposed = false;
17
+ _pending = new Set();
17
18
  constructor(options = {}, contextStorage) {
18
19
  this._configuration = resolveLoggerOptions(options);
19
20
  this._contextStorage = contextStorage ?? createLoggerContextStorage();
@@ -97,6 +98,24 @@ export class ZudojsLogger {
97
98
  createChildLogger(options) {
98
99
  return new ZudojsLogger(options, this._contextStorage);
99
100
  }
101
+ trackDispatch(dispatch) {
102
+ // dispatchEntry routes its own failures through handleError, so a
103
+ // rejection here is unexpected; swallow it to keep an unawaited log
104
+ // call from crashing the process, and keep the entry drainable.
105
+ const tracked = dispatch
106
+ .catch(() => { })
107
+ .finally(() => {
108
+ this._pending.delete(tracked);
109
+ });
110
+ this._pending.add(tracked);
111
+ }
112
+ async drainDispatches() {
113
+ // A dispatch can start further dispatches (a transport that logs),
114
+ // so drain until the set is genuinely empty.
115
+ while (this._pending.size > 0) {
116
+ await Promise.all([...this._pending]);
117
+ }
118
+ }
100
119
  }
101
120
  /**
102
121
  * 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,17 @@ export function createEntry(configuration, contextStorage, level, message, optio
13
14
  const contextMetadata = activeContext
14
15
  ? contextToLogMetadata(activeContext)
15
16
  : {};
16
- const metadata = {
17
+ const rawMetadata = {
17
18
  ...configuration.metadata,
18
19
  ...contextMetadata,
19
20
  ...(options.metadata ?? {}),
20
21
  };
22
+ // Redaction is applied HERE, before the entry is frozen, so every
23
+ // formatter and every transport sees the already-masked value and no
24
+ // path can bypass it — including nested objects, arrays and getters.
25
+ const isSecret = createSecretMatcher(configuration.redact);
26
+ const replacement = configuration.redact.replacement ?? LOGGER_REDACTION_TOKEN;
27
+ const metadata = redactLogValue(rawMetadata, isSecret, replacement);
21
28
  const context = options.context
22
29
  ? createLoggerContext({
23
30
  parent: activeContext,
@@ -30,7 +37,7 @@ export function createEntry(configuration, contextStorage, level, message, optio
30
37
  metadata,
31
38
  context: context
32
39
  ? {
33
- metadata: context.metadata,
40
+ metadata: redactLogValue(context.metadata, isSecret, replacement),
34
41
  }
35
42
  : undefined,
36
43
  source: options.source,
@@ -4,7 +4,7 @@
4
4
  import { LoggerLevel, shouldLog, } from "../../../loggerLevel/loggerLevel.type.js";
5
5
  import { LoggerConfigurationError } from "../../../loggerErrors/loggerError.base.js";
6
6
  import { createEntry } from "./loggerCoreMethods.entry.js";
7
- import { dispatchEntry } from "./loggerCoreMethods.dispatch.js";
7
+ import { dispatchEntrySync } from "./loggerCoreMethods.dispatch.js";
8
8
  /**
9
9
  * Level logging methods extracted from ZudojsLogger.
10
10
  */
@@ -18,6 +18,13 @@ export function logAtLevel(ctx, level, message, options = {}) {
18
18
  throw new LoggerConfigurationError("Logger message must be a string.");
19
19
  }
20
20
  const entry = createEntry(ctx.configuration, ctx.contextStorage, level, message, options);
21
- void dispatchEntry(ctx.configuration, entry, (error) => ctx.handleInfrastructureError(error));
21
+ // Dispatch is asynchronous. It used to be fired and forgotten, so
22
+ // flush() and close() could return while entries were still in
23
+ // flight — messages were lost on process exit. Registering the
24
+ // promise lets the lifecycle methods drain it.
25
+ const dispatch = dispatchEntrySync(ctx.configuration, entry, (error) => ctx.handleInfrastructureError(error));
26
+ if (dispatch) {
27
+ ctx.trackDispatch(dispatch);
28
+ }
22
29
  }
23
30
  //# sourceMappingURL=loggerCoreMethods.level.js.map
@@ -48,6 +48,10 @@ export function disableLogger(ctx) {
48
48
  */
49
49
  export async function flushLogger(ctx) {
50
50
  ctx.assertActive();
51
+ // In-flight dispatches must land in the transports before those
52
+ // transports are asked to flush, otherwise flush() is a no-op for
53
+ // everything logged in the same tick.
54
+ await ctx.drainDispatches();
51
55
  for (const transport of ctx.configuration.transports) {
52
56
  if (!isLoggerTransport(transport)) {
53
57
  continue;
@@ -68,11 +72,15 @@ export async function closeLogger(ctx) {
68
72
  if (ctx.isDisposed()) {
69
73
  return;
70
74
  }
75
+ await ctx.drainDispatches();
71
76
  for (const transport of ctx.configuration.transports) {
72
77
  if (!isLoggerTransport(transport)) {
73
78
  continue;
74
79
  }
75
80
  const registered = createLoggerTransport(transport);
81
+ if (registered.flush) {
82
+ await registered.flush();
83
+ }
76
84
  if (registered.close) {
77
85
  await registered.close();
78
86
  }
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Core logger entry creation functions.
3
3
  */
4
- import { loggerLevelNameFallback } from "./loggerEntryHelpers/loggerEntryHelpers.serialize.js";
5
4
  import { createLoggerEntry } from "./loggerEntryCreate.js";
6
5
  /**
7
6
  * Creates a unique log entry identifier.
@@ -6,4 +6,6 @@
6
6
  export type { LogValue, LogMetadata, LoggerSource, LoggerEntryContext, } from "./loggerEntryHelpers.types.js";
7
7
  export type { LoggerEntry, LoggerEntryInput, } from "./loggerEntryHelpers.interfaces.js";
8
8
  export { serializeLoggerEntry, serializeLoggerError, serializeLoggerValue, loggerLevelNameFallback, } from "./loggerEntryHelpers.serialize.js";
9
+ export { LOGGER_REDACTION_TOKEN, DEFAULT_LOGGER_SECRET_PATTERN, escapeLogText, hasLogControlCharacters, createSecretMatcher, redactLogValue, } from "./loggerEntryHelpers.sanitize.js";
10
+ export type { LoggerRedactionOptions } from "./loggerEntryHelpers.sanitize.js";
9
11
  //# sourceMappingURL=index.d.ts.map
@@ -4,4 +4,5 @@
4
4
  * Logger entry helper types and utilities.
5
5
  */
6
6
  export { serializeLoggerEntry, serializeLoggerError, serializeLoggerValue, loggerLevelNameFallback, } from "./loggerEntryHelpers.serialize.js";
7
+ export { LOGGER_REDACTION_TOKEN, DEFAULT_LOGGER_SECRET_PATTERN, escapeLogText, hasLogControlCharacters, createSecretMatcher, redactLogValue, } from "./loggerEntryHelpers.sanitize.js";
7
8
  //# sourceMappingURL=index.js.map