@warlock.js/logger 5.29.0 → 5.31.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 (81) hide show
  1. package/CHANGELOG.md +48 -2
  2. package/README.md +14 -17
  3. package/cjs/index.cjs +569 -57
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/channels/console-log.d.mts +1 -1
  6. package/esm/channels/console-log.d.mts.map +1 -1
  7. package/esm/channels/console-log.mjs +1 -1
  8. package/esm/channels/console-log.mjs.map +1 -1
  9. package/esm/channels/file-log.d.mts +129 -13
  10. package/esm/channels/file-log.d.mts.map +1 -1
  11. package/esm/channels/file-log.mjs +204 -23
  12. package/esm/channels/file-log.mjs.map +1 -1
  13. package/esm/channels/file-name-pattern.d.mts +16 -0
  14. package/esm/channels/file-name-pattern.d.mts.map +1 -0
  15. package/esm/channels/file-name-pattern.mjs +41 -0
  16. package/esm/channels/file-name-pattern.mjs.map +1 -0
  17. package/esm/channels/index.mjs +1 -1
  18. package/esm/channels/json-file-log.d.mts +1 -1
  19. package/esm/channels/json-file-log.d.mts.map +1 -1
  20. package/esm/channels/json-file-log.mjs +25 -6
  21. package/esm/channels/json-file-log.mjs.map +1 -1
  22. package/esm/channels/sentry-log.d.mts +1 -1
  23. package/esm/channels/sentry-log.d.mts.map +1 -1
  24. package/esm/channels/sentry-log.mjs +1 -1
  25. package/esm/channels/sentry-log.mjs.map +1 -1
  26. package/esm/index.d.mts +8 -5
  27. package/esm/index.mjs +5 -3
  28. package/esm/log-channel.d.mts +24 -2
  29. package/esm/log-channel.d.mts.map +1 -1
  30. package/esm/log-channel.mjs +23 -1
  31. package/esm/log-channel.mjs.map +1 -1
  32. package/esm/logger.d.mts +78 -18
  33. package/esm/logger.d.mts.map +1 -1
  34. package/esm/logger.mjs +163 -22
  35. package/esm/logger.mjs.map +1 -1
  36. package/esm/redact/default-keys.d.mts +1 -1
  37. package/esm/redact/default-keys.d.mts.map +1 -1
  38. package/esm/redact/default-keys.mjs +1 -1
  39. package/esm/redact/default-keys.mjs.map +1 -1
  40. package/esm/redact/escape-log-value.d.mts +26 -0
  41. package/esm/redact/escape-log-value.d.mts.map +1 -0
  42. package/esm/redact/escape-log-value.mjs +37 -0
  43. package/esm/redact/escape-log-value.mjs.map +1 -0
  44. package/esm/redact/index.mjs +2 -0
  45. package/esm/redact/mask-email.d.mts +40 -0
  46. package/esm/redact/mask-email.d.mts.map +1 -0
  47. package/esm/redact/mask-email.mjs +70 -0
  48. package/esm/redact/mask-email.mjs.map +1 -0
  49. package/esm/redact/redact.d.mts +1 -1
  50. package/esm/redact/redact.d.mts.map +1 -1
  51. package/esm/redact/redact.mjs +1 -1
  52. package/esm/redact/redact.mjs.map +1 -1
  53. package/esm/types.d.mts +10 -2
  54. package/esm/types.d.mts.map +1 -1
  55. package/esm/utils/capture-unhandled-errors.d.mts +1 -1
  56. package/esm/utils/capture-unhandled-errors.d.mts.map +1 -1
  57. package/esm/utils/capture-unhandled-errors.mjs +1 -1
  58. package/esm/utils/capture-unhandled-errors.mjs.map +1 -1
  59. package/esm/utils/clear-message.d.mts +1 -1
  60. package/esm/utils/clear-message.d.mts.map +1 -1
  61. package/esm/utils/clear-message.mjs +1 -1
  62. package/esm/utils/clear-message.mjs.map +1 -1
  63. package/esm/utils/safe-json-stringify.d.mts +1 -1
  64. package/esm/utils/safe-json-stringify.d.mts.map +1 -1
  65. package/esm/utils/safe-json-stringify.mjs +1 -1
  66. package/esm/utils/safe-json-stringify.mjs.map +1 -1
  67. package/llms-full.txt +247 -147
  68. package/llms.txt +2 -2
  69. package/package.json +4 -3
  70. package/skills/capture-unhandled-errors/SKILL.md +9 -10
  71. package/skills/configure-logger/SKILL.md +7 -10
  72. package/skills/filter-log-entries/SKILL.md +2 -2
  73. package/skills/flush-logs-on-shutdown/SKILL.md +34 -15
  74. package/skills/index.md +4 -1
  75. package/skills/logger-basics/SKILL.md +21 -24
  76. package/skills/overview/SKILL.md +17 -6
  77. package/skills/pick-log-channel/SKILL.md +36 -24
  78. package/skills/redact-sensitive-log-fields/SKILL.md +48 -18
  79. package/skills/ship-logs-to-sentry/SKILL.md +18 -18
  80. package/skills/test-logging-code/SKILL.md +4 -2
  81. package/skills/write-custom-log-channel/SKILL.md +51 -18
