@warlock.js/logger 5.31.0 → 5.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/cjs/index.cjs +4 -4
- package/cjs/index.cjs.map +1 -1
- package/esm/channels/console-log.d.mts +2 -4
- package/esm/channels/console-log.d.mts.map +1 -1
- package/esm/channels/file-log.d.mts +7 -8
- package/esm/channels/file-log.d.mts.map +1 -1
- package/esm/channels/file-log.mjs +2 -2
- package/esm/channels/file-log.mjs.map +1 -1
- package/esm/channels/file-name-pattern.d.mts +1 -2
- package/esm/channels/file-name-pattern.d.mts.map +1 -1
- package/esm/channels/index.mjs +1 -1
- package/esm/channels/json-file-log.d.mts +1 -3
- package/esm/channels/json-file-log.d.mts.map +1 -1
- package/esm/channels/sentry-log.d.mts +7 -8
- package/esm/channels/sentry-log.d.mts.map +1 -1
- package/esm/log-channel.d.mts +2 -4
- package/esm/log-channel.d.mts.map +1 -1
- package/esm/logger.d.mts +3 -5
- package/esm/logger.d.mts.map +1 -1
- package/esm/logger.mjs +1 -1
- package/esm/logger.mjs.map +1 -1
- package/esm/redact/default-keys.d.mts +3 -4
- package/esm/redact/default-keys.d.mts.map +1 -1
- package/esm/redact/escape-log-value.d.mts +2 -3
- package/esm/redact/escape-log-value.d.mts.map +1 -1
- package/esm/redact/index.mjs +1 -1
- package/esm/redact/mask-email.d.mts +3 -4
- package/esm/redact/mask-email.d.mts.map +1 -1
- package/esm/redact/redact.d.mts +3 -5
- package/esm/redact/redact.d.mts.map +1 -1
- package/esm/types.d.mts +11 -12
- package/esm/types.d.mts.map +1 -1
- package/esm/utils/capture-unhandled-errors.d.mts +2 -3
- package/esm/utils/capture-unhandled-errors.d.mts.map +1 -1
- package/esm/utils/clear-message.d.mts +1 -2
- package/esm/utils/clear-message.d.mts.map +1 -1
- package/esm/utils/index.mjs +1 -1
- package/esm/utils/safe-json-stringify.d.mts +1 -2
- package/esm/utils/safe-json-stringify.d.mts.map +1 -1
- package/package.json +2 -2
package/esm/logger.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
+
{"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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"}
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
* non-alphanumeric character. Collapses `apiKey`, `api_key`, `API-KEY`,
|
|
43
43
|
* `Api Key` and `apikey` onto a single entry.
|
|
44
44
|
*/
|
|
45
|
-
declare function normalizeRedactKey(key: string): string;
|
|
45
|
+
export declare function normalizeRedactKey(key: string): string;
|
|
46
46
|
/**
|
|
47
47
|
* The default denylist, in source spelling. Anything whose normalized key
|
|
48
48
|
* matches one of these is replaced by the configured censor (`"[REDACTED]"`
|
|
@@ -52,12 +52,11 @@ declare function normalizeRedactKey(key: string): string;
|
|
|
52
52
|
* Exported so applications can inspect, log, or build on the exact set they
|
|
53
53
|
* are getting rather than guessing at it.
|
|
54
54
|
*/
|
|
55
|
-
declare const DEFAULT_REDACT_KEYS: readonly string[];
|
|
55
|
+
export declare const DEFAULT_REDACT_KEYS: readonly string[];
|
|
56
56
|
/**
|
|
57
57
|
* The default denylist, pre-normalized. Frozen at module load so the hot path
|
|
58
58
|
* never rebuilds it.
|
|
59
59
|
*/
|
|
60
|
-
declare const DEFAULT_REDACT_KEY_SET: ReadonlySet<string>;
|
|
60
|
+
export declare const DEFAULT_REDACT_KEY_SET: ReadonlySet<string>;
|
|
61
61
|
//#endregion
|
|
62
|
-
export { DEFAULT_REDACT_KEYS, DEFAULT_REDACT_KEY_SET, normalizeRedactKey };
|
|
63
62
|
//# sourceMappingURL=default-keys.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default-keys.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/redact/default-keys.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"default-keys.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/redact/default-keys.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA4CgB,mBAAmB;;;;;;;;;;qBAatB;;;;;qBA2EA,wBAAwB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region ../../packages/logger/src/redact/escape-log-value.d.ts
|
|
2
|
-
type EscapeLogValueOptions = {
|
|
2
|
+
export type EscapeLogValueOptions = {
|
|
3
3
|
/**
|
|
4
4
|
* Longest result, in characters. Longer values are cut and end with
|
|
5
5
|
* `…[truncated]`. Unlimited by default.
|
|
@@ -20,7 +20,6 @@ type EscapeLogValueOptions = {
|
|
|
20
20
|
* @example
|
|
21
21
|
* log.info("auth", "login", `Login from ${escapeLogValue(request.userAgent)}`);
|
|
22
22
|
*/
|
|
23
|
-
declare function escapeLogValue(value: unknown, options?: EscapeLogValueOptions): string;
|
|
23
|
+
export declare function escapeLogValue(value: unknown, options?: EscapeLogValueOptions): string;
|
|
24
24
|
//#endregion
|
|
25
|
-
export { EscapeLogValueOptions, escapeLogValue };
|
|
26
25
|
//# sourceMappingURL=escape-log-value.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"escape-log-value.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/redact/escape-log-value.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"escape-log-value.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/redact/escape-log-value.ts"],"mappings":";YAWY;;;;;EAKV;;;;;;;;;;;;;;;;wBAiBc,eAAe,gBAAgB,UAAS"}
|
package/esm/redact/index.mjs
CHANGED
|
@@ -3,4 +3,4 @@ import { applyRedact, mergeRedact, resolveRedactKeys } from "./redact.mjs";
|
|
|
3
3
|
import { escapeLogValue } from "./escape-log-value.mjs";
|
|
4
4
|
import { MASK_EMAILS_MAX_LENGTH, maskEmail, maskEmailsInText } from "./mask-email.mjs";
|
|
5
5
|
|
|
6
|
-
export {
|
|
6
|
+
export { DEFAULT_REDACT_KEYS, DEFAULT_REDACT_KEY_SET, MASK_EMAILS_MAX_LENGTH, applyRedact, escapeLogValue, maskEmail, maskEmailsInText, mergeRedact, normalizeRedactKey, resolveRedactKeys };
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* replaced by a marker, so a huge cache key or message cannot cost more than a
|
|
5
5
|
* bounded amount of time per log line, and nothing past the cut can leak.
|
|
6
6
|
*/
|
|
7
|
-
declare const MASK_EMAILS_MAX_LENGTH = 16384;
|
|
7
|
+
export declare const MASK_EMAILS_MAX_LENGTH = 16384;
|
|
8
8
|
/**
|
|
9
9
|
* Mask one email address, keeping its first character and its domain:
|
|
10
10
|
* `member@example.com` → `m***@example.com`. A value without an `@` is
|
|
@@ -16,7 +16,7 @@ declare const MASK_EMAILS_MAX_LENGTH = 16384;
|
|
|
16
16
|
* @example
|
|
17
17
|
* log.info("mail", "send", `Sending mail to: ${maskEmail(to)}`);
|
|
18
18
|
*/
|
|
19
|
-
declare function maskEmail(address: string): string;
|
|
19
|
+
export declare function maskEmail(address: string): string;
|
|
20
20
|
/**
|
|
21
21
|
* Mask every email address the pattern above recognises in a piece of text
|
|
22
22
|
* with {@link maskEmail}. Text longer than {@link MASK_EMAILS_MAX_LENGTH}
|
|
@@ -34,7 +34,6 @@ declare function maskEmail(address: string): string;
|
|
|
34
34
|
* maskEmailsInText("sent to member@example.com") // "sent to m***@example.com"
|
|
35
35
|
* maskEmailsInText("email.member@example.com") // "e***@example.com"
|
|
36
36
|
*/
|
|
37
|
-
declare function maskEmailsInText(text: string): string;
|
|
37
|
+
export declare function maskEmailsInText(text: string): string;
|
|
38
38
|
//#endregion
|
|
39
|
-
export { MASK_EMAILS_MAX_LENGTH, maskEmail, maskEmailsInText };
|
|
40
39
|
//# sourceMappingURL=mask-email.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mask-email.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/redact/mask-email.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"mask-email.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/redact/mask-email.ts"],"mappings":";;;;;;qBAoBa;;;;;;;;;;;;wBAaG,UAAU;;;;;;;;;;;;;;;;;;wBA4BV,iBAAiB"}
|
package/esm/redact/redact.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { LoggingData, RedactConfig } from "../types.mjs";
|
|
2
|
-
|
|
3
2
|
//#region ../../packages/logger/src/redact/redact.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Resolve the effective key denylist for a config: the built-in set (unless
|
|
@@ -8,7 +7,7 @@ import { LoggingData, RedactConfig } from "../types.mjs";
|
|
|
8
7
|
* Returns `undefined` only when there is nothing to match — i.e. defaults are
|
|
9
8
|
* explicitly off and no custom keys were supplied.
|
|
10
9
|
*/
|
|
11
|
-
declare function resolveRedactKeys(config: RedactConfig | undefined): ReadonlySet<string> | undefined;
|
|
10
|
+
export declare function resolveRedactKeys(config: RedactConfig | undefined): ReadonlySet<string> | undefined;
|
|
12
11
|
/**
|
|
13
12
|
* Produce a new `LoggingData` with sensitive data censored:
|
|
14
13
|
*
|
|
@@ -37,7 +36,7 @@ declare function resolveRedactKeys(config: RedactConfig | undefined): ReadonlySe
|
|
|
37
36
|
* client that exposes request config behind a getter still slips through.
|
|
38
37
|
* Enumerable ones (axios's `.config`, `.response`) *are* covered.
|
|
39
38
|
*/
|
|
40
|
-
declare function applyRedact(data: LoggingData, config: RedactConfig | undefined): LoggingData;
|
|
39
|
+
export declare function applyRedact(data: LoggingData, config: RedactConfig | undefined): LoggingData;
|
|
41
40
|
/**
|
|
42
41
|
* Combine two redact configs into one effective config. Used to merge a
|
|
43
42
|
* channel's additive paths on top of the logger-wide floor.
|
|
@@ -52,7 +51,6 @@ declare function applyRedact(data: LoggingData, config: RedactConfig | undefined
|
|
|
52
51
|
* choice is inherited — opting out logger-wide is not quietly undone by
|
|
53
52
|
* any channel that happens to set a `redact` option.
|
|
54
53
|
*/
|
|
55
|
-
declare function mergeRedact(base: RedactConfig | undefined, extra: RedactConfig | undefined): RedactConfig | undefined;
|
|
54
|
+
export declare function mergeRedact(base: RedactConfig | undefined, extra: RedactConfig | undefined): RedactConfig | undefined;
|
|
56
55
|
//#endregion
|
|
57
|
-
export { applyRedact, mergeRedact, resolveRedactKeys };
|
|
58
56
|
//# sourceMappingURL=redact.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redact.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/redact/redact.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"redact.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/redact/redact.ts"],"mappings":";;;;;;;;;wBA+UgB,kBACd,QAAQ,2BACP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAiDa,YAAY,MAAM,aAAa,QAAQ,2BAA2B;;;;;;;;;;;;;;;wBAgDlE,YACd,MAAM,0BACN,OAAO,2BACN"}
|
package/esm/types.d.mts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - `error` — handled failure; the app continues
|
|
9
9
|
* - `fatal` — unrecoverable failure; the app is going down
|
|
10
10
|
*/
|
|
11
|
-
type LogLevel = "debug" | "info" | "warn" | "error" | "success" | "fatal";
|
|
11
|
+
export type LogLevel = "debug" | "info" | "warn" | "error" | "success" | "fatal";
|
|
12
12
|
/**
|
|
13
13
|
* Process-level events that `Logger.enableAutoFlush()` can hook to drain
|
|
14
14
|
* buffered channels before the process terminates.
|
|
@@ -17,14 +17,14 @@ type LogLevel = "debug" | "info" | "warn" | "error" | "success" | "fatal";
|
|
|
17
17
|
* then re-raised so Node's default exit behavior runs.
|
|
18
18
|
* - `beforeExit` is flushed in place — Node exits on its own afterwards.
|
|
19
19
|
*/
|
|
20
|
-
type AutoFlushEvent = "SIGINT" | "SIGTERM" | "SIGHUP" | "SIGBREAK" | "SIGUSR2" | "beforeExit";
|
|
21
|
-
type DebugMode = "daily" | "monthly" | "yearly" | "hourly";
|
|
20
|
+
export type AutoFlushEvent = "SIGINT" | "SIGTERM" | "SIGHUP" | "SIGBREAK" | "SIGUSR2" | "beforeExit";
|
|
21
|
+
export type DebugMode = "daily" | "monthly" | "yearly" | "hourly";
|
|
22
22
|
/**
|
|
23
23
|
* Replacement value used by `RedactConfig`. Either a literal string
|
|
24
24
|
* (e.g. `"[REDACTED]"`) or a function that receives the original value plus
|
|
25
25
|
* the dotted path it sits at and returns whatever should replace it.
|
|
26
26
|
*/
|
|
27
|
-
type RedactCensor = string | ((value: any, path: string) => any);
|
|
27
|
+
export type RedactCensor = string | ((value: any, path: string) => any);
|
|
28
28
|
/**
|
|
29
29
|
* Strip sensitive fields from log entries before they reach a channel.
|
|
30
30
|
*
|
|
@@ -62,7 +62,7 @@ type RedactCensor = string | ((value: any, path: string) => any);
|
|
|
62
62
|
* },
|
|
63
63
|
* });
|
|
64
64
|
*/
|
|
65
|
-
type RedactConfig = {
|
|
65
|
+
export type RedactConfig = {
|
|
66
66
|
/**
|
|
67
67
|
* Glob path patterns to redact. Paths are evaluated against the full
|
|
68
68
|
* `LoggingData` object — so prefix with `context.` or `message.` to scope
|
|
@@ -100,7 +100,7 @@ type RedactConfig = {
|
|
|
100
100
|
*/
|
|
101
101
|
censor?: RedactCensor;
|
|
102
102
|
};
|
|
103
|
-
type BasicLogConfigurations = {
|
|
103
|
+
export type BasicLogConfigurations = {
|
|
104
104
|
/**
|
|
105
105
|
* Set what level of logs should be logged
|
|
106
106
|
*
|
|
@@ -130,7 +130,7 @@ type BasicLogConfigurations = {
|
|
|
130
130
|
*/
|
|
131
131
|
redact?: RedactConfig;
|
|
132
132
|
};
|
|
133
|
-
type LogMessage = {
|
|
133
|
+
export type LogMessage = {
|
|
134
134
|
content: string;
|
|
135
135
|
level: LogLevel;
|
|
136
136
|
date: string;
|
|
@@ -140,7 +140,7 @@ type LogMessage = {
|
|
|
140
140
|
context?: Record<string, any>;
|
|
141
141
|
timestamp?: string;
|
|
142
142
|
};
|
|
143
|
-
interface LogContract {
|
|
143
|
+
export interface LogContract {
|
|
144
144
|
/**
|
|
145
145
|
* Channel name
|
|
146
146
|
*/
|
|
@@ -176,15 +176,14 @@ interface LogContract {
|
|
|
176
176
|
* `FileLog` retention failing to remove a file). Set with `Logger.onChannelError()` or
|
|
177
177
|
* `configure({ onChannelError })`. It may be async; a rejection is swallowed.
|
|
178
178
|
*/
|
|
179
|
-
type ChannelErrorHandler = (error: unknown, channel: LogContract, entry: LoggingData) => void | Promise<void>;
|
|
180
|
-
type LoggingData = {
|
|
179
|
+
export type ChannelErrorHandler = (error: unknown, channel: LogContract, entry: LoggingData) => void | Promise<void>;
|
|
180
|
+
export type LoggingData = {
|
|
181
181
|
type: LogLevel;
|
|
182
182
|
module: string;
|
|
183
183
|
action: string;
|
|
184
184
|
message: any;
|
|
185
185
|
context?: Record<string, any>;
|
|
186
186
|
};
|
|
187
|
-
type OmittedLoggingData = Omit<LoggingData, "type">;
|
|
187
|
+
export type OmittedLoggingData = Omit<LoggingData, "type">;
|
|
188
188
|
//#endregion
|
|
189
|
-
export { AutoFlushEvent, BasicLogConfigurations, ChannelErrorHandler, DebugMode, LogContract, LogLevel, LogMessage, LoggingData, OmittedLoggingData, RedactCensor, RedactConfig };
|
|
190
189
|
//# sourceMappingURL=types.d.mts.map
|
package/esm/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/logger/src/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/logger/src/types.ts"],"mappings":";;;;;;;;;;YASY;;;;;;;;;YAUA;YAQA;;;;;;YAOA,0BAA0B,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAuCtC;;;;;;;;EAQV;;;;;;;EAOA;;;;;;;;;;;;;;;EAeA;;;;;;EAMA,SAAS;;YAGC;;;;;;EAMV,SAAS;;;;EAIT;IACE;IACA;;;;;EAKF,UAAU,MAAM;;;;EAIhB,WAAW,MAAM,gBAAgB,QAAQ;;;;;;;EAOzC,SAAS;;YAGC;EACV;EACA,OAAO;EACP;EACA;EACA;EACA;EACA,UAAU;EACV;;iBAGe;;;;EAIf;;;;EAKA;;;;EAKA;;;;EAKA,IAAI,MAAM,qBAAqB;;;;;;;EAQ/B,iBAAiB;;;;EAKjB;;;;;;;;;YAUU,uBACV,gBACA,SAAS,aACT,OAAO,uBACG;YAEA;EACV,MAAM;EACN;EACA;EACA;EACA,UAAU;;YAGA,qBAAqB,KAAK"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Options for {@link captureAnyUnhandledRejection}.
|
|
4
4
|
*/
|
|
5
|
-
type CaptureUnhandledOptions = {
|
|
5
|
+
export type CaptureUnhandledOptions = {
|
|
6
6
|
/**
|
|
7
7
|
* Take the process down with `process.exit(1)` after an `uncaughtException`
|
|
8
8
|
* is logged. Defaults to `true` — an uncaught exception leaves the process
|
|
@@ -41,7 +41,6 @@ type CaptureUnhandledOptions = {
|
|
|
41
41
|
* captureAnyUnhandledRejection(); // exits on uncaught
|
|
42
42
|
* captureAnyUnhandledRejection({ exitOnUncaughtException: false }); // dev: log only
|
|
43
43
|
*/
|
|
44
|
-
declare function captureAnyUnhandledRejection(options?: CaptureUnhandledOptions): void;
|
|
44
|
+
export declare function captureAnyUnhandledRejection(options?: CaptureUnhandledOptions): void;
|
|
45
45
|
//#endregion
|
|
46
|
-
export { CaptureUnhandledOptions, captureAnyUnhandledRejection };
|
|
47
46
|
//# sourceMappingURL=capture-unhandled-errors.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture-unhandled-errors.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/utils/capture-unhandled-errors.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"capture-unhandled-errors.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/utils/capture-unhandled-errors.ts"],"mappings":";;;;YAKY;;;;;;;;;;;;;;EAcV;;;;;;;;;;;;;;;;;;;;;;;;;wBAkCc,6BAA6B,UAAS"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clear-message.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/utils/clear-message.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"clear-message.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/utils/clear-message.ts"],"mappings":";;;;wBAGgB,aAAa"}
|
package/esm/utils/index.mjs
CHANGED
|
@@ -2,4 +2,4 @@ import { safeJsonStringify } from "./safe-json-stringify.mjs";
|
|
|
2
2
|
import { clearMessage } from "./clear-message.mjs";
|
|
3
3
|
import { captureAnyUnhandledRejection } from "./capture-unhandled-errors.mjs";
|
|
4
4
|
|
|
5
|
-
export {
|
|
5
|
+
export { captureAnyUnhandledRejection, clearMessage, safeJsonStringify };
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* @example
|
|
9
9
|
* await fs.promises.writeFile(filePath, safeJsonStringify(payload, 2));
|
|
10
10
|
*/
|
|
11
|
-
declare function safeJsonStringify(value: unknown, space?: number): string;
|
|
11
|
+
export declare function safeJsonStringify(value: unknown, space?: number): string;
|
|
12
12
|
//#endregion
|
|
13
|
-
export { safeJsonStringify };
|
|
14
13
|
//# sourceMappingURL=safe-json-stringify.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-json-stringify.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/utils/safe-json-stringify.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"safe-json-stringify.d.mts","names":[],"sources":["../../../../../../../../packages/logger/src/utils/safe-json-stringify.ts"],"mappings":";;;;;;;;;;wBAyCgB,kBAAkB,gBAAgB"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@mongez/copper": "^2.1.3",
|
|
6
6
|
"@mongez/reinforcements": "^4.0.2",
|
|
7
|
-
"@warlock.js/fs": "5.
|
|
7
|
+
"@warlock.js/fs": "5.32.0",
|
|
8
8
|
"dayjs": "^1.11.9",
|
|
9
9
|
"safe-stable-stringify": "^2.5.0"
|
|
10
10
|
},
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"warlock": {
|
|
34
34
|
"environment": "server"
|
|
35
35
|
},
|
|
36
|
-
"version": "5.
|
|
36
|
+
"version": "5.32.0",
|
|
37
37
|
"main": "./cjs/index.cjs",
|
|
38
38
|
"module": "./esm/index.mjs",
|
|
39
39
|
"types": "./esm/index.d.mts",
|