@@ -1 +1 @@
1
- {"version":3,"file":"log-channel.mjs","names":[],"sources":["../../../../../../logger/src/log-channel.ts"],"sourcesContent":["import type {\n BasicLogConfigurations,\n LogContract,\n LoggingData,\n RedactConfig,\n} from \"./types\";\n\nexport abstract class LogChannel<\n Options extends BasicLogConfigurations = BasicLogConfigurations,\n> implements LogContract {\n /**\n * Channel name\n */\n public name!: string;\n\n /**\n * Channel description\n */\n public description?: string;\n\n /**\n * Determine if channel is logging in terminal\n */\n public terminal = false;\n\n /**\n * Default Configurations\n */\n protected defaultConfigurations: Options = {} as Options;\n\n /**\n * Channel configurations\n */\n protected channelConfigurations: Options = {} as Options; //\n\n /**\n * Determine whether the channel is fully initialized\n */\n protected isInitialized = false;\n\n /**\n * Constructor\n */\n public constructor(configurations?: Options) {\n if (configurations) {\n this.setConfigurations(configurations);\n }\n\n setTimeout(async () => {\n if (this.init) {\n await this.init();\n }\n\n this.isInitialized = true;\n }, 0);\n }\n\n /**\n * Initialize the channel\n */\n protected init?(): void | Promise<void>;\n\n /**\n * Get config value\n */\n protected config<K extends keyof Options>(key: K): Options[K] {\n return (\n this.channelConfigurations[key] ?? (this.defaultConfigurations ?? {})[key]\n );\n }\n\n /**\n * Set configurations\n */\n protected setConfigurations(configurations: Options) {\n this.channelConfigurations = {\n ...this.channelConfigurations,\n ...configurations,\n };\n\n return this;\n }\n\n /**\n * Determine if the message should be logged\n */\n protected shouldBeLogged(data: LoggingData): boolean {\n // check for debug mode\n const allowedLevels = this.config(\"levels\");\n\n if (allowedLevels?.length && !allowedLevels.includes(data.type))\n return false;\n\n const filter = this.config(\"filter\");\n\n if (filter) {\n return filter(data);\n }\n\n return true;\n }\n\n /**\n * Log the given message\n */\n public abstract log(data: LoggingData): void | Promise<void>;\n\n /**\n * Synchronously flush messages\n */\n public flushSync?(): void;\n\n /**\n * Asynchronously flush buffered messages.\n *\n * Optional — channels that write synchronously per entry (e.g. `ConsoleLog`)\n * don't implement it. Buffering channels override this to drain their buffer\n * over async I/O so callers can `await log.flush()` on graceful shutdown.\n */\n public flush?(): void | Promise<void>;\n\n /**\n * Read the channel's redact config (if any). Used by `Logger` to apply\n * per-channel additive redaction on top of the logger-wide floor.\n * Subclasses normally don't override this — set `redact` in your channel\n * configuration instead.\n */\n public getRedactConfig(): RedactConfig | undefined {\n return this.config(\"redact\" as keyof Options) as RedactConfig | undefined;\n }\n\n /**\n * Get date and time formats\n */\n protected getDateAndTimeFormat() {\n const dateFormat = this.config(\"dateFormat\");\n const date = dateFormat?.date ?? \"DD-MM-YYYY\";\n const time = dateFormat?.time ?? \"HH:mm:ss\";\n\n return { date, time };\n }\n\n /**\n * get basic configurations with the given ones\n */\n protected withBasicConfigurations(configurations: Partial<Options>): Options {\n return {\n filter: () => true,\n ...configurations,\n } as any as Options;\n }\n}\n"],"mappings":";AAOA,IAAsB,aAAtB,MAEyB;;;;CAkCvB,AAAO,YAAY,gBAA0B;kBApB3B;+BAKyB,CAAC;+BAKD,CAAC;uBAKlB;EAMxB,IAAI,gBACF,KAAK,kBAAkB,cAAc;EAGvC,WAAW,YAAY;GACrB,IAAI,KAAK,MACP,MAAM,KAAK,KAAK;GAGlB,KAAK,gBAAgB;EACvB,GAAG,CAAC;CACN;;;;CAUA,AAAU,OAAgC,KAAoB;EAC5D,OACE,KAAK,sBAAsB,SAAS,KAAK,yBAAyB,CAAC,EAAC,CAAE;CAE1E;;;;CAKA,AAAU,kBAAkB,gBAAyB;EACnD,KAAK,wBAAwB;GAC3B,GAAG,KAAK;GACR,GAAG;EACL;EAEA,OAAO;CACT;;;;CAKA,AAAU,eAAe,MAA4B;EAEnD,MAAM,gBAAgB,KAAK,OAAO,QAAQ;EAE1C,IAAI,eAAe,UAAU,CAAC,cAAc,SAAS,KAAK,IAAI,GAC5D,OAAO;EAET,MAAM,SAAS,KAAK,OAAO,QAAQ;EAEnC,IAAI,QACF,OAAO,OAAO,IAAI;EAGpB,OAAO;CACT;;;;;;;CA2BA,AAAO,kBAA4C;EACjD,OAAO,KAAK,OAAO,QAAyB;CAC9C;;;;CAKA,AAAU,uBAAuB;EAC/B,MAAM,aAAa,KAAK,OAAO,YAAY;EAI3C,OAAO;GAAE,MAHI,YAAY,QAAQ;GAGlB,MAFF,YAAY,QAAQ;EAEb;CACtB;;;;CAKA,AAAU,wBAAwB,gBAA2C;EAC3E,OAAO;GACL,cAAc;GACd,GAAG;EACL;CACF;AACF"}
1
+ {"version":3,"file":"log-channel.mjs","names":[],"sources":["../../../../../../../packages/logger/src/log-channel.ts"],"sourcesContent":["import type { BasicLogConfigurations, LogContract, LoggingData, RedactConfig } from \"./types\";\n\n/**\n * Receives a failure the channel hits outside a `log()` call (for example\n * `FileLog` retention). The logger installs one that forwards to its\n * `onChannelError` handler.\n */\nexport type ChannelErrorReporter = (error: unknown, entry: LoggingData) => void;\n\nexport abstract class LogChannel<\n Options extends BasicLogConfigurations = BasicLogConfigurations,\n> implements LogContract {\n /**\n * Channel name\n */\n public name!: string;\n\n /**\n * Channel description\n */\n public description?: string;\n\n /**\n * Determine if channel is logging in terminal\n */\n public terminal = false;\n\n /**\n * Default Configurations\n */\n protected defaultConfigurations: Options = {} as Options;\n\n /**\n * Channel configurations\n */\n protected channelConfigurations: Options = {} as Options; //\n\n /**\n * Determine whether the channel is fully initialized\n */\n protected isInitialized = false;\n\n /**\n * Where {@link reportError} sends failures; set by the logger the channel\n * is added to.\n */\n protected errorReporter?: ChannelErrorReporter;\n\n /**\n * Constructor\n */\n public constructor(configurations?: Options) {\n if (configurations) {\n this.setConfigurations(configurations);\n }\n\n setTimeout(async () => {\n if (this.init) {\n await this.init();\n }\n\n this.isInitialized = true;\n }, 0);\n }\n\n /**\n * Route this channel's own failures (those outside a `log()` call) to the\n * given reporter. `Logger` calls this when the channel is added, so the\n * failures reach `log.onChannelError(...)`.\n */\n public setErrorReporter(reporter: ChannelErrorReporter | undefined): void {\n this.errorReporter = reporter;\n }\n\n /**\n * Report a failure that happened outside a `log()` call. Never throws:\n * without a reporter it writes one line to stderr.\n */\n protected reportError(error: unknown, entry: LoggingData): void {\n try {\n if (this.errorReporter) {\n this.errorReporter(error, entry);\n return;\n }\n\n const reason = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[@warlock.js/logger] channel \"${this.name}\" failed: ${reason}\\n`);\n } catch {\n // Reporting a failure must never become one.\n }\n }\n\n /**\n * Initialize the channel\n */\n protected init?(): void | Promise<void>;\n\n /**\n * Get config value\n */\n protected config<K extends keyof Options>(key: K): Options[K] {\n return this.channelConfigurations[key] ?? (this.defaultConfigurations ?? {})[key];\n }\n\n /**\n * Set configurations\n */\n protected setConfigurations(configurations: Options) {\n this.channelConfigurations = {\n ...this.channelConfigurations,\n ...configurations,\n };\n\n return this;\n }\n\n /**\n * Determine if the message should be logged\n */\n protected shouldBeLogged(data: LoggingData): boolean {\n // check for debug mode\n const allowedLevels = this.config(\"levels\");\n\n if (allowedLevels?.length && !allowedLevels.includes(data.type)) return false;\n\n const filter = this.config(\"filter\");\n\n if (filter) {\n return filter(data);\n }\n\n return true;\n }\n\n /**\n * Log the given message\n */\n public abstract log(data: LoggingData): void | Promise<void>;\n\n /**\n * Synchronously flush messages\n */\n public flushSync?(): void;\n\n /**\n * Asynchronously flush buffered messages.\n *\n * Optional — channels that write synchronously per entry (e.g. `ConsoleLog`)\n * don't implement it. Buffering channels override this to drain their buffer\n * over async I/O so callers can `await log.flush()` on graceful shutdown.\n */\n public flush?(): void | Promise<void>;\n\n /**\n * Read the channel's redact config (if any). Used by `Logger` to apply\n * per-channel additive redaction on top of the logger-wide floor.\n * Subclasses normally don't override this — set `redact` in your channel\n * configuration instead.\n */\n public getRedactConfig(): RedactConfig | undefined {\n return this.config(\"redact\" as keyof Options) as RedactConfig | undefined;\n }\n\n /**\n * Get date and time formats\n */\n protected getDateAndTimeFormat() {\n const dateFormat = this.config(\"dateFormat\");\n const date = dateFormat?.date ?? \"DD-MM-YYYY\";\n const time = dateFormat?.time ?? \"HH:mm:ss\";\n\n return { date, time };\n }\n\n /**\n * get basic configurations with the given ones\n */\n protected withBasicConfigurations(configurations: Partial<Options>): Options {\n return {\n filter: () => true,\n ...configurations,\n } as any as Options;\n }\n}\n"],"mappings":";AASA,IAAsB,aAAtB,MAEyB;;;;CAwCvB,AAAO,YAAY,gBAA0B;kBA1B3B;+BAKyB,CAAC;+BAKD,CAAC;uBAKlB;EAYxB,IAAI,gBACF,KAAK,kBAAkB,cAAc;EAGvC,WAAW,YAAY;GACrB,IAAI,KAAK,MACP,MAAM,KAAK,KAAK;GAGlB,KAAK,gBAAgB;EACvB,GAAG,CAAC;CACN;;;;;;CAOA,AAAO,iBAAiB,UAAkD;EACxE,KAAK,gBAAgB;CACvB;;;;;CAMA,AAAU,YAAY,OAAgB,OAA0B;EAC9D,IAAI;GACF,IAAI,KAAK,eAAe;IACtB,KAAK,cAAc,OAAO,KAAK;IAC/B;GACF;GAEA,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACpE,QAAQ,OAAO,MAAM,iCAAiC,KAAK,KAAK,YAAY,OAAO,GAAG;EACxF,QAAQ,CAER;CACF;;;;CAUA,AAAU,OAAgC,KAAoB;EAC5D,OAAO,KAAK,sBAAsB,SAAS,KAAK,yBAAyB,CAAC,EAAC,CAAE;CAC/E;;;;CAKA,AAAU,kBAAkB,gBAAyB;EACnD,KAAK,wBAAwB;GAC3B,GAAG,KAAK;GACR,GAAG;EACL;EAEA,OAAO;CACT;;;;CAKA,AAAU,eAAe,MAA4B;EAEnD,MAAM,gBAAgB,KAAK,OAAO,QAAQ;EAE1C,IAAI,eAAe,UAAU,CAAC,cAAc,SAAS,KAAK,IAAI,GAAG,OAAO;EAExE,MAAM,SAAS,KAAK,OAAO,QAAQ;EAEnC,IAAI,QACF,OAAO,OAAO,IAAI;EAGpB,OAAO;CACT;;;;;;;CA2BA,AAAO,kBAA4C;EACjD,OAAO,KAAK,OAAO,QAAyB;CAC9C;;;;CAKA,AAAU,uBAAuB;EAC/B,MAAM,aAAa,KAAK,OAAO,YAAY;EAI3C,OAAO;GAAE,MAHI,YAAY,QAAQ;GAGlB,MAFF,YAAY,QAAQ;EAEb;CACtB;;;;CAKA,AAAU,wBAAwB,gBAA2C;EAC3E,OAAO;GACL,cAAc;GACd,GAAG;EACL;CACF;AACF"}
package/esm/logger.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { AutoFlushEvent, BasicLogConfigurations, LogLevel, LoggingData, OmittedLoggingData, RedactConfig } from "./types.mjs";
1
+ import { AutoFlushEvent, BasicLogConfigurations, ChannelErrorHandler, LogLevel, LoggingData, OmittedLoggingData, RedactConfig } from "./types.mjs";
2
2
  import { LogChannel } from "./log-channel.mjs";
3
3
 
4
- //#region ../logger/src/logger.d.ts
4
+ //#region ../../packages/logger/src/logger.d.ts
5
5
  declare class Logger {
6
6
  /**
7
7
  * Current channel
@@ -28,6 +28,18 @@ declare class Logger {
28
28
  * set here.
29
29
  */
30
30
  private redactConfig?;
31
+ /**
32
+ * Deliveries still in flight on async channels, per channel. `log()` never
33
+ * awaits them; `flush()` does, so a graceful shutdown still waits for every
34
+ * entry. Kept per channel so one hung channel cannot hold up another's
35
+ * drain.
36
+ */
37
+ private pendingDeliveries;
38
+ /**
39
+ * Receives a channel's delivery failure (a sync throw or a rejected async
40
+ * `log()`). Defaults to a single stderr line, never back through the logger.
41
+ */
42
+ private channelErrorHandler;
31
43
  /**
32
44
  * Add a new channel
33
45
  */
@@ -40,7 +52,19 @@ declare class Logger {
40
52
  autoFlushOn?: AutoFlushEvent[];
41
53
  minLevel?: LogLevel;
42
54
  redact?: RedactConfig;
55
+ onChannelError?: ChannelErrorHandler;
43
56
  }): this;
57
+ /**
58
+ * Handle a channel's delivery failure. Logging is fire-and-forget, so a
59
+ * channel that throws or rejects never reaches the call site and never
60
+ * becomes an unhandled rejection; it goes to this handler instead. The
61
+ * default writes one line to stderr. The handler must not log through this
62
+ * logger (a failing channel would loop).
63
+ *
64
+ * @example
65
+ * log.onChannelError((error, channel) => metrics.increment(`log.${channel.name}.failed`));
66
+ */
67
+ onChannelError(handler: ChannelErrorHandler): this;
44
68
  /**
45
69
  * Set the logger-wide redaction floor. Applied to every entry before
46
70
  * fan-out; channel configs add more paths on top, never fewer. Pass
@@ -76,6 +100,11 @@ declare class Logger {
76
100
  * Set channels
77
101
  */
78
102
  setChannels(channels: LogChannel[]): this;
103
+ /**
104
+ * Route a channel's own failures (outside `log()`, such as file retention)
105
+ * to this logger's channel error handler.
106
+ */
107
+ private attachChannel;
79
108
  /**
80
109
  * Normalize log data to a single object
81
110
  */
@@ -88,28 +117,40 @@ declare class Logger {
88
117
  * — each channel sees its own shallow clone so one channel cannot observe
89
118
  * another's mutations (e.g. a later terminal channel still sees the original
90
119
  * colored message).
120
+ *
121
+ * Synchronous and fire-and-forget: it returns the logger, not a promise.
122
+ * Async channel deliveries are tracked and awaited by {@link flush}; a
123
+ * channel that throws or rejects is isolated from the others and reported
124
+ * to {@link onChannelError}.
125
+ */
126
+ log(data: LoggingData): this;
127
+ /**
128
+ * Prepare and hand one entry to one channel, isolating its failure (a
129
+ * channel redaction that throws included) and tracking an async delivery
130
+ * until it settles.
91
131
  */
92
- log(data: LoggingData): Promise<this>;
132
+ private deliver;
133
+ private reportChannelError;
93
134
  /**
94
135
  * Make debug log
95
136
  */
96
- debug(dataOrModule: OmittedLoggingData | string, action?: string, message?: any, context?: Record<string, any>): Promise<this>;
137
+ debug(dataOrModule: OmittedLoggingData | string, action?: string, message?: any, context?: Record<string, any>): this;
97
138
  /**
98
139
  * Make info log
99
140
  */
100
- info(dataOrModule: OmittedLoggingData | string, action?: string, message?: any, context?: Record<string, any>): Promise<this>;
141
+ info(dataOrModule: OmittedLoggingData | string, action?: string, message?: any, context?: Record<string, any>): this;
101
142
  /**
102
143
  * Make warn log
103
144
  */
104
- warn(dataOrModule: OmittedLoggingData | string, action?: string, message?: any, context?: Record<string, any>): Promise<this>;
145
+ warn(dataOrModule: OmittedLoggingData | string, action?: string, message?: any, context?: Record<string, any>): this;
105
146
  /**
106
147
  * Make error log
107
148
  */
108
- error(dataOrModule: OmittedLoggingData | string, action?: string, message?: any, context?: Record<string, any>): Promise<this>;
149
+ error(dataOrModule: OmittedLoggingData | string, action?: string, message?: any, context?: Record<string, any>): this;
109
150
  /**
110
151
  * Make success log
111
152
  */
112
- success(dataOrModule: OmittedLoggingData | string, action?: string, message?: any, context?: Record<string, any>): Promise<this>;
153
+ success(dataOrModule: OmittedLoggingData | string, action?: string, message?: any, context?: Record<string, any>): this;
113
154
  /**
114
155
  * Make fatal log — for unrecoverable failures where the application is going
115
156
  * down (failed bootstrap, lost connection to a required dependency that the
@@ -119,7 +160,7 @@ declare class Logger {
119
160
  * `fatal` does NOT auto-flush or exit. The caller decides whether to call
120
161
  * `await log.flush()` and `process.exit(...)`.
121
162
  */
122
- fatal(dataOrModule: OmittedLoggingData | string, action?: string, message?: any, context?: Record<string, any>): Promise<this>;
163
+ fatal(dataOrModule: OmittedLoggingData | string, action?: string, message?: any, context?: Record<string, any>): this;
123
164
  /**
124
165
  * Log an `error` entry when `condition` is falsy. No-op otherwise — the
125
166
  * entry is never built and channels are not invoked, so this is genuinely
@@ -129,7 +170,7 @@ declare class Logger {
129
170
  * @example
130
171
  * log.assert(user !== null, "auth", "session", "user vanished mid-flight", { sessionId });
131
172
  */
132
- assert(condition: unknown, module: string, action: string, message: any, context?: Record<string, any>): Promise<Logger> | Logger;
173
+ assert(condition: unknown, module: string, action: string, message: any, context?: Record<string, any>): this;
133
174
  /**
134
175
  * Start a duration timer. The returned function emits an `info` entry
135
176
  * with `completed in <ms>ms` and a `durationMs` field in `context` when
@@ -140,7 +181,7 @@ declare class Logger {
140
181
  * const user = await usersRepo.findById(id);
141
182
  * end({ id, found: !!user });
142
183
  */
143
- timer(module: string, action: string): (extra?: Record<string, any>) => Promise<Logger>;
184
+ timer(module: string, action: string): (extra?: Record<string, any>) => this;
144
185
  /**
145
186
  * Get channel by name
146
187
  */
@@ -150,7 +191,9 @@ declare class Logger {
150
191
  */
151
192
  flushSync(): void;
152
193
  /**
153
- * Asynchronously drain every channel that implements `flush()`.
194
+ * Wait for every in-flight async delivery, then drain every channel that
195
+ * implements `flush()` — bounded by `timeout` (default
196
+ * {@link DEFAULT_FLUSH_TIMEOUT_MS}).
154
197
  *
155
198
  * Unlike {@link flushSync}, this awaits each channel's async I/O — the
156
199
  * correct call for a graceful shutdown that can afford to wait
@@ -158,18 +201,30 @@ declare class Logger {
158
201
  * `process.exit`). A channel whose delivery is async (a network transport,
159
202
  * an async disk write) implements `flush()`, not `flushSync()`.
160
203
  *
161
- * Channels are isolated: a channel whose flush rejects can neither prevent
162
- * the others from draining nor escape as an unhandled rejection. Channels
163
- * without `flush()` are skipped.
204
+ * Channels drain concurrently and independently: each waits for its own
205
+ * pending deliveries (including ones logged while the flush runs), then
206
+ * runs its `flush()`. A channel whose delivery hangs therefore cannot hold
207
+ * up another channel's drain, and a channel whose flush rejects can neither
208
+ * stop the others nor escape as an unhandled rejection. When `timeout`
209
+ * elapses, `flush()` resolves anyway and each channel still draining is
210
+ * reported to {@link onChannelError}. Channels without `flush()` only wait
211
+ * for their deliveries.
164
212
  *
165
213
  * @example
166
214
  * async function shutdown() {
167
215
  * await httpServer.close();
168
- * await log.flush();
216
+ * await log.flush({ timeout: 3000 });
169
217
  * process.exit(0);
170
218
  * }
171
219
  */
172
- flush(): Promise<void>;
220
+ flush(options?: {
221
+ timeout?: number;
222
+ }): Promise<void>;
223
+ /**
224
+ * Wait until the channel has no delivery in flight, run its `flush()`, and
225
+ * repeat while that flush let new deliveries in.
226
+ */
227
+ private drainChannel;
173
228
  /**
174
229
  * Register one process-level handler per event that calls `flushSync()`
175
230
  * before the process terminates.
@@ -195,6 +250,11 @@ declare class Logger {
195
250
  */
196
251
  disableAutoFlush(): this;
197
252
  }
253
+ /**
254
+ * Default `timeout` of {@link Logger.flush}: long enough for a healthy network
255
+ * channel, short enough that a hung one cannot eat a shutdown budget.
256
+ */
257
+ declare const DEFAULT_FLUSH_TIMEOUT_MS = 5000;
198
258
  /**
199
259
  * The package singleton. Use this for everyday logging — `log.info(...)`,
200
260
  * `log.error(...)`, `log.configure(...)`. Custom logger instances can be
@@ -211,5 +271,5 @@ declare class Logger {
211
271
  */
212
272
  declare const log: Logger;
213
273
  //#endregion
214
- export { Logger, log };
274
+ export { DEFAULT_FLUSH_TIMEOUT_MS, Logger, log };
215
275
  //# sourceMappingURL=logger.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.mts","names":[],"sources":["../../../../../../logger/src/logger.ts"],"mappings":";;;;cAoCa,MAAA;;AAAb;;EAIS,QAAA,EAAU,UAAA;EAEV,EAAA;EA4BoB;;;;;EAAA,QArBnB,iBAAA;EA0EY;;;;;;EAAA,QAlEZ,QAAA;EAkLI;;;;;;EAAA,QA1KJ,YAAA;EAoMuB;;;EA/LxB,UAAA,CAAW,OAAA,EAAS,UAAA;EAsNX;;;EA7MT,SAAA,CAAU,MAAA;IACf,QAAA,GAAW,UAAA;IACX,WAAA,GAAc,cAAA;IACd,QAAA,GAAW,QAAA;IACX,MAAA,GAAS,YAAA;EAAA;EAsPU;;;;;;;;;;;EAtNd,SAAA,CAAU,MAAA,EAAQ,YAAA;EAzElB;;;EAiFA,SAAA,IAAa,YAAA;EArDb;;;;;;;;;;EAmEA,WAAA,CAAY,KAAA,EAAO,QAAA;EAtDf;;;EA8DJ,WAAA,IAAe,QAAA;EA9BL;;;EAqCV,WAAA,CAAY,QAAA,EAAU,UAAA;EAfH;;;EAAA,QAwBlB,gBAAA;EATD;;;;;;;;;EA4CM,GAAA,CAAI,IAAA,EAAM,WAAA,GAAW,OAAA;EAoChC;;;EADK,KAAA,CACL,YAAA,EAAc,kBAAA,WACd,MAAA,WACA,OAAA,QACA,OAAA,GAAU,MAAA,gBAAmB,OAAA;EAA7B;;;EASK,IAAA,CACL,YAAA,EAAc,kBAAA,WACd,MAAA,WACA,OAAA,QACA,OAAA,GAAU,MAAA,gBAAmB,OAAA;EAH7B;;;EAYK,IAAA,CACL,YAAA,EAAc,kBAAA,WACd,MAAA,WACA,OAAA,QACA,OAAA,GAAU,MAAA,gBAAmB,OAAA;EAb7B;;;EAsBK,KAAA,CACL,YAAA,EAAc,kBAAA,WACd,MAAA,WACA,OAAA,QACA,OAAA,GAAU,MAAA,gBAAmB,OAAA;EAhB7B;;;EAyBK,OAAA,CACL,YAAA,EAAc,kBAAA,WACd,MAAA,WACA,OAAA,QACA,OAAA,GAAU,MAAA,gBAAmB,OAAA;EA1B7B;;;;;;;;;EA0CK,KAAA,CACL,YAAA,EAAc,kBAAA,WACd,MAAA,WACA,OAAA,QACA,OAAA,GAAU,MAAA,gBAAmB,OAAA;EAxBxB;;;;;;;;;EAwCA,MAAA,CACL,SAAA,WACA,MAAA,UACA,MAAA,UACA,OAAA,OACA,OAAA,GAAU,MAAA,gBACT,OAAA,CAAQ,MAAA,IAAU,MAAA;EAzBnB;;;;;;;;;;EAwCK,KAAA,CACL,MAAA,UACA,MAAA,YACE,KAAA,GAAQ,MAAA,kBAAwB,OAAA,CAAQ,MAAA;EAnBhC;;;EAiCL,OAAA,CAAQ,IAAA,WAAY,UAAA,CAdgB,sBAAA;EAlBtB;;;EAuCd,SAAA;EArBK;;;;;;;;;;;;;;;;AA6GW;AAyBzB;;;EArFe,KAAA,IAAS,OAAA;EAqFO;;;;;;;;;;;;;;;;;;EAlDtB,eAAA,CAAgB,MAAA,EAAQ,cAAA;;;;;EAyBxB,gBAAA;AAAA;;;;;;;;;;;;;;;cAyBI,GAAA,EAAG,MAAe"}
1
+ {"version":3,"file":"logger.d.mts","names":[],"sources":["../../../../../../../packages/logger/src/logger.ts"],"mappings":";;;;cAsCa,MAAA;;AAAb;;EAIS,QAAA,EAAU,UAAA;EAEV,EAAA;EA0CoB;;;;;EAAA,QAnCnB,iBAAA;EAoFuB;;;;;;EAAA,QA5EvB,QAAA;EA2SQ;;;;;;EAAA,QAnSR,YAAA;EA6UI;;;;;;EAAA,QArUJ,iBAAA;EA2YqD;;;;EAAA,QArYrD,mBAAA;EAwhBqC;;;EAnhBtC,UAAA,CAAW,OAAA,EAAS,UAAA;EAnCnB;;;EA4CD,SAAA,CAAU,MAAA;IACf,QAAA,GAAW,UAAA;IACX,WAAA,GAAc,cAAA;IACd,QAAA,GAAW,QAAA;IACX,MAAA,GAAS,YAAA;IACT,cAAA,GAAiB,mBAAA;EAAA;EAJN;;;;;;;;;;EAuCN,cAAA,CAAe,OAAA,EAAS,mBAAA;EAAA;;;;;;;;;;;EAgBxB,SAAA,CAAU,MAAA,EAAQ,YAAA;EAqClB;;;EA7BA,SAAA,IAAa,YAAA;EAsDZ;;;;;;;;;;EAxCD,WAAA,CAAY,KAAA,EAAO,QAAA;EA4Ld;;;EApLL,WAAA,IAAe,QAAA;EA8LpB;;;EAvLK,WAAA,CAAY,QAAA,EAAU,UAAA;EA0L3B;;;;EAAA,QA5KM,aAAA;EAwLN;;;EAAA,QA7KM,gBAAA;EAwLQ;;;;;;;;;;;;;;EAhJT,GAAA,CAAI,IAAA,EAAM,WAAA;EAiLf;;;;;EAAA,QA/IM,OAAA;EAAA,QAqDA,kBAAA;EA+GN;;;EA9FK,KAAA,CACL,YAAA,EAAc,kBAAA,WACd,MAAA,WACA,OAAA,QACA,OAAA,GAAU,MAAA;EA6FV;;;EApFK,IAAA,CACL,YAAA,EAAc,kBAAA,WACd,MAAA,WACA,OAAA,QACA,OAAA,GAAU,MAAA;EAgG2C;;;EAvFhD,IAAA,CACL,YAAA,EAAc,kBAAA,WACd,MAAA,WACA,OAAA,QACA,OAAA,GAAU,MAAA;EAiGe;;;EAxFpB,KAAA,CACL,YAAA,EAAc,kBAAA,WACd,MAAA,WACA,OAAA,QACA,OAAA,GAAU,MAAA;EA8HkB;;;EArHvB,OAAA,CACL,YAAA,EAAc,kBAAA,WACd,MAAA,WACA,OAAA,QACA,OAAA,GAAU,MAAA;EA4ML;;;;;AAyBgB;AAezB;;;EApOS,KAAA,CACL,YAAA,EAAc,kBAAA,WACd,MAAA,WACA,OAAA,QACA,OAAA,GAAU,MAAA;EAgOuB;AA8CrC;;;;AAA+B;;;;EA9PtB,MAAA,CACL,SAAA,WACA,MAAA,UACA,MAAA,UACA,OAAA,OACA,OAAA,GAAU,MAAA;;;;;;;;;;;EAgBL,KAAA,CAAM,MAAA,UAAgB,MAAA,YAAkB,KAAA,GAAQ,MAAA;;;;EAchD,OAAA,CAAQ,IAAA,WAAY,UAAA,CAdkC,sBAAA;;;;EAqBtD,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmCM,KAAA,CAAM,OAAA;IAAW,OAAA;EAAA,IAA0B,OAAA;;;;;UA0D1C,YAAA;;;;;;;;;;;;;;;;;;;EAiCP,eAAA,CAAgB,MAAA,EAAQ,cAAA;;;;;EAyBxB,gBAAA;AAAA;;;;;cAeI,wBAAA;;;;;;;;;;;;;;;cA8CA,GAAA,EAAG,MAAe"}
package/esm/logger.mjs CHANGED
@@ -3,7 +3,7 @@ import "./redact/index.mjs";
3
3
  import { clearMessage } from "./utils/clear-message.mjs";
4
4
  import { Random } from "@mongez/reinforcements";
5
5
 
6
- //#region ../logger/src/logger.ts
6
+ //#region ../../packages/logger/src/logger.ts
7
7
  const SIGNAL_EVENTS = new Set([
8
8
  "SIGINT",
9
9
  "SIGTERM",
@@ -31,22 +31,39 @@ var Logger = class {
31
31
  this.channels = [];
32
32
  this.id = "logger-" + Random.string(32);
33
33
  this.autoFlushHandlers = /* @__PURE__ */ new Map();
34
+ this.pendingDeliveries = /* @__PURE__ */ new Map();
35
+ this.channelErrorHandler = reportChannelError;
34
36
  }
35
37
  /**
36
38
  * Add a new channel
37
39
  */
38
40
  addChannel(channel) {
39
- this.channels.push(channel);
41
+ this.channels.push(this.attachChannel(channel));
40
42
  return this;
41
43
  }
42
44
  /**
43
45
  * Set base configurations
44
46
  */
45
47
  configure(config) {
46
- if (config.channels) this.channels = config.channels;
48
+ if (config.channels) this.setChannels(config.channels);
47
49
  if (config.autoFlushOn) this.enableAutoFlush(config.autoFlushOn);
48
50
  if (config.minLevel !== void 0) this.setMinLevel(config.minLevel);
49
51
  if (config.redact !== void 0) this.setRedact(config.redact);
52
+ if (config.onChannelError) this.onChannelError(config.onChannelError);
53
+ return this;
54
+ }
55
+ /**
56
+ * Handle a channel's delivery failure. Logging is fire-and-forget, so a
57
+ * channel that throws or rejects never reaches the call site and never
58
+ * becomes an unhandled rejection; it goes to this handler instead. The
59
+ * default writes one line to stderr. The handler must not log through this
60
+ * logger (a failing channel would loop).
61
+ *
62
+ * @example
63
+ * log.onChannelError((error, channel) => metrics.increment(`log.${channel.name}.failed`));
64
+ */
65
+ onChannelError(handler) {
66
+ this.channelErrorHandler = handler;
50
67
  return this;
51
68
  }
52
69
  /**
@@ -95,9 +112,18 @@ var Logger = class {
95
112
  */
96
113
  setChannels(channels) {
97
114
  this.channels = channels;
115
+ for (const channel of channels) this.attachChannel(channel);
98
116
  return this;
99
117
  }
100
118
  /**
119
+ * Route a channel's own failures (outside `log()`, such as file retention)
120
+ * to this logger's channel error handler.
121
+ */
122
+ attachChannel(channel) {
123
+ if (typeof channel.setErrorReporter === "function") channel.setErrorReporter((error, entry) => this.reportChannelError(error, channel, entry));
124
+ return channel;
125
+ }
126
+ /**
101
127
  * Normalize log data to a single object
102
128
  */
103
129
  normalizeLogData(dataOrModule, action, message = "", level, context) {
@@ -124,21 +150,62 @@ var Logger = class {
124
150
  * — each channel sees its own shallow clone so one channel cannot observe
125
151
  * another's mutations (e.g. a later terminal channel still sees the original
126
152
  * colored message).
153
+ *
154
+ * Synchronous and fire-and-forget: it returns the logger, not a promise.
155
+ * Async channel deliveries are tracked and awaited by {@link flush}; a
156
+ * channel that throws or rejects is isolated from the others and reported
157
+ * to {@link onChannelError}.
127
158
  */
128
- async log(data) {
159
+ log(data) {
129
160
  if (this.minLevel && LEVEL_RANK[data.type] < LEVEL_RANK[this.minLevel]) return this;
130
- const baseEntry = applyRedact(data, this.redactConfig);
131
- for (const channel of this.channels) {
161
+ let baseEntry;
162
+ try {
163
+ baseEntry = applyRedact(data, this.redactConfig);
164
+ } catch (error) {
165
+ for (const channel of this.channels) this.reportChannelError(error, channel, redactionFailedEntry(data));
166
+ return this;
167
+ }
168
+ for (const channel of this.channels) this.deliver(channel, data, baseEntry);
169
+ return this;
170
+ }
171
+ /**
172
+ * Prepare and hand one entry to one channel, isolating its failure (a
173
+ * channel redaction that throws included) and tracking an async delivery
174
+ * until it settles.
175
+ */
176
+ deliver(channel, data, baseEntry) {
177
+ let payload = baseEntry;
178
+ let result;
179
+ try {
132
180
  const channelRedact = channel.getRedactConfig?.();
133
- const effectiveRedact = channelRedact ? mergeRedact(this.redactConfig, channelRedact) : void 0;
134
- let payload = effectiveRedact ? applyRedact(data, effectiveRedact) : baseEntry;
181
+ if (channelRedact) payload = applyRedact(data, mergeRedact(this.redactConfig, channelRedact));
135
182
  if (channel.terminal === false) payload = {
136
183
  ...payload,
137
184
  message: clearMessage(payload.message)
138
185
  };
139
- channel.log(payload);
186
+ result = channel.log(payload);
187
+ } catch (error) {
188
+ this.reportChannelError(error, channel, payload);
189
+ return;
140
190
  }
141
- return this;
191
+ if (!result || typeof result.then !== "function") return;
192
+ let pending = this.pendingDeliveries.get(channel);
193
+ if (!pending) {
194
+ pending = /* @__PURE__ */ new Set();
195
+ this.pendingDeliveries.set(channel, pending);
196
+ }
197
+ const tracked = pending;
198
+ const delivery = Promise.resolve(result).catch((error) => this.reportChannelError(error, channel, payload)).finally(() => {
199
+ tracked.delete(delivery);
200
+ if (tracked.size === 0 && this.pendingDeliveries.get(channel) === tracked) this.pendingDeliveries.delete(channel);
201
+ });
202
+ tracked.add(delivery);
203
+ }
204
+ reportChannelError(error, channel, entry) {
205
+ try {
206
+ const outcome = this.channelErrorHandler(error, channel, entry);
207
+ if (outcome && typeof outcome.then === "function") outcome.then(void 0, () => {});
208
+ } catch {}
142
209
  }
143
210
  /**
144
211
  * Make debug log
@@ -234,7 +301,9 @@ var Logger = class {
234
301
  for (const channel of this.channels) if (channel.flushSync) channel.flushSync();
235
302
  }
236
303
  /**
237
- * Asynchronously drain every channel that implements `flush()`.
304
+ * Wait for every in-flight async delivery, then drain every channel that
305
+ * implements `flush()` — bounded by `timeout` (default
306
+ * {@link DEFAULT_FLUSH_TIMEOUT_MS}).
238
307
  *
239
308
  * Unlike {@link flushSync}, this awaits each channel's async I/O — the
240
309
  * correct call for a graceful shutdown that can afford to wait
@@ -242,24 +311,68 @@ var Logger = class {
242
311
  * `process.exit`). A channel whose delivery is async (a network transport,
243
312
  * an async disk write) implements `flush()`, not `flushSync()`.
244
313
  *
245
- * Channels are isolated: a channel whose flush rejects can neither prevent
246
- * the others from draining nor escape as an unhandled rejection. Channels
247
- * without `flush()` are skipped.
314
+ * Channels drain concurrently and independently: each waits for its own
315
+ * pending deliveries (including ones logged while the flush runs), then
316
+ * runs its `flush()`. A channel whose delivery hangs therefore cannot hold
317
+ * up another channel's drain, and a channel whose flush rejects can neither
318
+ * stop the others nor escape as an unhandled rejection. When `timeout`
319
+ * elapses, `flush()` resolves anyway and each channel still draining is
320
+ * reported to {@link onChannelError}. Channels without `flush()` only wait
321
+ * for their deliveries.
248
322
  *
249
323
  * @example
250
324
  * async function shutdown() {
251
325
  * await httpServer.close();
252
- * await log.flush();
326
+ * await log.flush({ timeout: 3000 });
253
327
  * process.exit(0);
254
328
  * }
255
329
  */
256
- async flush() {
257
- await Promise.allSettled(this.channels.map(async (channel) => {
258
- if (!channel.flush) return;
330
+ async flush(options = {}) {
331
+ const timeout = options.timeout ?? 5e3;
332
+ const draining = new Set(this.channels);
333
+ for (const channel of this.pendingDeliveries.keys()) draining.add(channel);
334
+ const unfinished = new Set(draining);
335
+ const drains = [...draining].map(async (channel) => {
259
336
  try {
260
- await channel.flush();
261
- } catch {}
262
- }));
337
+ await this.drainChannel(channel);
338
+ } catch {} finally {
339
+ unfinished.delete(channel);
340
+ }
341
+ });
342
+ const settled = Promise.allSettled(drains).then(() => false);
343
+ if (!Number.isFinite(timeout)) {
344
+ await settled;
345
+ return;
346
+ }
347
+ let resolveTimeout;
348
+ const timeoutReached = new Promise((resolve) => {
349
+ resolveTimeout = resolve;
350
+ });
351
+ const timer = setTimeout(() => resolveTimeout(true), Math.max(0, timeout));
352
+ timer.unref?.();
353
+ const timedOut = await Promise.race([settled, timeoutReached]);
354
+ clearTimeout(timer);
355
+ if (!timedOut) return;
356
+ for (const channel of unfinished) this.reportChannelError(/* @__PURE__ */ new Error(`flush timed out after ${timeout}ms; entries still in flight may be lost`), channel, {
357
+ type: "warn",
358
+ module: "logger",
359
+ action: "flush",
360
+ message: "flush timed out"
361
+ });
362
+ }
363
+ /**
364
+ * Wait until the channel has no delivery in flight, run its `flush()`, and
365
+ * repeat while that flush let new deliveries in.
366
+ */
367
+ async drainChannel(channel) {
368
+ do {
369
+ let pending = this.pendingDeliveries.get(channel);
370
+ while (pending && pending.size > 0) {
371
+ await Promise.allSettled([...pending]);
372
+ pending = this.pendingDeliveries.get(channel);
373
+ }
374
+ if (channel.flush) await channel.flush();
375
+ } while ((this.pendingDeliveries.get(channel)?.size ?? 0) > 0);
263
376
  }
264
377
  /**
265
378
  * Register one process-level handler per event that calls `flushSync()`
@@ -305,6 +418,34 @@ var Logger = class {
305
418
  }
306
419
  };
307
420
  /**
421
+ * Default `timeout` of {@link Logger.flush}: long enough for a healthy network
422
+ * channel, short enough that a hung one cannot eat a shutdown budget.
423
+ */
424
+ const DEFAULT_FLUSH_TIMEOUT_MS = 5e3;
425
+ /**
426
+ * What the error handler receives when the logger-wide redaction itself
427
+ * failed: the entry's identity only, never the unredacted message or context.
428
+ */
429
+ function redactionFailedEntry(data) {
430
+ return {
431
+ type: data.type,
432
+ module: data.module,
433
+ action: data.action,
434
+ message: "[entry dropped: redaction failed]"
435
+ };
436
+ }
437
+ /**
438
+ * Default {@link ChannelErrorHandler}: one stderr line per failure. Written
439
+ * directly to the stream, never through a logger, so a broken channel cannot
440
+ * recurse.
441
+ */
442
+ function reportChannelError(error, channel) {
443
+ const reason = error instanceof Error ? error.message : String(error);
444
+ try {
445
+ process.stderr.write(`[@warlock.js/logger] channel "${channel.name}" failed to log: ${reason}\n`);
446
+ } catch {}
447
+ }
448
+ /**
308
449
  * The package singleton. Use this for everyday logging — `log.info(...)`,
309
450
  * `log.error(...)`, `log.configure(...)`. Custom logger instances can be
310
451
  * created by instantiating `Logger` directly.
@@ -321,5 +462,5 @@ var Logger = class {
321
462
  const log = new Logger();
322
463
 
323
464
  //#endregion
324
- export { Logger, log };
465
+ export { DEFAULT_FLUSH_TIMEOUT_MS, Logger, log };
325
466
  //# sourceMappingURL=logger.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.mjs","names":[],"sources":["../../../../../../logger/src/logger.ts"],"sourcesContent":["import { Random } from \"@mongez/reinforcements\";\nimport type { LogChannel } from \"./log-channel\";\nimport { applyRedact, mergeRedact } from \"./redact\";\nimport type {\n AutoFlushEvent,\n LoggingData,\n LogLevel,\n OmittedLoggingData,\n RedactConfig,\n} from \"./types\";\nimport { clearMessage } from \"./utils/clear-message\";\n\nconst SIGNAL_EVENTS: ReadonlySet<AutoFlushEvent> = new Set([\n \"SIGINT\",\n \"SIGTERM\",\n \"SIGHUP\",\n \"SIGBREAK\",\n \"SIGUSR2\",\n]);\n\n/**\n * Severity ranks used by `setMinLevel`. Higher number = more severe. The\n * ordering matches conventional log-level hierarchies: `debug` is noisiest\n * and easiest to drop; `error` is the loudest and never dropped by the\n * minimum-level filter. `success` sits beside `info` — it's an informational\n * outcome, not a warning.\n */\nconst LEVEL_RANK: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n success: 1,\n warn: 2,\n error: 3,\n fatal: 4,\n};\n\nexport class Logger {\n /**\n * Current channel\n */\n public channels: LogChannel[] = [];\n\n public id = \"logger-\" + Random.string(32);\n\n /**\n * Registered auto-flush handlers, keyed by event name. Stored so repeated\n * calls to `enableAutoFlush` replace rather than stack, and so\n * `disableAutoFlush` can remove them cleanly.\n */\n private autoFlushHandlers = new Map<AutoFlushEvent, () => void>();\n\n /**\n * Logger-wide minimum severity. When set, entries below this level are\n * dropped before any channel is invoked — cheaper than per-channel `levels`\n * filters because the fan-out loop is skipped entirely. `undefined` means\n * no minimum (every entry reaches every channel that accepts it).\n */\n private minLevel?: LogLevel;\n\n /**\n * Logger-wide redaction floor. Applied once before fan-out — every\n * channel receives an entry with these paths already censored. Channel\n * configs can extend the path list (additive); they cannot remove paths\n * set here.\n */\n private redactConfig?: RedactConfig;\n\n /**\n * Add a new channel\n */\n public addChannel(channel: LogChannel) {\n this.channels.push(channel);\n\n return this;\n }\n\n /**\n * Set base configurations\n */\n public configure(config: {\n channels?: LogChannel[];\n autoFlushOn?: AutoFlushEvent[];\n minLevel?: LogLevel;\n redact?: RedactConfig;\n }) {\n if (config.channels) {\n this.channels = config.channels;\n }\n\n if (config.autoFlushOn) {\n this.enableAutoFlush(config.autoFlushOn);\n }\n\n if (config.minLevel !== undefined) {\n this.setMinLevel(config.minLevel);\n }\n\n if (config.redact !== undefined) {\n this.setRedact(config.redact);\n }\n\n return this;\n }\n\n /**\n * Set the logger-wide redaction floor. Applied to every entry before\n * fan-out; channel configs add more paths on top, never fewer. Pass\n * `undefined` to clear.\n *\n * @example\n * log.setRedact({\n * paths: [\"context.password\", \"context.*.token\"],\n * censor: \"[REDACTED]\",\n * });\n */\n public setRedact(config: RedactConfig | undefined): this {\n this.redactConfig = config;\n return this;\n }\n\n /**\n * Read the active logger-wide redact config (or `undefined`).\n */\n public getRedact(): RedactConfig | undefined {\n return this.redactConfig;\n }\n\n /**\n * Drop every entry whose severity is below `level` before fan-out. Cheaper\n * than per-channel `levels` filters because the loop never runs and no\n * channel receives the entry. Pass `undefined` to clear and accept all\n * levels again.\n *\n * @example\n * // production: silence debug noise everywhere at once\n * logger.setMinLevel(\"info\");\n */\n public setMinLevel(level: LogLevel | undefined): this {\n this.minLevel = level;\n return this;\n }\n\n /**\n * Read the active minimum severity (or `undefined` when none is set).\n */\n public getMinLevel(): LogLevel | undefined {\n return this.minLevel;\n }\n\n /**\n * Set channels\n */\n public setChannels(channels: LogChannel[]) {\n this.channels = channels;\n\n return this;\n }\n\n /**\n * Normalize log data to a single object\n */\n private normalizeLogData(\n dataOrModule: LoggingData | OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n level?: LogLevel,\n context?: Record<string, any>,\n ): LoggingData {\n if (typeof dataOrModule === \"object\") {\n // If level is provided, override type\n return {\n type: (level || (dataOrModule as any).type || \"info\") as LogLevel,\n module: dataOrModule.module,\n action: dataOrModule.action,\n message: dataOrModule.message,\n ...(context ? { context } : dataOrModule.context ? { context: dataOrModule.context } : {}),\n };\n }\n return {\n type: (level || \"info\") as LogLevel,\n module: dataOrModule,\n action: action as string,\n message,\n ...(context ? { context } : {}),\n };\n }\n\n /**\n * Make log\n *\n * Fans out a single log entry to every registered channel. Non-terminal\n * channels receive a copy whose `message` has had ANSI color codes stripped\n * — each channel sees its own shallow clone so one channel cannot observe\n * another's mutations (e.g. a later terminal channel still sees the original\n * colored message).\n */\n public async log(data: LoggingData) {\n if (this.minLevel && LEVEL_RANK[data.type] < LEVEL_RANK[this.minLevel]) {\n return this;\n }\n\n // Apply the logger-wide redact floor once. Every channel sees the\n // result; no channel can undo a logger-wide redaction (additive-only\n // semantics).\n const baseEntry = applyRedact(data, this.redactConfig);\n\n for (const channel of this.channels) {\n const channelRedact = channel.getRedactConfig?.();\n const effectiveRedact = channelRedact\n ? mergeRedact(this.redactConfig, channelRedact)\n : undefined;\n\n // When the channel adds paths, redact again from `data` rather than\n // from `baseEntry` so the merged config (which already contains the\n // logger-wide paths) does the full pass — avoids double-cloning the\n // already-redacted base.\n let payload = effectiveRedact ? applyRedact(data, effectiveRedact) : baseEntry;\n\n if (channel.terminal === false) {\n payload = { ...payload, message: clearMessage(payload.message) };\n }\n\n channel.log(payload);\n }\n\n return this;\n }\n\n /**\n * Make debug log\n */\n public debug(\n dataOrModule: OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n context?: Record<string, any>,\n ) {\n const data = this.normalizeLogData(dataOrModule, action, message, \"debug\", context);\n return this.log(data);\n }\n\n /**\n * Make info log\n */\n public info(\n dataOrModule: OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n context?: Record<string, any>,\n ) {\n const data = this.normalizeLogData(dataOrModule, action, message, \"info\", context);\n return this.log(data);\n }\n\n /**\n * Make warn log\n */\n public warn(\n dataOrModule: OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n context?: Record<string, any>,\n ) {\n const data = this.normalizeLogData(dataOrModule, action, message, \"warn\", context);\n return this.log(data);\n }\n\n /**\n * Make error log\n */\n public error(\n dataOrModule: OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n context?: Record<string, any>,\n ) {\n const data = this.normalizeLogData(dataOrModule, action, message, \"error\", context);\n return this.log(data);\n }\n\n /**\n * Make success log\n */\n public success(\n dataOrModule: OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n context?: Record<string, any>,\n ) {\n const data = this.normalizeLogData(dataOrModule, action, message, \"success\", context);\n\n return this.log(data);\n }\n\n /**\n * Make fatal log — for unrecoverable failures where the application is going\n * down (failed bootstrap, lost connection to a required dependency that the\n * caller has decided not to retry, an `uncaughtException`).\n *\n * Identical shape to {@link error}; the level is purely informational —\n * `fatal` does NOT auto-flush or exit. The caller decides whether to call\n * `await log.flush()` and `process.exit(...)`.\n */\n public fatal(\n dataOrModule: OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n context?: Record<string, any>,\n ) {\n const data = this.normalizeLogData(dataOrModule, action, message, \"fatal\", context);\n\n return this.log(data);\n }\n\n /**\n * Log an `error` entry when `condition` is falsy. No-op otherwise — the\n * entry is never built and channels are not invoked, so this is genuinely\n * free in the happy path. Mirrors the spirit of `console.assert` but routes\n * through the logger pipeline so persistent channels capture failures.\n *\n * @example\n * log.assert(user !== null, \"auth\", \"session\", \"user vanished mid-flight\", { sessionId });\n */\n public assert(\n condition: unknown,\n module: string,\n action: string,\n message: any,\n context?: Record<string, any>,\n ): Promise<Logger> | Logger {\n if (condition) return this;\n return this.error(module, action, message, context);\n }\n\n /**\n * Start a duration timer. The returned function emits an `info` entry\n * with `completed in <ms>ms` and a `durationMs` field in `context` when\n * called. Pass an object to `end()` to merge extra fields into context.\n *\n * @example\n * const end = log.timer(\"db\", \"users.findById\");\n * const user = await usersRepo.findById(id);\n * end({ id, found: !!user });\n */\n public timer(\n module: string,\n action: string,\n ): (extra?: Record<string, any>) => Promise<Logger> {\n const startedAt = Date.now();\n return (extra?: Record<string, any>) => {\n const durationMs = Date.now() - startedAt;\n return this.info(module, action, `completed in ${durationMs}ms`, {\n durationMs,\n ...(extra ?? {}),\n });\n };\n }\n\n /**\n * Get channel by name\n */\n public channel(name: string) {\n return this.channels.find((channel) => channel.name === name);\n }\n\n /**\n * Synchronously flush logs\n */\n public flushSync() {\n for (const channel of this.channels) {\n if (channel.flushSync) {\n channel.flushSync();\n }\n }\n }\n\n /**\n * Asynchronously drain every channel that implements `flush()`.\n *\n * Unlike {@link flushSync}, this awaits each channel's async I/O — the\n * correct call for a graceful shutdown that can afford to wait\n * (`await log.flush()` after closing the HTTP server, before\n * `process.exit`). A channel whose delivery is async (a network transport,\n * an async disk write) implements `flush()`, not `flushSync()`.\n *\n * Channels are isolated: a channel whose flush rejects can neither prevent\n * the others from draining nor escape as an unhandled rejection. Channels\n * without `flush()` are skipped.\n *\n * @example\n * async function shutdown() {\n * await httpServer.close();\n * await log.flush();\n * process.exit(0);\n * }\n */\n public async flush(): Promise<void> {\n await Promise.allSettled(\n this.channels.map(async (channel) => {\n if (!channel.flush) {\n return;\n }\n\n try {\n await channel.flush();\n } catch {\n // A single channel must never break shutdown for the others —\n // a graceful drain is best-effort across every channel.\n }\n }),\n );\n }\n\n /**\n * Register one process-level handler per event that calls `flushSync()`\n * before the process terminates.\n *\n * For signal events (`SIGINT`, `SIGTERM`, `SIGHUP`, `SIGBREAK`, `SIGUSR2`)\n * the handler flushes and then re-raises the signal so Node's default exit\n * behavior runs. For `beforeExit`, the handler flushes in place — Node exits\n * naturally afterwards.\n *\n * Idempotent: calling with the same events replaces the previous handlers.\n * Call `disableAutoFlush()` to unregister.\n *\n * @example\n * log.configure({\n * channels: [new ConsoleLog(), new FileLog()],\n * autoFlushOn: [\"SIGINT\", \"SIGTERM\", \"beforeExit\"],\n * });\n */\n public enableAutoFlush(events: AutoFlushEvent[]): this {\n this.disableAutoFlush();\n\n for (const event of events) {\n const handler = SIGNAL_EVENTS.has(event)\n ? () => {\n this.flushSync();\n process.off(event, handler);\n process.kill(process.pid, event as NodeJS.Signals);\n }\n : () => {\n this.flushSync();\n };\n\n process.on(event, handler);\n this.autoFlushHandlers.set(event, handler);\n }\n\n return this;\n }\n\n /**\n * Remove every handler previously registered by `enableAutoFlush`.\n * Safe to call when no handlers are registered.\n */\n public disableAutoFlush(): this {\n for (const [event, handler] of this.autoFlushHandlers) {\n process.off(event, handler);\n }\n\n this.autoFlushHandlers.clear();\n\n return this;\n }\n}\n\n/**\n * The package singleton. Use this for everyday logging — `log.info(...)`,\n * `log.error(...)`, `log.configure(...)`. Custom logger instances can be\n * created by instantiating `Logger` directly.\n *\n * The name is intentionally short: `log` reads naturally at the call site\n * (`log.info(\"auth\", \"login\", \"ok\")`) and matches the convention used in\n * pino, bunyan, and most JS logging tutorials.\n *\n * Note that `log` is a `Logger` instance, **not** a function — the bare\n * callable form was removed when the dual `log` / `logger` exports were\n * collapsed into a single name. Use `log.info(...)` (or any other level\n * shortcut) to emit entries.\n */\nexport const log = new Logger();\n"],"mappings":"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"}
1
+ {"version":3,"file":"logger.mjs","names":[],"sources":["../../../../../../../packages/logger/src/logger.ts"],"sourcesContent":["import { Random } from \"@mongez/reinforcements\";\nimport type { LogChannel } from \"./log-channel\";\nimport { applyRedact, mergeRedact } from \"./redact\";\nimport type {\n AutoFlushEvent,\n ChannelErrorHandler,\n LogContract,\n LoggingData,\n LogLevel,\n OmittedLoggingData,\n RedactConfig,\n} from \"./types\";\nimport { clearMessage } from \"./utils/clear-message\";\n\nconst SIGNAL_EVENTS: ReadonlySet<AutoFlushEvent> = new Set([\n \"SIGINT\",\n \"SIGTERM\",\n \"SIGHUP\",\n \"SIGBREAK\",\n \"SIGUSR2\",\n]);\n\n/**\n * Severity ranks used by `setMinLevel`. Higher number = more severe. The\n * ordering matches conventional log-level hierarchies: `debug` is noisiest\n * and easiest to drop; `error` is the loudest and never dropped by the\n * minimum-level filter. `success` sits beside `info` — it's an informational\n * outcome, not a warning.\n */\nconst LEVEL_RANK: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n success: 1,\n warn: 2,\n error: 3,\n fatal: 4,\n};\n\nexport class Logger {\n /**\n * Current channel\n */\n public channels: LogChannel[] = [];\n\n public id = \"logger-\" + Random.string(32);\n\n /**\n * Registered auto-flush handlers, keyed by event name. Stored so repeated\n * calls to `enableAutoFlush` replace rather than stack, and so\n * `disableAutoFlush` can remove them cleanly.\n */\n private autoFlushHandlers = new Map<AutoFlushEvent, () => void>();\n\n /**\n * Logger-wide minimum severity. When set, entries below this level are\n * dropped before any channel is invoked — cheaper than per-channel `levels`\n * filters because the fan-out loop is skipped entirely. `undefined` means\n * no minimum (every entry reaches every channel that accepts it).\n */\n private minLevel?: LogLevel;\n\n /**\n * Logger-wide redaction floor. Applied once before fan-out — every\n * channel receives an entry with these paths already censored. Channel\n * configs can extend the path list (additive); they cannot remove paths\n * set here.\n */\n private redactConfig?: RedactConfig;\n\n /**\n * Deliveries still in flight on async channels, per channel. `log()` never\n * awaits them; `flush()` does, so a graceful shutdown still waits for every\n * entry. Kept per channel so one hung channel cannot hold up another's\n * drain.\n */\n private pendingDeliveries = new Map<LogChannel, Set<Promise<void>>>();\n\n /**\n * Receives a channel's delivery failure (a sync throw or a rejected async\n * `log()`). Defaults to a single stderr line, never back through the logger.\n */\n private channelErrorHandler: ChannelErrorHandler = reportChannelError;\n\n /**\n * Add a new channel\n */\n public addChannel(channel: LogChannel) {\n this.channels.push(this.attachChannel(channel));\n\n return this;\n }\n\n /**\n * Set base configurations\n */\n public configure(config: {\n channels?: LogChannel[];\n autoFlushOn?: AutoFlushEvent[];\n minLevel?: LogLevel;\n redact?: RedactConfig;\n onChannelError?: ChannelErrorHandler;\n }) {\n if (config.channels) {\n this.setChannels(config.channels);\n }\n\n if (config.autoFlushOn) {\n this.enableAutoFlush(config.autoFlushOn);\n }\n\n if (config.minLevel !== undefined) {\n this.setMinLevel(config.minLevel);\n }\n\n if (config.redact !== undefined) {\n this.setRedact(config.redact);\n }\n\n if (config.onChannelError) {\n this.onChannelError(config.onChannelError);\n }\n\n return this;\n }\n\n /**\n * Handle a channel's delivery failure. Logging is fire-and-forget, so a\n * channel that throws or rejects never reaches the call site and never\n * becomes an unhandled rejection; it goes to this handler instead. The\n * default writes one line to stderr. The handler must not log through this\n * logger (a failing channel would loop).\n *\n * @example\n * log.onChannelError((error, channel) => metrics.increment(`log.${channel.name}.failed`));\n */\n public onChannelError(handler: ChannelErrorHandler): this {\n this.channelErrorHandler = handler;\n return this;\n }\n\n /**\n * Set the logger-wide redaction floor. Applied to every entry before\n * fan-out; channel configs add more paths on top, never fewer. Pass\n * `undefined` to clear.\n *\n * @example\n * log.setRedact({\n * paths: [\"context.password\", \"context.*.token\"],\n * censor: \"[REDACTED]\",\n * });\n */\n public setRedact(config: RedactConfig | undefined): this {\n this.redactConfig = config;\n return this;\n }\n\n /**\n * Read the active logger-wide redact config (or `undefined`).\n */\n public getRedact(): RedactConfig | undefined {\n return this.redactConfig;\n }\n\n /**\n * Drop every entry whose severity is below `level` before fan-out. Cheaper\n * than per-channel `levels` filters because the loop never runs and no\n * channel receives the entry. Pass `undefined` to clear and accept all\n * levels again.\n *\n * @example\n * // production: silence debug noise everywhere at once\n * logger.setMinLevel(\"info\");\n */\n public setMinLevel(level: LogLevel | undefined): this {\n this.minLevel = level;\n return this;\n }\n\n /**\n * Read the active minimum severity (or `undefined` when none is set).\n */\n public getMinLevel(): LogLevel | undefined {\n return this.minLevel;\n }\n\n /**\n * Set channels\n */\n public setChannels(channels: LogChannel[]) {\n this.channels = channels;\n\n for (const channel of channels) {\n this.attachChannel(channel);\n }\n\n return this;\n }\n\n /**\n * Route a channel's own failures (outside `log()`, such as file retention)\n * to this logger's channel error handler.\n */\n private attachChannel(channel: LogChannel): LogChannel {\n if (typeof channel.setErrorReporter === \"function\") {\n channel.setErrorReporter((error, entry) => this.reportChannelError(error, channel, entry));\n }\n\n return channel;\n }\n\n /**\n * Normalize log data to a single object\n */\n private normalizeLogData(\n dataOrModule: LoggingData | OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n level?: LogLevel,\n context?: Record<string, any>,\n ): LoggingData {\n if (typeof dataOrModule === \"object\") {\n // If level is provided, override type\n return {\n type: (level || (dataOrModule as any).type || \"info\") as LogLevel,\n module: dataOrModule.module,\n action: dataOrModule.action,\n message: dataOrModule.message,\n ...(context ? { context } : dataOrModule.context ? { context: dataOrModule.context } : {}),\n };\n }\n return {\n type: (level || \"info\") as LogLevel,\n module: dataOrModule,\n action: action as string,\n message,\n ...(context ? { context } : {}),\n };\n }\n\n /**\n * Make log\n *\n * Fans out a single log entry to every registered channel. Non-terminal\n * channels receive a copy whose `message` has had ANSI color codes stripped\n * — each channel sees its own shallow clone so one channel cannot observe\n * another's mutations (e.g. a later terminal channel still sees the original\n * colored message).\n *\n * Synchronous and fire-and-forget: it returns the logger, not a promise.\n * Async channel deliveries are tracked and awaited by {@link flush}; a\n * channel that throws or rejects is isolated from the others and reported\n * to {@link onChannelError}.\n */\n public log(data: LoggingData): this {\n if (this.minLevel && LEVEL_RANK[data.type] < LEVEL_RANK[this.minLevel]) {\n return this;\n }\n\n // Apply the logger-wide redact floor once. Every channel sees the\n // result; no channel can undo a logger-wide redaction (additive-only\n // semantics). A redaction that throws (a bad censor function) drops the\n // entry: delivering it unredacted would leak exactly what the config\n // exists to hide.\n let baseEntry: LoggingData;\n\n try {\n baseEntry = applyRedact(data, this.redactConfig);\n } catch (error) {\n for (const channel of this.channels) {\n this.reportChannelError(error, channel, redactionFailedEntry(data));\n }\n\n return this;\n }\n\n for (const channel of this.channels) {\n this.deliver(channel, data, baseEntry);\n }\n\n return this;\n }\n\n /**\n * Prepare and hand one entry to one channel, isolating its failure (a\n * channel redaction that throws included) and tracking an async delivery\n * until it settles.\n */\n private deliver(channel: LogChannel, data: LoggingData, baseEntry: LoggingData) {\n let payload: LoggingData = baseEntry;\n let result: void | Promise<void>;\n\n try {\n const channelRedact = channel.getRedactConfig?.();\n\n // When the channel adds paths, redact again from `data` rather than\n // from `baseEntry` so the merged config (which already contains the\n // logger-wide paths) does the full pass — avoids double-cloning the\n // already-redacted base.\n if (channelRedact) {\n payload = applyRedact(data, mergeRedact(this.redactConfig, channelRedact));\n }\n\n if (channel.terminal === false) {\n payload = { ...payload, message: clearMessage(payload.message) };\n }\n\n result = channel.log(payload);\n } catch (error) {\n // `payload` is the logger-wide redacted entry until the channel's own\n // redaction succeeded, so the handler never sees less-redacted data\n // than some channel would have.\n this.reportChannelError(error, channel, payload);\n return;\n }\n\n if (!result || typeof (result as Promise<void>).then !== \"function\") {\n return;\n }\n\n let pending = this.pendingDeliveries.get(channel);\n\n if (!pending) {\n pending = new Set();\n this.pendingDeliveries.set(channel, pending);\n }\n\n const tracked = pending;\n const delivery: Promise<void> = Promise.resolve(result)\n .catch((error) => this.reportChannelError(error, channel, payload))\n .finally(() => {\n tracked.delete(delivery);\n\n if (tracked.size === 0 && this.pendingDeliveries.get(channel) === tracked) {\n this.pendingDeliveries.delete(channel);\n }\n });\n\n tracked.add(delivery);\n }\n\n private reportChannelError(error: unknown, channel: LogContract, entry: LoggingData) {\n try {\n const outcome: unknown = this.channelErrorHandler(error, channel, entry);\n\n // An async handler that rejects must not become an unhandled rejection\n // (which an app may route straight back into this logger).\n if (outcome && typeof (outcome as Promise<unknown>).then === \"function\") {\n (outcome as Promise<unknown>).then(undefined, () => {});\n }\n } catch {\n // An error handler that throws must not turn logging into a crash.\n }\n }\n\n /**\n * Make debug log\n */\n public debug(\n dataOrModule: OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n context?: Record<string, any>,\n ) {\n const data = this.normalizeLogData(dataOrModule, action, message, \"debug\", context);\n return this.log(data);\n }\n\n /**\n * Make info log\n */\n public info(\n dataOrModule: OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n context?: Record<string, any>,\n ) {\n const data = this.normalizeLogData(dataOrModule, action, message, \"info\", context);\n return this.log(data);\n }\n\n /**\n * Make warn log\n */\n public warn(\n dataOrModule: OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n context?: Record<string, any>,\n ) {\n const data = this.normalizeLogData(dataOrModule, action, message, \"warn\", context);\n return this.log(data);\n }\n\n /**\n * Make error log\n */\n public error(\n dataOrModule: OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n context?: Record<string, any>,\n ) {\n const data = this.normalizeLogData(dataOrModule, action, message, \"error\", context);\n return this.log(data);\n }\n\n /**\n * Make success log\n */\n public success(\n dataOrModule: OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n context?: Record<string, any>,\n ) {\n const data = this.normalizeLogData(dataOrModule, action, message, \"success\", context);\n\n return this.log(data);\n }\n\n /**\n * Make fatal log — for unrecoverable failures where the application is going\n * down (failed bootstrap, lost connection to a required dependency that the\n * caller has decided not to retry, an `uncaughtException`).\n *\n * Identical shape to {@link error}; the level is purely informational —\n * `fatal` does NOT auto-flush or exit. The caller decides whether to call\n * `await log.flush()` and `process.exit(...)`.\n */\n public fatal(\n dataOrModule: OmittedLoggingData | string,\n action?: string,\n message: any = \"\",\n context?: Record<string, any>,\n ) {\n const data = this.normalizeLogData(dataOrModule, action, message, \"fatal\", context);\n\n return this.log(data);\n }\n\n /**\n * Log an `error` entry when `condition` is falsy. No-op otherwise — the\n * entry is never built and channels are not invoked, so this is genuinely\n * free in the happy path. Mirrors the spirit of `console.assert` but routes\n * through the logger pipeline so persistent channels capture failures.\n *\n * @example\n * log.assert(user !== null, \"auth\", \"session\", \"user vanished mid-flight\", { sessionId });\n */\n public assert(\n condition: unknown,\n module: string,\n action: string,\n message: any,\n context?: Record<string, any>,\n ): this {\n if (condition) return this;\n return this.error(module, action, message, context);\n }\n\n /**\n * Start a duration timer. The returned function emits an `info` entry\n * with `completed in <ms>ms` and a `durationMs` field in `context` when\n * called. Pass an object to `end()` to merge extra fields into context.\n *\n * @example\n * const end = log.timer(\"db\", \"users.findById\");\n * const user = await usersRepo.findById(id);\n * end({ id, found: !!user });\n */\n public timer(module: string, action: string): (extra?: Record<string, any>) => this {\n const startedAt = Date.now();\n return (extra?: Record<string, any>) => {\n const durationMs = Date.now() - startedAt;\n return this.info(module, action, `completed in ${durationMs}ms`, {\n durationMs,\n ...(extra ?? {}),\n });\n };\n }\n\n /**\n * Get channel by name\n */\n public channel(name: string) {\n return this.channels.find((channel) => channel.name === name);\n }\n\n /**\n * Synchronously flush logs\n */\n public flushSync() {\n for (const channel of this.channels) {\n if (channel.flushSync) {\n channel.flushSync();\n }\n }\n }\n\n /**\n * Wait for every in-flight async delivery, then drain every channel that\n * implements `flush()` — bounded by `timeout` (default\n * {@link DEFAULT_FLUSH_TIMEOUT_MS}).\n *\n * Unlike {@link flushSync}, this awaits each channel's async I/O — the\n * correct call for a graceful shutdown that can afford to wait\n * (`await log.flush()` after closing the HTTP server, before\n * `process.exit`). A channel whose delivery is async (a network transport,\n * an async disk write) implements `flush()`, not `flushSync()`.\n *\n * Channels drain concurrently and independently: each waits for its own\n * pending deliveries (including ones logged while the flush runs), then\n * runs its `flush()`. A channel whose delivery hangs therefore cannot hold\n * up another channel's drain, and a channel whose flush rejects can neither\n * stop the others nor escape as an unhandled rejection. When `timeout`\n * elapses, `flush()` resolves anyway and each channel still draining is\n * reported to {@link onChannelError}. Channels without `flush()` only wait\n * for their deliveries.\n *\n * @example\n * async function shutdown() {\n * await httpServer.close();\n * await log.flush({ timeout: 3000 });\n * process.exit(0);\n * }\n */\n public async flush(options: { timeout?: number } = {}): Promise<void> {\n const timeout = options.timeout ?? DEFAULT_FLUSH_TIMEOUT_MS;\n const draining = new Set<LogChannel>(this.channels);\n\n for (const channel of this.pendingDeliveries.keys()) {\n draining.add(channel);\n }\n\n const unfinished = new Set(draining);\n\n const drains = [...draining].map(async (channel) => {\n try {\n await this.drainChannel(channel);\n } catch {\n // A single channel must never break shutdown for the others —\n // a graceful drain is best-effort across every channel.\n } finally {\n unfinished.delete(channel);\n }\n });\n\n const settled = Promise.allSettled(drains).then(() => false);\n\n // `Infinity` (or any non-finite value) waits without a limit; setTimeout\n // would otherwise overflow it to an immediate timeout.\n if (!Number.isFinite(timeout)) {\n await settled;\n return;\n }\n\n let resolveTimeout!: (timedOut: boolean) => void;\n const timeoutReached = new Promise<boolean>((resolve) => {\n resolveTimeout = resolve;\n });\n const timer = setTimeout(() => resolveTimeout(true), Math.max(0, timeout));\n\n // Never keep the process alive just to time a flush out.\n (timer as { unref?: () => void }).unref?.();\n\n const timedOut = await Promise.race([settled, timeoutReached]);\n\n clearTimeout(timer);\n\n if (!timedOut) return;\n\n for (const channel of unfinished) {\n this.reportChannelError(\n new Error(`flush timed out after ${timeout}ms; entries still in flight may be lost`),\n channel,\n { type: \"warn\", module: \"logger\", action: \"flush\", message: \"flush timed out\" },\n );\n }\n }\n\n /**\n * Wait until the channel has no delivery in flight, run its `flush()`, and\n * repeat while that flush let new deliveries in.\n */\n private async drainChannel(channel: LogChannel) {\n do {\n let pending = this.pendingDeliveries.get(channel);\n\n while (pending && pending.size > 0) {\n await Promise.allSettled([...pending]);\n pending = this.pendingDeliveries.get(channel);\n }\n\n if (channel.flush) {\n await channel.flush();\n }\n } while ((this.pendingDeliveries.get(channel)?.size ?? 0) > 0);\n }\n\n /**\n * Register one process-level handler per event that calls `flushSync()`\n * before the process terminates.\n *\n * For signal events (`SIGINT`, `SIGTERM`, `SIGHUP`, `SIGBREAK`, `SIGUSR2`)\n * the handler flushes and then re-raises the signal so Node's default exit\n * behavior runs. For `beforeExit`, the handler flushes in place — Node exits\n * naturally afterwards.\n *\n * Idempotent: calling with the same events replaces the previous handlers.\n * Call `disableAutoFlush()` to unregister.\n *\n * @example\n * log.configure({\n * channels: [new ConsoleLog(), new FileLog()],\n * autoFlushOn: [\"SIGINT\", \"SIGTERM\", \"beforeExit\"],\n * });\n */\n public enableAutoFlush(events: AutoFlushEvent[]): this {\n this.disableAutoFlush();\n\n for (const event of events) {\n const handler = SIGNAL_EVENTS.has(event)\n ? () => {\n this.flushSync();\n process.off(event, handler);\n process.kill(process.pid, event as NodeJS.Signals);\n }\n : () => {\n this.flushSync();\n };\n\n process.on(event, handler);\n this.autoFlushHandlers.set(event, handler);\n }\n\n return this;\n }\n\n /**\n * Remove every handler previously registered by `enableAutoFlush`.\n * Safe to call when no handlers are registered.\n */\n public disableAutoFlush(): this {\n for (const [event, handler] of this.autoFlushHandlers) {\n process.off(event, handler);\n }\n\n this.autoFlushHandlers.clear();\n\n return this;\n }\n}\n\n/**\n * Default `timeout` of {@link Logger.flush}: long enough for a healthy network\n * channel, short enough that a hung one cannot eat a shutdown budget.\n */\nexport const DEFAULT_FLUSH_TIMEOUT_MS = 5000;\n\n/**\n * What the error handler receives when the logger-wide redaction itself\n * failed: the entry's identity only, never the unredacted message or context.\n */\nfunction redactionFailedEntry(data: LoggingData): LoggingData {\n return {\n type: data.type,\n module: data.module,\n action: data.action,\n message: \"[entry dropped: redaction failed]\",\n };\n}\n\n/**\n * Default {@link ChannelErrorHandler}: one stderr line per failure. Written\n * directly to the stream, never through a logger, so a broken channel cannot\n * recurse.\n */\nfunction reportChannelError(error: unknown, channel: LogContract) {\n const reason = error instanceof Error ? error.message : String(error);\n\n try {\n process.stderr.write(\n `[@warlock.js/logger] channel \"${channel.name}\" failed to log: ${reason}\\n`,\n );\n } catch {\n // stderr itself is gone; nothing left to report to.\n }\n}\n\n/**\n * The package singleton. Use this for everyday logging — `log.info(...)`,\n * `log.error(...)`, `log.configure(...)`. Custom logger instances can be\n * created by instantiating `Logger` directly.\n *\n * The name is intentionally short: `log` reads naturally at the call site\n * (`log.info(\"auth\", \"login\", \"ok\")`) and matches the convention used in\n * pino, bunyan, and most JS logging tutorials.\n *\n * Note that `log` is a `Logger` instance, **not** a function — the bare\n * callable form was removed when the dual `log` / `logger` exports were\n * collapsed into a single name. Use `log.info(...)` (or any other level\n * shortcut) to emit entries.\n */\nexport const log = new Logger();\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- //#region ../logger/src/redact/default-keys.d.ts
1
+ //#region ../../packages/logger/src/redact/default-keys.d.ts
2
2
  /**
3
3
  * Built-in secret-key denylist — the redaction floor that applies with no
4
4
  * configuration at all.
@@ -1 +1 @@
1
- {"version":3,"file":"default-keys.d.mts","names":[],"sources":["../../../../../../../logger/src/redact/default-keys.ts"],"mappings":";;AA4CA;;;;AAA8C;AAa9C;;;;AAqEC;AAMD;;;;AAAgD;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAxFhC,kBAAA,CAAmB,GAAW;;;;;;;;;;cAajC,mBAAA;;;;;cA2EA,sBAAA,EAAwB,WAAW"}
1
+ {"version":3,"file":"default-keys.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/redact/default-keys.ts"],"mappings":";;AA4CA;;;;AAA8C;AAa9C;;;;AAqEC;AAMD;;;;AAAgD;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAxFhC,kBAAA,CAAmB,GAAW;;;;;;;;;;cAajC,mBAAA;;;;;cA2EA,sBAAA,EAAwB,WAAW"}
@@ -1,4 +1,4 @@
1
- //#region ../logger/src/redact/default-keys.ts
1
+ //#region ../../packages/logger/src/redact/default-keys.ts
2
2
  /**
3
3
  * Built-in secret-key denylist — the redaction floor that applies with no
4
4
  * configuration at all.