@vielzeug/rune 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -0
- package/dist/_dev.cjs +2 -0
- package/dist/_dev.cjs.map +1 -0
- package/dist/_dev.d.ts +2 -0
- package/dist/_dev.d.ts.map +1 -0
- package/dist/_dev.js +9 -0
- package/dist/_dev.js.map +1 -0
- package/dist/_prototype.cjs +2 -0
- package/dist/_prototype.cjs.map +1 -0
- package/dist/_prototype.d.ts +2 -0
- package/dist/_prototype.d.ts.map +1 -0
- package/dist/_prototype.js +13 -0
- package/dist/_prototype.js.map +1 -0
- package/dist/console.cjs +2 -0
- package/dist/console.cjs.map +1 -0
- package/dist/console.d.ts +65 -0
- package/dist/console.d.ts.map +1 -0
- package/dist/console.js +132 -0
- package/dist/console.js.map +1 -0
- package/dist/errors.cjs +2 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/lazy.cjs +2 -0
- package/dist/lazy.cjs.map +1 -0
- package/dist/lazy.d.ts +28 -0
- package/dist/lazy.d.ts.map +1 -0
- package/dist/lazy.js +21 -0
- package/dist/lazy.js.map +1 -0
- package/dist/logger.cjs +2 -0
- package/dist/logger.cjs.map +1 -0
- package/dist/logger.d.ts +15 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +158 -0
- package/dist/logger.js.map +1 -0
- package/dist/rune.cjs +3 -0
- package/dist/rune.cjs.map +1 -0
- package/dist/rune.iife.js +3 -0
- package/dist/rune.iife.js.map +1 -0
- package/dist/rune.js +3 -0
- package/dist/rune.js.map +1 -0
- package/dist/transports.cjs +3 -0
- package/dist/transports.cjs.map +1 -0
- package/dist/transports.d.ts +88 -0
- package/dist/transports.d.ts.map +1 -0
- package/dist/transports.js +111 -0
- package/dist/transports.js.map +1 -0
- package/dist/types.cjs +2 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.ts +274 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/package.json +43 -0
package/dist/rune.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rune.js","names":[],"sources":["../src/errors.ts","../src/types.ts","../src/_prototype.ts","../src/lazy.ts","../src/_dev.ts","../src/console.ts","../src/logger.ts","../src/transports.ts"],"sourcesContent":["/** Base class for all rune errors. Use `instanceof RuneError` to catch any rune-originated error. */\nexport class RuneError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is RuneError {\n return err instanceof RuneError;\n }\n}\n\n/**\n * Constructed internally when a transport function throws during log entry emission.\n * Never thrown/propagated to the caller — the logger catches the underlying error, wraps it here\n * (available as `.cause`), and reports it via a dev-only warning so the failing transport cannot\n * crash the caller of `log.info()`/etc. or prevent sibling transports from receiving the entry.\n */\nexport class RuneTransportError extends RuneError {\n constructor(cause: unknown) {\n super('Transport threw an unhandled error', { cause: cause instanceof Error ? cause : undefined });\n }\n}\n","/* ─── Log levels ─── */\n\nexport type LogType = 'debug' | 'error' | 'fatal' | 'info' | 'warn';\nexport type LogLevel = LogType | 'off';\n\n/** Numeric priority for each level. Lower = more verbose. Exported for transport authors. */\nexport const PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n error: 3,\n fatal: 4,\n info: 1,\n off: 5,\n warn: 2,\n};\n\n/** Returns true if `level` passes the `threshold`. Returns false when `level` is 'off'. Exported for transport/middleware authors. */\nexport function isLevelEnabled(threshold: LogLevel, level: LogLevel): boolean {\n if (level === 'off') return false;\n\n return PRIORITY[threshold] <= PRIORITY[level];\n}\n\n/* ─── Bindings ─── */\n\nexport type Bindings = Record<string, unknown>;\n\n/* ─── Log entry ─── */\n\n/**\n * The structured record produced by every log call and dispatched to all transports.\n * `data` is the merged result of pinned bindings and per-call context — transports\n * receive a single flat object and do not need to merge anything themselves.\n * Any `Error` instances — whether from a pinned binding (`bindings`/`withBindings()`) or\n * per-call context — are automatically serialized to `{ message, name, stack }`.\n * **Shallow only** — an `Error` nested inside a plain object (e.g. `{ meta: { err } }`) is left as-is;\n * only top-level fields of `data` are checked.\n */\nexport type LogEntry = {\n /**\n * Merged structured data: pinned bindings overlaid with per-call context.\n * Already shallow-copied and immutable — do not mutate.\n */\n data: Readonly<Bindings>;\n level: LogType;\n message?: string;\n namespace: string;\n /** Exact moment of the log call — shared across all transports for the same entry. */\n timestamp: Date;\n};\n\n/* ─── Transport ─── */\n\n/**\n * A transport receives a log entry and is responsible for its own delivery and formatting.\n * If a transport throws, the logger catches it, reports it via a dev-only warning (wrapped in\n * `RuneTransportError`), and continues dispatching the entry to remaining transports — a single\n * misbehaving transport can never crash the caller of `log.info()`/etc. or block its siblings.\n */\nexport type Transport = (entry: LogEntry) => void;\n\n/**\n * Middleware function that transforms or filters log entries before dispatch. Return null to drop the entry.\n * If middleware throws, the logger catches it, reports it via a dev-only warning, and drops the entry\n * (no transports run for it) rather than crashing the caller.\n */\nexport type LogMiddleware = (entry: LogEntry) => LogEntry | null;\n\n/* ─── Transport option types ─── */\n\nexport type RemoteLogData = {\n data?: Bindings;\n env: 'development' | 'production';\n level: LogType;\n message?: string;\n namespace?: string;\n timestamp: string;\n};\n\nexport type RemoteTransportOptions = {\n /** Override the detected runtime environment. Default: auto-detected. */\n env?: 'development' | 'production';\n /** Remote delivery handler — receives the log type and structured payload. */\n handler: (type: LogType, data: RemoteLogData) => void;\n /** Minimum level to forward. Default: 'debug'. */\n level?: LogLevel;\n /**\n * Called when the handler throws or rejects.\n * The async error path is separate from any synchronous errors in the emit call stack.\n * Default: a dev-only `console.warn` (gated by `__RUNE_PROD__`). In production builds,\n * unhandled remote transport errors are silently swallowed — pass an explicit `onError`\n * if you need delivery-failure observability in production.\n */\n onError?: (error: unknown, data: RemoteLogData) => void;\n};\n\nexport type JsonTransportOptions = {\n /**\n * Custom output field names. Useful for adapting to aggregator conventions\n * (Datadog, ELK, Loki, etc.).\n *\n * @example\n * jsonTransport({ fields: { level: 'severity', time: '@timestamp', msg: 'message' } })\n */\n fields?: {\n level?: string;\n msg?: string;\n ns?: string;\n time?: string;\n };\n /** Minimum level to output. Default: 'debug'. */\n level?: LogLevel;\n /** Custom output function. Default: process.stdout.write. */\n output?: (line: string) => void;\n /**\n * Replace circular references with `'[Circular]'` instead of throwing a TypeError.\n * Useful in environments where log payloads may contain complex object graphs.\n * Default: false.\n */\n safe?: boolean;\n};\n\n/** Handle returned by `batchTransport()`. Pass `handle.transport` to `createLogger({ transports })`. */\nexport type BatchHandle = {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose]: () => void;\n /** Stop the interval timer and flush remaining entries. Call on shutdown. Idempotent. */\n dispose: () => void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Immediately flush buffered entries to the downstream handler without stopping the timer. */\n flush: () => void;\n /** The transport function to pass to `createLogger({ transports: [handle.transport] })`. */\n transport: Transport;\n};\n\nexport type BatchTransportOptions = {\n /** Flush interval in milliseconds. Default: 5000. */\n interval?: number;\n /** Minimum level to buffer. Default: 'debug'. */\n level?: LogLevel;\n /**\n * Hard limit on the in-memory buffer size. When the buffer exceeds this value,\n * the oldest entries are dropped to prevent unbounded memory growth.\n * Unlike `maxSize`, this does NOT trigger a flush — it silently drops.\n * Default: unbounded.\n */\n maxBuffer?: number;\n /** Maximum buffer size before an early flush. Default: 50. */\n maxSize?: number;\n /**\n * Callback to receive flushed batches. May return a Promise — async rejections\n * are forwarded to `onFlushError` in addition to synchronous throws.\n */\n onFlush: (entries: LogEntry[]) => void | Promise<void>;\n /**\n * Called when onFlush throws synchronously or rejects asynchronously.\n * Allows retry/dead-letter logic. Default: silent.\n */\n onFlushError?: (entries: LogEntry[], error: unknown) => void;\n};\n\nexport type PipeOptions = {\n /**\n * Called when one of the piped transports throws.\n * Receives the thrown error and the log entry that triggered it.\n * Default: silent (errors are swallowed to protect remaining transports).\n */\n onError?: (error: unknown, entry: LogEntry) => void;\n};\n\nexport type SampleTransportOptions = {\n /** Minimum level to sample. Default: 'debug'. */\n level?: LogLevel;\n /** Fraction of entries to forward (0–1). */\n rate: number;\n /** Downstream transport to receive sampled entries. */\n transport: Transport;\n};\n\nexport type RedactTransportOptions = {\n /**\n * Field names to replace at any depth in `data`.\n * Matched by exact field name — dot-path notation (e.g. `'user.password'`) is NOT supported.\n * A key like `'password'` will redact every field named `'password'` at any nesting level.\n */\n keys: string[];\n /**\n * Maximum object nesting depth to traverse during redaction.\n * Objects deeper than this limit are returned as-is (not redacted).\n * A dev-only warning is emitted when the cap is hit.\n * Default: 20.\n * @security In production builds, the depth warning is suppressed — deeply-nested sensitive\n * fields beyond `maxDepth` will pass through unredacted without any indication. Ensure that\n * sensitive payloads are not nested beyond this limit, or lower `maxDepth` as needed.\n */\n maxDepth?: number;\n /** Replacement value for redacted fields. Default: '[REDACTED]'. */\n replacement?: string;\n /** Downstream transport to receive the redacted entry. */\n transport: Transport;\n};\n\n/* ─── Logger options ─── */\n\nexport type RuneOptions = {\n /** Initial pinned bindings for this logger instance. `Error` values are auto-serialized, same as per-call context. */\n bindings?: Bindings;\n /** Minimum log level for this logger instance. Default: 'debug'. */\n logLevel?: LogLevel;\n /** Middleware pipeline applied to every entry before dispatch to transports. */\n middleware?: LogMiddleware[];\n /**\n * Namespace for this logger. When passed to `child()`, it is automatically\n * dot-joined to the parent namespace (e.g. parent `'api'` + child `'auth'` → `'api.auth'`).\n */\n namespace?: string;\n /**\n * Transport pipeline. Each transport receives every entry that passes the level threshold.\n * Default: [consoleTransport()].\n */\n transports?: Transport[];\n};\n\n/* ─── Log method ─── */\n\n/**\n * Signature shared by all five log-level methods.\n *\n * - `log.info('message')` — string-only, most common.\n * - `log.info({ ...fields }, 'message')` — structured context + optional message.\n * `Error` values in `fields` are automatically serialized to `{ message, name, stack }`.\n * Serialization is shallow only — an `Error` nested inside a nested object is left as-is.\n * - `log.error(err, { ...fields }, 'message')` — Error first, then optional context + message.\n * Shorthand for the pattern where an Error is the primary subject of the log call.\n * The context object may be omitted entirely: `log.error(err, 'message')`.\n *\n * @example\n * log.error({ err: new Error('timeout'), requestId }, 'request failed')\n * log.error(new Error('timeout'), { requestId }, 'request failed')\n */\nexport type LogMethod = {\n (message: string): void;\n (error: Error, message?: string): void;\n (error: Error, context: Bindings, message?: string): void;\n (context: Bindings, message?: string): void;\n};\n\n/* ─── Logger interface ─── */\n\nexport type Logger = {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose]: () => void;\n /** Snapshot of currently pinned bindings. */\n readonly bindings: Readonly<Bindings>;\n /** Create a child logger with config overrides. Inherits all config and bindings by default. */\n child: (overrides?: RuneOptions) => Logger;\n debug: LogMethod;\n /** `AbortSignal` aborted when `dispose()` is called. Use to tie external lifetimes to this logger. */\n readonly disposalSignal: AbortSignal;\n /**\n * Marks the logger as disposed — all subsequent log calls become no-ops.\n * Aborts `disposalSignal`. Does NOT auto-discover or dispose batch transports;\n * hold a direct reference to `batchTransport` and call its `dispose()` on shutdown.\n * Idempotent — safe to call multiple times.\n */\n dispose: () => void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Returns true if entries at this level will pass the configured threshold. */\n enabled: (type: LogLevel) => boolean;\n error: LogMethod;\n fatal: LogMethod;\n /**\n * Wrap a callback in a console group, closing it even on throw/reject.\n * Pass a `level` to gate the group header on the configured log threshold\n * (e.g. `level: 'debug'` suppresses the group when `logLevel` is above `'debug'`).\n * Default: always renders (unless `logLevel` is `'off'`).\n */\n group: <T>(label: string, fn: () => T, level?: LogType) => T;\n /**\n * Same as `group`, using `console.groupCollapsed`.\n * Pass a `level` to gate the group on the configured log threshold.\n */\n groupCollapsed: <T>(label: string, fn: () => T, level?: LogType) => T;\n info: LogMethod;\n /** Active log level for this logger instance. */\n readonly logLevel: LogLevel;\n /** Middleware pipeline applied before dispatch. */\n readonly middleware: readonly LogMiddleware[];\n /** Namespace string for this logger instance. */\n readonly namespace: string;\n /**\n * Measure execution time of `fn` and emit a structured log entry.\n * The entry message is `label`; `data` contains `{ duration_ms }` (rounded to 2 dp).\n * When `fn` throws or rejects, `data` also includes `{ err }` with the serialized error.\n * @param label - Human-readable description of the operation.\n * @param fn - Synchronous or async function to time.\n * @param level - Log level for the timing entry. Default: `'debug'`.\n */\n time: <T>(label: string, fn: () => T, level?: LogType) => T;\n /** Transport pipeline for this logger instance. */\n readonly transports: readonly Transport[];\n /**\n * Add a middleware function to the pipeline. Returns a **new** logger — the original is unchanged.\n * Discarding the return value is a common mistake: always assign the result.\n * @example\n * const log = baseLog.use(tracingMiddleware); // ✓ keep the result\n */\n use: (middleware: LogMiddleware) => Logger;\n warn: LogMethod;\n /**\n * Derive a child logger with additional pinned bindings.\n * The returned logger is fully independent — disposing it does not affect the parent,\n * and disposing the parent does not affect child loggers.\n */\n withBindings: (bindings: Bindings) => Logger;\n};\n","/**\n * Property names that must never be used as a bracket-assignment key on a plain object literal —\n * `obj[key] = value` for `key === '__proto__'` invokes `Object.prototype`'s `__proto__` accessor\n * and reassigns `obj`'s own prototype instead of setting an own property. `constructor` and\n * `prototype` are excluded defensively for the same class of risk.\n *\n * Used when rebuilding a caller-supplied `Bindings` object key-by-key (`logger.ts`'s\n * `serializeErrors()`, `transports.ts`'s `redactObject()`) — bindings/context ultimately come\n * from application code, but nothing prevents an app from passing through attacker-controlled\n * data (e.g. `log.info(JSON.parse(untrustedInput))`).\n *\n * @internal\n */\nconst UNSAFE_OBJECT_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\n/** @internal */\nexport function isUnsafeObjectKey(key: string): boolean {\n return UNSAFE_OBJECT_KEYS.has(key);\n}\n","import type { Bindings } from './types';\n\nimport { isUnsafeObjectKey } from './_prototype';\n\nconst LAZY_BRAND = Symbol('rune.lazy');\n\n/** A deferred binding value — evaluated only when an entry is actually emitted. Create via `lazy(fn)`. */\nexport type LazyBinding = { readonly factory: () => unknown; readonly [LAZY_BRAND]: true };\n\nfunction isLazy(v: unknown): v is LazyBinding {\n return typeof v === 'object' && v !== null && (v as Record<symbol, unknown>)[LAZY_BRAND] === true;\n}\n\n/**\n * Defer evaluation of an expensive binding value until after the log level check passes.\n * The factory function is only called when an entry is actually emitted.\n *\n * @example\n * const reqLog = log.withBindings({ diagnostics: lazy(() => buildExpensiveDiagnostics()) });\n * reqLog.debug('trace'); // diagnostics() only called when logLevel allows debug\n */\nexport function lazy(fn: () => unknown): LazyBinding {\n return { factory: fn, [LAZY_BRAND]: true } as LazyBinding;\n}\n\n/**\n * Resolve any lazy bindings in the given object, returning a plain Bindings object.\n * Non-lazy values are passed through unchanged. Single-pass: allocates only when a lazy\n * binding is actually present (copy-on-first-lazy).\n *\n * **Shallow only** — if a lazy factory returns an object that itself contains lazy bindings,\n * those nested lazy values are NOT resolved. Only top-level keys of the bindings object\n * are checked.\n */\nexport function resolveBindings(bindings: Bindings): Bindings {\n let resolved: Bindings | undefined;\n\n for (const [k, v] of Object.entries(bindings)) {\n // Guard against a `__proto__`/`constructor`/`prototype` binding key hijacking resolved's own\n // prototype via the bracket-assignment accessor — see _prototype.ts.\n if (isLazy(v) && !isUnsafeObjectKey(k)) {\n resolved ??= { ...bindings };\n resolved[k] = v.factory();\n }\n }\n\n return resolved ?? bindings;\n}\n","const isDev = !(globalThis as { __RUNE_PROD__?: boolean }).__RUNE_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/rune] ${msg}`);\n}\n","import type { Bindings, LogEntry, LogLevel, LogType, Transport } from './types';\n\nimport { isLevelEnabled } from './types';\n\n/* ─── Console theme types ─── */\n\n/**\n * Per-level style definition for the console transport.\n * All fields are optional when providing a level override — unspecified fields fall back to the default theme.\n */\nexport type ConsoleThemeEntry = {\n badge: string;\n bg: string;\n border: string;\n color: string;\n};\n\n/**\n * Partial theme overrides merged on top of the default theme.\n * Each level entry is also partial — only specify the fields you want to change.\n *\n * @example\n * consoleTransport({ theme: { error: { badge: '✖' } } })\n */\nexport type ConsoleTheme = Partial<Record<LogType | 'group' | 'ns', Partial<ConsoleThemeEntry>>>;\n\n/**\n * Fully-resolved console theme where every level and every field is populated.\n */\nexport type ResolvedTheme = Record<LogType | 'group' | 'ns', ConsoleThemeEntry>;\n\n/* ─── ConsoleTransportOptions ─── */\n\nexport type ConsoleTransportOptions = {\n /**\n * Enable ANSI 24-bit color output in Node.js.\n * Default: true when process.stdout.isTTY is true, false otherwise.\n */\n ansi?: boolean;\n /**\n * Object serialization format for Node.js output.\n * - 'json' — JSON.stringify (machine-readable, fails on circular refs)\n * - 'raw' — pass the object directly to the console method (default)\n * Default: 'raw'.\n */\n format?: 'json' | 'raw';\n /**\n * Custom object inspector function. When provided, the `data` object is\n * passed through this function before being written to the console.\n */\n inspectFn?: (value: unknown) => string;\n /** Minimum level to output. Default: 'debug'. */\n level?: LogLevel;\n /** Partial theme overrides merged on top of the default theme. */\n theme?: ConsoleTheme;\n /** Whether to include timestamp in console output. Default: true. */\n timestamp?: boolean;\n};\n\n/* ─── Payload builder ─── */\n\nfunction buildPayload(\n data: Readonly<Bindings>,\n message: string | undefined,\n inspectFn: ((v: unknown) => string) | undefined,\n): unknown[] {\n const hasData = Object.keys(data).length > 0;\n const formatted: unknown = inspectFn && hasData ? inspectFn(data) : hasData ? data : undefined;\n\n if (formatted !== undefined && message !== undefined) return [message, formatted];\n\n if (formatted !== undefined) return [formatted];\n\n if (message !== undefined) return [message];\n\n return [];\n}\n\n/* ─── Console theme ─── */\n\nexport const DEFAULT_THEME: ResolvedTheme = {\n debug: { badge: '🅳', bg: '#616161', border: '#424242', color: '#e0e0e0' },\n error: { badge: '🅴', bg: '#d32f2f', border: '#c62828', color: '#fff' },\n fatal: { badge: '🅵', bg: '#4a148c', border: '#38006b', color: '#fff' },\n group: { badge: '🅶', bg: '#546e7a', border: '#455a64', color: '#fff' },\n info: { badge: '🅸', bg: '#1976d2', border: '#1565c0', color: '#fff' },\n ns: { badge: '', bg: '#424242', border: '#212121', color: '#fff' },\n warn: { badge: '🆆', bg: '#ffb300', border: '#ffa000', color: '#212121' },\n};\n\nconst NS_STYLE = 'border-radius: 8px; font: italic small-caps bold 12px; font-weight: lighter; padding: 0 4px;';\n\nconst LOG_METHOD: Record<LogType, 'error' | 'info' | 'log' | 'warn'> = {\n debug: 'log',\n error: 'error',\n fatal: 'error',\n info: 'info',\n warn: 'warn',\n};\n\n/** Deep-merges per-level overrides onto the default theme. Only specified fields within each level entry are replaced. */\nexport function resolveTheme(override: ConsoleTheme | undefined): ResolvedTheme {\n if (!override) return DEFAULT_THEME;\n\n const result: ResolvedTheme = { ...DEFAULT_THEME };\n\n for (const key of Object.keys(override) as Array<LogType | 'group' | 'ns'>) {\n const entry = override[key];\n\n if (entry) result[key] = { ...DEFAULT_THEME[key], ...entry };\n }\n\n return result;\n}\n\n/* ─── ANSI helpers (Node) ─── */\n\nfunction hexToRgb(hex: string): [number, number, number] {\n return [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)];\n}\n\nfunction ansiColor(hex: string, text: string): string {\n const [r, g, b] = hexToRgb(hex);\n\n return `\\x1b[38;2;${r};${g};${b}m${text}\\x1b[0m`;\n}\n\nfunction ansiMuted(text: string): string {\n return `\\x1b[90m${text}\\x1b[0m`;\n}\n\nfunction supportsAnsi(): boolean {\n if (typeof window !== 'undefined') return false;\n\n return (\n (globalThis as Record<string, unknown> & { process?: { stdout?: { isTTY?: boolean } } }).process?.stdout?.isTTY ===\n true\n );\n}\n\nfunction badgeStyle(entry: ConsoleThemeEntry, extra = ''): string {\n return `background: ${entry.bg}; color: ${entry.color}; border: 1px solid ${entry.border}; border-radius: 4px; padding: 0 1px${extra}`;\n}\n\nfunction escapeConsoleFormat(s: string): string {\n return s.replace(/%/g, '%%');\n}\n\n/**\n * Builds the Node console prefix string, passed as the first argument to `console.log()`/etc.\n * alongside the payload. `namespace` is escaped because Node's `console.*` methods run the first\n * string argument through `util.format` — an unescaped `%s`/`%d`/`%o`/etc. in a caller-controlled\n * namespace would consume and hide the actual payload arguments that follow (log forging).\n */\nfunction buildNodePrefix(\n theme: ResolvedTheme,\n type: LogType,\n namespace: string,\n timestamp: string,\n useAnsi: boolean,\n): string {\n const t = theme[type];\n const safeBadge = escapeConsoleFormat(t.badge);\n const badgeText = useAnsi ? ansiColor(t.bg, safeBadge) : safeBadge;\n const meta = [badgeText];\n\n if (namespace) {\n const safeNamespace = escapeConsoleFormat(namespace);\n\n meta.push(useAnsi ? ansiMuted(`[${safeNamespace}]`) : `[${safeNamespace}]`);\n }\n\n if (timestamp) meta.push(useAnsi ? ansiMuted(timestamp) : timestamp);\n\n return `${meta.join(' | ')} |`;\n}\n\nfunction buildBrowserPrefix(\n theme: ResolvedTheme,\n type: LogType,\n namespace: string,\n timestamp: string,\n): { fmt: string; parts: string[] } {\n let fmt = `%c${escapeConsoleFormat(theme[type].badge)}%c`;\n const parts: string[] = [badgeStyle(theme[type]), ''];\n\n if (namespace) {\n fmt += ` %c${escapeConsoleFormat(namespace)}%c`;\n parts.push(badgeStyle(theme.ns, `; ${NS_STYLE}`), '');\n }\n\n if (timestamp) {\n fmt += ` %c${timestamp}%c`;\n parts.push('color: gray', '');\n }\n\n return { fmt, parts };\n}\n\n/* ─── consoleTransport ─── */\n\n/**\n * Formats and writes log entries to the browser or Node.js console.\n * Uses CSS-styled badges in browsers and plain text in Node.\n * Accepts an optional partial `theme` to override colors and badges per level.\n *\n * @example\n * consoleTransport()\n * consoleTransport({ level: 'warn', timestamp: false })\n * consoleTransport({ theme: { error: { badge: '✖' } } })\n * consoleTransport({ inspectFn: (v) => require('util').inspect(v, { colors: true, depth: 4 }) })\n */\nexport function consoleTransport(options: ConsoleTransportOptions = {}): Transport {\n const level = options.level ?? 'debug';\n const showTimestamp = options.timestamp ?? true;\n const format = options.format ?? 'raw';\n const isNode = typeof window === 'undefined';\n const useAnsi = options.ansi ?? (isNode ? supportsAnsi() : false);\n const resolved = resolveTheme(options.theme);\n const inspectFn: ((v: unknown) => string) | undefined =\n format === 'json' ? (v) => JSON.stringify(v) : options.inspectFn;\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n const timestamp = showTimestamp ? entry.timestamp.toISOString().slice(11, 23) : '';\n const payload = buildPayload(entry.data, entry.message, inspectFn);\n const method = console[LOG_METHOD[entry.level]] as (...args: unknown[]) => void;\n\n if (isNode) {\n method(buildNodePrefix(resolved, entry.level, entry.namespace, timestamp, useAnsi), ...payload);\n } else {\n const { fmt, parts } = buildBrowserPrefix(resolved, entry.level, entry.namespace, timestamp);\n\n method(fmt, ...parts, ...payload);\n }\n };\n}\n\n/* ─── Group rendering (used by logger.ts wrapGroup) ─── */\n\nexport function renderGroup(collapsed: boolean, label: string, namespace: string, theme: ResolvedTheme): void {\n const fn = collapsed ? console.groupCollapsed : console.group;\n const isNode = typeof window === 'undefined';\n\n if (isNode) {\n const meta = [theme.group.badge, label];\n\n if (namespace) meta.push(`[${namespace}]`);\n\n fn(meta.join(' | '));\n\n return;\n }\n\n let fmt = `${theme.group.badge} %c${escapeConsoleFormat(label)}%c`;\n const parts: string[] = [badgeStyle(theme.group, '; margin-right: 6px; padding: 1px 3px 0'), ''];\n\n if (namespace) {\n fmt += ` %c${escapeConsoleFormat(namespace)}%c`;\n parts.push(badgeStyle(theme.ns, `; ${NS_STYLE}; margin-right: 6px`), '');\n }\n\n fn(fmt, ...parts);\n}\n","import type { Bindings, LogEntry, Logger, LogLevel, LogMiddleware, LogType, RuneOptions, Transport } from './types';\n\nimport { warn } from './_dev';\nimport { isUnsafeObjectKey } from './_prototype';\nimport { DEFAULT_THEME, consoleTransport, renderGroup } from './console';\nimport { RuneTransportError } from './errors';\nimport { resolveBindings } from './lazy';\nimport { isLevelEnabled } from './types';\n\n/* --- Arg parsing (simplified) --- */\n\nfunction serializeError(err: Error): { message: string; name: string; stack?: string } {\n return { message: err.message, name: err.name, stack: err.stack };\n}\n\nfunction serializeErrors(ctx: Bindings): Bindings {\n const out: Bindings = {};\n\n for (const [k, v] of Object.entries(ctx)) {\n // Guard against a `__proto__`/`constructor`/`prototype` field name hijacking out's own\n // prototype via the bracket-assignment accessor — see _prototype.ts.\n if (isUnsafeObjectKey(k)) continue;\n\n out[k] = v instanceof Error ? serializeError(v) : v;\n }\n\n return out;\n}\n\ntype ParsedArgs = { context: Bindings | undefined; message: string | undefined };\n\nfunction parseArgs(msgOrCtx: unknown, second: unknown, third?: unknown): ParsedArgs {\n if (typeof msgOrCtx === 'string') {\n return { context: undefined, message: msgOrCtx };\n }\n\n if (msgOrCtx instanceof Error) {\n const ctx: Bindings = second !== null && typeof second === 'object' ? (second as Bindings) : {};\n\n return {\n context: { err: serializeError(msgOrCtx), ...ctx },\n message:\n third !== undefined\n ? String(third)\n : second !== undefined && typeof second !== 'object'\n ? String(second)\n : undefined,\n };\n }\n\n if (typeof msgOrCtx === 'object' && msgOrCtx !== null) {\n return {\n context: msgOrCtx as Bindings,\n message: second !== undefined ? String(second) : undefined,\n };\n }\n\n return { context: undefined, message: msgOrCtx !== undefined ? String(msgOrCtx) : undefined };\n}\n\n/* --- Namespace joining --- */\n\n/**\n * Joins parent and child namespaces with '.' as separator.\n * Examples:\n * joinNamespace('api', 'auth') → 'api.auth'\n * joinNamespace('', 'api') → 'api'\n * joinNamespace('api', '') → 'api'\n */\nfunction joinNamespace(parent: string, child: string): string {\n if (!parent) return child;\n\n if (!child) return parent;\n\n return `${parent}.${child}`;\n}\n\n/* --- createLogger --- */\n\n/**\n * Creates an isolated logger instance.\n *\n * Accepts two call signatures:\n * - `createLogger()` / `createLogger(options)` — configure via `RuneOptions`\n * - `createLogger('namespace', options?)` — namespace shorthand + optional options\n *\n * Each instance has its own immutable config (logLevel, transports, middleware).\n * To change the log level at runtime, create a new logger or child.\n */\nexport function createLogger(namespace: string, options?: Omit<RuneOptions, 'namespace'>): Logger;\nexport function createLogger(options?: RuneOptions): Logger;\nexport function createLogger(initial: RuneOptions | string = {}, extra?: Omit<RuneOptions, 'namespace'>): Logger {\n const initialOpts: RuneOptions = typeof initial === 'string' ? { namespace: initial, ...extra } : initial;\n\n const logLevel: LogLevel = initialOpts.logLevel ?? 'debug';\n const middleware: LogMiddleware[] = initialOpts.middleware ?? [];\n const namespace: string = initialOpts.namespace ?? '';\n const transports = initialOpts.transports ?? [consoleTransport()];\n const ownBindings: Bindings = { ...(initialOpts.bindings ?? {}) };\n\n const disposeController = new AbortController();\n let isDisposed = false;\n\n const passes = (type: LogType): boolean => isLevelEnabled(logLevel, type);\n const namespaceSuffix = (): string => (namespace ? ` (namespace: \"${namespace}\")` : '');\n\n const dispatch = (entry: LogEntry): void => {\n let current: LogEntry = entry;\n\n if (middleware.length > 0) {\n let c: LogEntry = entry;\n\n for (const mw of middleware) {\n try {\n const next = mw(c);\n\n if (next == null) return;\n\n c = next;\n } catch (err) {\n warn(`middleware threw and dropped this entry${namespaceSuffix()}: ${String(err)}`);\n\n return;\n }\n }\n\n current = c;\n }\n\n for (const t of transports) {\n try {\n t(current);\n } catch (err) {\n const transportErr = new RuneTransportError(err);\n\n warn(`${transportErr.message}${namespaceSuffix()}: ${String(err)}`);\n }\n }\n };\n\n const emit = (type: LogType, msgOrCtx: unknown, second?: unknown, third?: unknown): void => {\n if (isDisposed) return;\n\n if (!passes(type)) return;\n\n const { context, message } = parseArgs(msgOrCtx, second, third);\n // serializeErrors() always allocates a fresh object, so `data` is never an alias of `ownBindings`.\n const resolvedBindings = serializeErrors(resolveBindings(ownBindings));\n\n const data: Bindings = context\n ? { ...resolvedBindings, ...serializeErrors(resolveBindings(context)) }\n : resolvedBindings;\n\n dispatch({\n data,\n level: type,\n message,\n namespace,\n timestamp: new Date(),\n });\n };\n\n const timeImpl = <T>(label: string, fn: () => T, level: LogType = 'debug'): T => {\n const start = performance.now();\n\n const done = (thrownErr?: unknown): void => {\n const duration_ms = Math.round((performance.now() - start) * 100) / 100;\n const ctx: Bindings = { duration_ms };\n\n if (thrownErr !== undefined)\n ctx['err'] = serializeError(thrownErr instanceof Error ? thrownErr : new Error(String(thrownErr)));\n\n emit(level, ctx, label);\n };\n\n try {\n const result = fn();\n\n if (result instanceof Promise) {\n return result.then(\n (v) => {\n done();\n\n return v;\n },\n (err: unknown) => {\n done(err);\n\n return Promise.reject(err) as never;\n },\n ) as T;\n }\n\n done();\n\n return result;\n } catch (err) {\n done(err);\n throw err;\n }\n };\n\n const wrapGroup = <T>(collapsed: boolean, label: string, fn: () => T, level?: LogType): T => {\n if (isDisposed || logLevel === 'off') return fn();\n\n if (level !== undefined && !passes(level)) {\n return fn();\n }\n\n renderGroup(collapsed, label, namespace, DEFAULT_THEME);\n\n try {\n const result = fn();\n\n if (result instanceof Promise) return result.finally(() => console.groupEnd()) as T;\n\n console.groupEnd();\n\n return result;\n } catch (err) {\n console.groupEnd();\n throw err;\n }\n };\n\n const childLogger = (overrides: RuneOptions = {}): Logger =>\n createLogger({\n bindings: { ...ownBindings, ...(overrides.bindings ?? {}) },\n logLevel: overrides.logLevel ?? logLevel,\n middleware: overrides.middleware ?? middleware,\n namespace: overrides.namespace !== undefined ? joinNamespace(namespace, overrides.namespace) : namespace,\n transports: overrides.transports ?? transports,\n });\n\n const logger: Logger = {\n get bindings(): Readonly<Bindings> {\n return { ...ownBindings };\n },\n\n child: childLogger,\n\n debug: (m: unknown, s?: unknown, t?: unknown) => emit('debug', m, s, t),\n\n get disposalSignal(): AbortSignal {\n return disposeController.signal;\n },\n\n dispose: (): void => {\n if (isDisposed) return;\n\n isDisposed = true;\n disposeController.abort();\n },\n\n get disposed(): boolean {\n return isDisposed;\n },\n\n enabled: (type: LogLevel): boolean => isLevelEnabled(logLevel, type),\n\n error: (m: unknown, s?: unknown, t?: unknown) => emit('error', m, s, t),\n\n fatal: (m: unknown, s?: unknown, t?: unknown) => emit('fatal', m, s, t),\n\n group: (label, fn, level) => wrapGroup(false, label, fn, level),\n\n groupCollapsed: (label, fn, level) => wrapGroup(true, label, fn, level),\n\n info: (m: unknown, s?: unknown, t?: unknown) => emit('info', m, s, t),\n\n get logLevel(): LogLevel {\n return logLevel;\n },\n\n get middleware(): readonly LogMiddleware[] {\n return [...middleware];\n },\n\n get namespace(): string {\n return namespace;\n },\n\n [Symbol.dispose](): void {\n logger.dispose();\n },\n\n time: timeImpl,\n\n get transports(): readonly Transport[] {\n return [...transports];\n },\n\n use: (mw: LogMiddleware): Logger => childLogger({ middleware: [...middleware, mw] }),\n\n warn: (m: unknown, s?: unknown, t?: unknown) => emit('warn', m, s, t),\n\n withBindings: (bindings: Bindings): Logger => childLogger({ bindings }),\n };\n\n return logger;\n}\n\nexport const defaultLogger = createLogger();\n","import type {\n BatchHandle,\n BatchTransportOptions,\n Bindings,\n JsonTransportOptions,\n LogEntry,\n PipeOptions,\n RedactTransportOptions,\n RemoteLogData,\n RemoteTransportOptions,\n SampleTransportOptions,\n Transport,\n} from './types';\n\nimport { warn } from './_dev';\nimport { isUnsafeObjectKey } from './_prototype';\nimport { isLevelEnabled } from './types';\n\nexport type { RemoteLogData };\n\n/* ─── Environment detection ─── */\n\nfunction detectEnv(): 'development' | 'production' {\n if (typeof window === 'undefined') {\n return (globalThis as Record<string, unknown> & { process?: { env?: { NODE_ENV?: string } } }).process?.env\n ?.NODE_ENV === 'production'\n ? 'production'\n : 'development';\n }\n\n return (import.meta as ImportMeta & { env?: { PROD?: boolean } }).env?.PROD ? 'production' : 'development';\n}\n\n/* ─── remoteTransport ─── */\n\n/**\n * Forwards log entries asynchronously to a remote handler.\n * The handler is fire-and-forget. Use onError to observe delivery failures.\n * Console and remote thresholds are fully independent.\n *\n * **Security note:** serialized `Error` objects include the full `stack` trace,\n * which may expose internal file paths. Use `redactTransport` or a middleware\n * to strip `err.stack` before forwarding in production if this is a concern.\n *\n * @example\n * remoteTransport({\n * handler: async (type, data) => {\n * await fetch('/api/logs', { body: JSON.stringify(data), method: 'POST' });\n * },\n * level: 'error',\n * })\n */\nexport function remoteTransport(options: RemoteTransportOptions): Transport {\n const { handler } = options;\n const level = options.level ?? 'debug';\n const env = options.env ?? detectEnv();\n const onError = options.onError ?? ((err: unknown) => warn(`remote transport error: ${String(err)}`));\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n const hasData = Object.keys(entry.data).length > 0;\n const payload: RemoteLogData = {\n data: hasData ? entry.data : undefined,\n env,\n level: entry.level,\n message: entry.message,\n namespace: entry.namespace || undefined,\n timestamp: entry.timestamp.toISOString(),\n };\n\n Promise.resolve()\n .then(() => handler(entry.level, payload))\n .catch((err: unknown) => onError(err, payload));\n };\n}\n\n/* ─── jsonTransport ─── */\n\nfunction makeCircularReplacer(): (_key: string, value: unknown) => unknown {\n const seen = new WeakSet();\n\n return (_key: string, value: unknown): unknown => {\n if (typeof value === 'object' && value !== null) {\n if (seen.has(value)) return '[Circular]';\n\n seen.add(value);\n }\n\n return value;\n };\n}\n\n/**\n * Writes newline-delimited JSON (NDJSON) to stdout or a custom output function.\n * Useful for structured log aggregation pipelines in Node.js (ELK, Datadog, etc.).\n *\n * @example\n * jsonTransport({ level: 'info' })\n * jsonTransport({ safe: true }) // handles circular references gracefully\n * jsonTransport({ output: (line) => fs.appendFileSync('app.log', line + '\\n') })\n */\nexport function jsonTransport(options: JsonTransportOptions = {}): Transport {\n const level = options.level ?? 'debug';\n const f = options.fields ?? {};\n const fLevel = f.level ?? 'level';\n const fTime = f.time ?? 'time';\n const fNs = f.ns ?? 'ns';\n const fMsg = f.msg ?? 'msg';\n const safe = options.safe ?? false;\n const output =\n options.output ??\n ((line: string) => {\n (\n globalThis as Record<string, unknown> & { process?: { stdout?: { write: (s: string) => void } } }\n ).process?.stdout?.write(line + '\\n');\n });\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n const record: Record<string, unknown> = {\n ...entry.data,\n [fLevel]: entry.level,\n [fTime]: entry.timestamp.toISOString(),\n ...(entry.namespace && { [fNs]: entry.namespace }),\n ...(entry.message !== undefined && { [fMsg]: entry.message }),\n };\n\n output(JSON.stringify(record, safe ? makeCircularReplacer() : undefined));\n };\n}\n\n/* ─── batchTransport ─── */\n\n/**\n * Buffers log entries and flushes them in batches, reducing I/O overhead.\n * Flushes when the buffer reaches maxSize or after the interval elapses.\n *\n * Returns a `BatchHandle` with `.transport`, `.flush()`, and `.dispose()` methods:\n * - `.transport` — pass to `createLogger({ transports: [handle.transport] })`\n * - `.flush()` — immediately send buffered entries without stopping the timer\n * - `.dispose()` — stop the interval and flush remaining entries (call on shutdown)\n *\n * Use `onFlushError` to observe or retry failed flushes (e.g. dead-letter queue).\n *\n * @example\n * const batch = batchTransport({\n * onFlush: (entries) => sendToCollector(entries),\n * onFlushError: (entries, err) => deadLetter.push(entries),\n * interval: 10_000,\n * maxSize: 100,\n * });\n * createLogger({ transports: [batch.transport] });\n * batch.dispose(); // call on app shutdown\n */\nexport function batchTransport(options: BatchTransportOptions): BatchHandle {\n const level = options.level ?? 'debug';\n const maxSize = options.maxSize ?? 50;\n const maxBuffer = options.maxBuffer;\n const interval = options.interval ?? 5000;\n\n let buffer: LogEntry[] = [];\n let timer: ReturnType<typeof setInterval> | undefined;\n let batchDisposed = false;\n\n function flush(): void {\n if (buffer.length === 0) return;\n\n const entries = buffer;\n\n buffer = [];\n\n Promise.resolve()\n .then(() => options.onFlush(entries))\n .catch((err: unknown) => options.onFlushError?.(entries, err));\n }\n\n const transportFn: Transport = (entry: LogEntry): void => {\n if (batchDisposed) return;\n\n if (!isLevelEnabled(level, entry.level)) return;\n\n if (!timer) timer = setInterval(flush, interval);\n\n buffer.push(entry);\n\n if (maxBuffer !== undefined && buffer.length > maxBuffer) {\n buffer = buffer.slice(buffer.length - maxBuffer);\n }\n\n if (buffer.length >= maxSize) flush();\n };\n\n const handle: BatchHandle = {\n dispose(): void {\n if (batchDisposed) return;\n\n batchDisposed = true;\n\n if (timer) {\n clearInterval(timer);\n timer = undefined;\n }\n\n flush();\n },\n get disposed(): boolean {\n return batchDisposed;\n },\n flush,\n [Symbol.dispose](): void {\n handle.dispose();\n },\n transport: transportFn,\n };\n\n return handle;\n}\n\n/* ─── sampleTransport ─── */\n\n/**\n * Probabilistically forwards entries to a downstream transport.\n * Useful for reducing volume of high-frequency debug logs in production.\n *\n * @example\n * sampleTransport({ rate: 0.1, transport: remoteTransport({ handler }) })\n */\nexport function sampleTransport(options: SampleTransportOptions): Transport {\n const { rate, transport } = options;\n const level = options.level ?? 'debug';\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n if (Math.random() < rate) transport(entry);\n };\n}\n\n/* ─── redactTransport ─── */\n\n/**\n * Strips sensitive fields from `data` before forwarding to a downstream transport.\n * Redaction is applied recursively at any depth, including inside arrays.\n *\n * @example\n * redactTransport({\n * keys: ['password', 'token', 'ssn'],\n * replacement: '[REDACTED]',\n * transport: remoteTransport({ handler }),\n * })\n */\nexport function redactTransport(options: RedactTransportOptions): Transport {\n const { keys, maxDepth = 20, replacement = '[REDACTED]', transport } = options;\n\n for (const key of keys) {\n if (key.includes('.')) {\n warn(\n `redactTransport: key \"${key}\" contains a dot. Dot-path notation is not supported — use the plain field name (e.g. 'password') to redact at any depth.`,\n );\n }\n }\n\n const keySet = new Set(keys);\n\n function redactValue(v: unknown, depth = 0): unknown {\n if (depth > maxDepth) {\n warn(\n `redactTransport: object nesting depth exceeded ${maxDepth} — redaction truncated at this level. Sensitive fields below depth ${maxDepth} may not be redacted.`,\n );\n\n return v;\n }\n\n if (Array.isArray(v)) return v.map((item) => redactValue(item, depth + 1));\n\n if (typeof v === 'object' && v !== null) return redactObject(v as Bindings, depth + 1);\n\n return v;\n }\n\n function redactObject(obj: Bindings, depth = 0): Bindings {\n const result: Bindings = {};\n\n for (const [k, v] of Object.entries(obj)) {\n // Guard against a `__proto__`/`constructor`/`prototype` field name hijacking result's own\n // prototype via the bracket-assignment accessor — see _prototype.ts.\n if (isUnsafeObjectKey(k)) continue;\n\n result[k] = keySet.has(k) ? replacement : redactValue(v, depth);\n }\n\n return result;\n }\n\n return (entry: LogEntry): void => {\n transport({ ...entry, data: redactObject(entry.data as Bindings) });\n };\n}\n\n/* ─── pipe — fault-tolerant fan-out ─── */\n\n/**\n * Fan-out: dispatches each entry to all provided transports independently.\n * A throw in one transport does not prevent others from receiving the entry.\n * Pass `onError` to observe individual transport failures; without it, errors are silently swallowed.\n *\n * @example\n * createLogger({\n * transports: [pipe(consoleTransport(), remoteTransport({ handler }))],\n * })\n *\n * // With error observer:\n * pipe({ onError: (err) => console.error('[pipe]', err) }, consoleTransport(), remoteTransport({ handler }))\n */\nexport function pipe(...transports: Transport[]): Transport;\nexport function pipe(options: PipeOptions, ...transports: Transport[]): Transport;\nexport function pipe(optionsOrTransport: PipeOptions | Transport, ...rest: Transport[]): Transport {\n let opts: PipeOptions;\n let transports: Transport[];\n\n if (typeof optionsOrTransport === 'function') {\n opts = {};\n transports = [optionsOrTransport, ...rest];\n } else {\n opts = optionsOrTransport;\n transports = rest;\n }\n\n return (entry: LogEntry): void => {\n for (const t of transports) {\n try {\n t(entry);\n } catch (err) {\n opts.onError?.(err, entry);\n }\n }\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
const e=require("./types.cjs"),t=require("./_prototype.cjs"),n=require("./_dev.cjs");function r(){return typeof window>`u`?globalThis.process?.env?.NODE_ENV===`production`?`production`:`development`:`production`}function i(t){let{handler:i}=t,a=t.level??`debug`,o=t.env??r(),s=t.onError??(e=>n.warn(`remote transport error: ${String(e)}`));return t=>{if(!e.isLevelEnabled(a,t.level))return;let n={data:Object.keys(t.data).length>0?t.data:void 0,env:o,level:t.level,message:t.message,namespace:t.namespace||void 0,timestamp:t.timestamp.toISOString()};Promise.resolve().then(()=>i(t.level,n)).catch(e=>s(e,n))}}function a(){let e=new WeakSet;return(t,n)=>{if(typeof n==`object`&&n){if(e.has(n))return`[Circular]`;e.add(n)}return n}}function o(t={}){let n=t.level??`debug`,r=t.fields??{},i=r.level??`level`,o=r.time??`time`,s=r.ns??`ns`,c=r.msg??`msg`,l=t.safe??!1,u=t.output??(e=>{globalThis.process?.stdout?.write(e+`
|
|
2
|
+
`)});return t=>{if(!e.isLevelEnabled(n,t.level))return;let r={...t.data,[i]:t.level,[o]:t.timestamp.toISOString(),...t.namespace&&{[s]:t.namespace},...t.message!==void 0&&{[c]:t.message}};u(JSON.stringify(r,l?a():void 0))}}function s(t){let n=t.level??`debug`,r=t.maxSize??50,i=t.maxBuffer,a=t.interval??5e3,o=[],s,c=!1;function l(){if(o.length===0)return;let e=o;o=[],Promise.resolve().then(()=>t.onFlush(e)).catch(n=>t.onFlushError?.(e,n))}let u=t=>{c||e.isLevelEnabled(n,t.level)&&(s||=setInterval(l,a),o.push(t),i!==void 0&&o.length>i&&(o=o.slice(o.length-i)),o.length>=r&&l())},d={dispose(){c||(c=!0,s&&=(clearInterval(s),void 0),l())},get disposed(){return c},flush:l,[Symbol.dispose](){d.dispose()},transport:u};return d}function c(t){let{rate:n,transport:r}=t,i=t.level??`debug`;return t=>{e.isLevelEnabled(i,t.level)&&Math.random()<n&&r(t)}}function l(e){let{keys:r,maxDepth:i=20,replacement:a=`[REDACTED]`,transport:o}=e;for(let e of r)e.includes(`.`)&&n.warn(`redactTransport: key "${e}" contains a dot. Dot-path notation is not supported — use the plain field name (e.g. 'password') to redact at any depth.`);let s=new Set(r);function c(e,t=0){return t>i?(n.warn(`redactTransport: object nesting depth exceeded ${i} — redaction truncated at this level. Sensitive fields below depth ${i} may not be redacted.`),e):Array.isArray(e)?e.map(e=>c(e,t+1)):typeof e==`object`&&e?l(e,t+1):e}function l(e,n=0){let r={};for(let[i,o]of Object.entries(e))t.isUnsafeObjectKey(i)||(r[i]=s.has(i)?a:c(o,n));return r}return e=>{o({...e,data:l(e.data)})}}function u(e,...t){let n,r;return typeof e==`function`?(n={},r=[e,...t]):(n=e,r=t),e=>{for(let t of r)try{t(e)}catch(t){n.onError?.(t,e)}}}exports.batchTransport=s,exports.jsonTransport=o,exports.pipe=u,exports.redactTransport=l,exports.remoteTransport=i,exports.sampleTransport=c;
|
|
3
|
+
//# sourceMappingURL=transports.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transports.cjs","names":[],"sources":["../src/transports.ts"],"sourcesContent":["import type {\n BatchHandle,\n BatchTransportOptions,\n Bindings,\n JsonTransportOptions,\n LogEntry,\n PipeOptions,\n RedactTransportOptions,\n RemoteLogData,\n RemoteTransportOptions,\n SampleTransportOptions,\n Transport,\n} from './types';\n\nimport { warn } from './_dev';\nimport { isUnsafeObjectKey } from './_prototype';\nimport { isLevelEnabled } from './types';\n\nexport type { RemoteLogData };\n\n/* ─── Environment detection ─── */\n\nfunction detectEnv(): 'development' | 'production' {\n if (typeof window === 'undefined') {\n return (globalThis as Record<string, unknown> & { process?: { env?: { NODE_ENV?: string } } }).process?.env\n ?.NODE_ENV === 'production'\n ? 'production'\n : 'development';\n }\n\n return (import.meta as ImportMeta & { env?: { PROD?: boolean } }).env?.PROD ? 'production' : 'development';\n}\n\n/* ─── remoteTransport ─── */\n\n/**\n * Forwards log entries asynchronously to a remote handler.\n * The handler is fire-and-forget. Use onError to observe delivery failures.\n * Console and remote thresholds are fully independent.\n *\n * **Security note:** serialized `Error` objects include the full `stack` trace,\n * which may expose internal file paths. Use `redactTransport` or a middleware\n * to strip `err.stack` before forwarding in production if this is a concern.\n *\n * @example\n * remoteTransport({\n * handler: async (type, data) => {\n * await fetch('/api/logs', { body: JSON.stringify(data), method: 'POST' });\n * },\n * level: 'error',\n * })\n */\nexport function remoteTransport(options: RemoteTransportOptions): Transport {\n const { handler } = options;\n const level = options.level ?? 'debug';\n const env = options.env ?? detectEnv();\n const onError = options.onError ?? ((err: unknown) => warn(`remote transport error: ${String(err)}`));\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n const hasData = Object.keys(entry.data).length > 0;\n const payload: RemoteLogData = {\n data: hasData ? entry.data : undefined,\n env,\n level: entry.level,\n message: entry.message,\n namespace: entry.namespace || undefined,\n timestamp: entry.timestamp.toISOString(),\n };\n\n Promise.resolve()\n .then(() => handler(entry.level, payload))\n .catch((err: unknown) => onError(err, payload));\n };\n}\n\n/* ─── jsonTransport ─── */\n\nfunction makeCircularReplacer(): (_key: string, value: unknown) => unknown {\n const seen = new WeakSet();\n\n return (_key: string, value: unknown): unknown => {\n if (typeof value === 'object' && value !== null) {\n if (seen.has(value)) return '[Circular]';\n\n seen.add(value);\n }\n\n return value;\n };\n}\n\n/**\n * Writes newline-delimited JSON (NDJSON) to stdout or a custom output function.\n * Useful for structured log aggregation pipelines in Node.js (ELK, Datadog, etc.).\n *\n * @example\n * jsonTransport({ level: 'info' })\n * jsonTransport({ safe: true }) // handles circular references gracefully\n * jsonTransport({ output: (line) => fs.appendFileSync('app.log', line + '\\n') })\n */\nexport function jsonTransport(options: JsonTransportOptions = {}): Transport {\n const level = options.level ?? 'debug';\n const f = options.fields ?? {};\n const fLevel = f.level ?? 'level';\n const fTime = f.time ?? 'time';\n const fNs = f.ns ?? 'ns';\n const fMsg = f.msg ?? 'msg';\n const safe = options.safe ?? false;\n const output =\n options.output ??\n ((line: string) => {\n (\n globalThis as Record<string, unknown> & { process?: { stdout?: { write: (s: string) => void } } }\n ).process?.stdout?.write(line + '\\n');\n });\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n const record: Record<string, unknown> = {\n ...entry.data,\n [fLevel]: entry.level,\n [fTime]: entry.timestamp.toISOString(),\n ...(entry.namespace && { [fNs]: entry.namespace }),\n ...(entry.message !== undefined && { [fMsg]: entry.message }),\n };\n\n output(JSON.stringify(record, safe ? makeCircularReplacer() : undefined));\n };\n}\n\n/* ─── batchTransport ─── */\n\n/**\n * Buffers log entries and flushes them in batches, reducing I/O overhead.\n * Flushes when the buffer reaches maxSize or after the interval elapses.\n *\n * Returns a `BatchHandle` with `.transport`, `.flush()`, and `.dispose()` methods:\n * - `.transport` — pass to `createLogger({ transports: [handle.transport] })`\n * - `.flush()` — immediately send buffered entries without stopping the timer\n * - `.dispose()` — stop the interval and flush remaining entries (call on shutdown)\n *\n * Use `onFlushError` to observe or retry failed flushes (e.g. dead-letter queue).\n *\n * @example\n * const batch = batchTransport({\n * onFlush: (entries) => sendToCollector(entries),\n * onFlushError: (entries, err) => deadLetter.push(entries),\n * interval: 10_000,\n * maxSize: 100,\n * });\n * createLogger({ transports: [batch.transport] });\n * batch.dispose(); // call on app shutdown\n */\nexport function batchTransport(options: BatchTransportOptions): BatchHandle {\n const level = options.level ?? 'debug';\n const maxSize = options.maxSize ?? 50;\n const maxBuffer = options.maxBuffer;\n const interval = options.interval ?? 5000;\n\n let buffer: LogEntry[] = [];\n let timer: ReturnType<typeof setInterval> | undefined;\n let batchDisposed = false;\n\n function flush(): void {\n if (buffer.length === 0) return;\n\n const entries = buffer;\n\n buffer = [];\n\n Promise.resolve()\n .then(() => options.onFlush(entries))\n .catch((err: unknown) => options.onFlushError?.(entries, err));\n }\n\n const transportFn: Transport = (entry: LogEntry): void => {\n if (batchDisposed) return;\n\n if (!isLevelEnabled(level, entry.level)) return;\n\n if (!timer) timer = setInterval(flush, interval);\n\n buffer.push(entry);\n\n if (maxBuffer !== undefined && buffer.length > maxBuffer) {\n buffer = buffer.slice(buffer.length - maxBuffer);\n }\n\n if (buffer.length >= maxSize) flush();\n };\n\n const handle: BatchHandle = {\n dispose(): void {\n if (batchDisposed) return;\n\n batchDisposed = true;\n\n if (timer) {\n clearInterval(timer);\n timer = undefined;\n }\n\n flush();\n },\n get disposed(): boolean {\n return batchDisposed;\n },\n flush,\n [Symbol.dispose](): void {\n handle.dispose();\n },\n transport: transportFn,\n };\n\n return handle;\n}\n\n/* ─── sampleTransport ─── */\n\n/**\n * Probabilistically forwards entries to a downstream transport.\n * Useful for reducing volume of high-frequency debug logs in production.\n *\n * @example\n * sampleTransport({ rate: 0.1, transport: remoteTransport({ handler }) })\n */\nexport function sampleTransport(options: SampleTransportOptions): Transport {\n const { rate, transport } = options;\n const level = options.level ?? 'debug';\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n if (Math.random() < rate) transport(entry);\n };\n}\n\n/* ─── redactTransport ─── */\n\n/**\n * Strips sensitive fields from `data` before forwarding to a downstream transport.\n * Redaction is applied recursively at any depth, including inside arrays.\n *\n * @example\n * redactTransport({\n * keys: ['password', 'token', 'ssn'],\n * replacement: '[REDACTED]',\n * transport: remoteTransport({ handler }),\n * })\n */\nexport function redactTransport(options: RedactTransportOptions): Transport {\n const { keys, maxDepth = 20, replacement = '[REDACTED]', transport } = options;\n\n for (const key of keys) {\n if (key.includes('.')) {\n warn(\n `redactTransport: key \"${key}\" contains a dot. Dot-path notation is not supported — use the plain field name (e.g. 'password') to redact at any depth.`,\n );\n }\n }\n\n const keySet = new Set(keys);\n\n function redactValue(v: unknown, depth = 0): unknown {\n if (depth > maxDepth) {\n warn(\n `redactTransport: object nesting depth exceeded ${maxDepth} — redaction truncated at this level. Sensitive fields below depth ${maxDepth} may not be redacted.`,\n );\n\n return v;\n }\n\n if (Array.isArray(v)) return v.map((item) => redactValue(item, depth + 1));\n\n if (typeof v === 'object' && v !== null) return redactObject(v as Bindings, depth + 1);\n\n return v;\n }\n\n function redactObject(obj: Bindings, depth = 0): Bindings {\n const result: Bindings = {};\n\n for (const [k, v] of Object.entries(obj)) {\n // Guard against a `__proto__`/`constructor`/`prototype` field name hijacking result's own\n // prototype via the bracket-assignment accessor — see _prototype.ts.\n if (isUnsafeObjectKey(k)) continue;\n\n result[k] = keySet.has(k) ? replacement : redactValue(v, depth);\n }\n\n return result;\n }\n\n return (entry: LogEntry): void => {\n transport({ ...entry, data: redactObject(entry.data as Bindings) });\n };\n}\n\n/* ─── pipe — fault-tolerant fan-out ─── */\n\n/**\n * Fan-out: dispatches each entry to all provided transports independently.\n * A throw in one transport does not prevent others from receiving the entry.\n * Pass `onError` to observe individual transport failures; without it, errors are silently swallowed.\n *\n * @example\n * createLogger({\n * transports: [pipe(consoleTransport(), remoteTransport({ handler }))],\n * })\n *\n * // With error observer:\n * pipe({ onError: (err) => console.error('[pipe]', err) }, consoleTransport(), remoteTransport({ handler }))\n */\nexport function pipe(...transports: Transport[]): Transport;\nexport function pipe(options: PipeOptions, ...transports: Transport[]): Transport;\nexport function pipe(optionsOrTransport: PipeOptions | Transport, ...rest: Transport[]): Transport {\n let opts: PipeOptions;\n let transports: Transport[];\n\n if (typeof optionsOrTransport === 'function') {\n opts = {};\n transports = [optionsOrTransport, ...rest];\n } else {\n opts = optionsOrTransport;\n transports = rest;\n }\n\n return (entry: LogEntry): void => {\n for (const t of transports) {\n try {\n t(entry);\n } catch (err) {\n opts.onError?.(err, entry);\n }\n }\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { BatchHandle, BatchTransportOptions, JsonTransportOptions, PipeOptions, RedactTransportOptions, RemoteLogData, RemoteTransportOptions, SampleTransportOptions, Transport } from './types';
|
|
2
|
+
export type { RemoteLogData };
|
|
3
|
+
/**
|
|
4
|
+
* Forwards log entries asynchronously to a remote handler.
|
|
5
|
+
* The handler is fire-and-forget. Use onError to observe delivery failures.
|
|
6
|
+
* Console and remote thresholds are fully independent.
|
|
7
|
+
*
|
|
8
|
+
* **Security note:** serialized `Error` objects include the full `stack` trace,
|
|
9
|
+
* which may expose internal file paths. Use `redactTransport` or a middleware
|
|
10
|
+
* to strip `err.stack` before forwarding in production if this is a concern.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* remoteTransport({
|
|
14
|
+
* handler: async (type, data) => {
|
|
15
|
+
* await fetch('/api/logs', { body: JSON.stringify(data), method: 'POST' });
|
|
16
|
+
* },
|
|
17
|
+
* level: 'error',
|
|
18
|
+
* })
|
|
19
|
+
*/
|
|
20
|
+
export declare function remoteTransport(options: RemoteTransportOptions): Transport;
|
|
21
|
+
/**
|
|
22
|
+
* Writes newline-delimited JSON (NDJSON) to stdout or a custom output function.
|
|
23
|
+
* Useful for structured log aggregation pipelines in Node.js (ELK, Datadog, etc.).
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* jsonTransport({ level: 'info' })
|
|
27
|
+
* jsonTransport({ safe: true }) // handles circular references gracefully
|
|
28
|
+
* jsonTransport({ output: (line) => fs.appendFileSync('app.log', line + '\n') })
|
|
29
|
+
*/
|
|
30
|
+
export declare function jsonTransport(options?: JsonTransportOptions): Transport;
|
|
31
|
+
/**
|
|
32
|
+
* Buffers log entries and flushes them in batches, reducing I/O overhead.
|
|
33
|
+
* Flushes when the buffer reaches maxSize or after the interval elapses.
|
|
34
|
+
*
|
|
35
|
+
* Returns a `BatchHandle` with `.transport`, `.flush()`, and `.dispose()` methods:
|
|
36
|
+
* - `.transport` — pass to `createLogger({ transports: [handle.transport] })`
|
|
37
|
+
* - `.flush()` — immediately send buffered entries without stopping the timer
|
|
38
|
+
* - `.dispose()` — stop the interval and flush remaining entries (call on shutdown)
|
|
39
|
+
*
|
|
40
|
+
* Use `onFlushError` to observe or retry failed flushes (e.g. dead-letter queue).
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* const batch = batchTransport({
|
|
44
|
+
* onFlush: (entries) => sendToCollector(entries),
|
|
45
|
+
* onFlushError: (entries, err) => deadLetter.push(entries),
|
|
46
|
+
* interval: 10_000,
|
|
47
|
+
* maxSize: 100,
|
|
48
|
+
* });
|
|
49
|
+
* createLogger({ transports: [batch.transport] });
|
|
50
|
+
* batch.dispose(); // call on app shutdown
|
|
51
|
+
*/
|
|
52
|
+
export declare function batchTransport(options: BatchTransportOptions): BatchHandle;
|
|
53
|
+
/**
|
|
54
|
+
* Probabilistically forwards entries to a downstream transport.
|
|
55
|
+
* Useful for reducing volume of high-frequency debug logs in production.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* sampleTransport({ rate: 0.1, transport: remoteTransport({ handler }) })
|
|
59
|
+
*/
|
|
60
|
+
export declare function sampleTransport(options: SampleTransportOptions): Transport;
|
|
61
|
+
/**
|
|
62
|
+
* Strips sensitive fields from `data` before forwarding to a downstream transport.
|
|
63
|
+
* Redaction is applied recursively at any depth, including inside arrays.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* redactTransport({
|
|
67
|
+
* keys: ['password', 'token', 'ssn'],
|
|
68
|
+
* replacement: '[REDACTED]',
|
|
69
|
+
* transport: remoteTransport({ handler }),
|
|
70
|
+
* })
|
|
71
|
+
*/
|
|
72
|
+
export declare function redactTransport(options: RedactTransportOptions): Transport;
|
|
73
|
+
/**
|
|
74
|
+
* Fan-out: dispatches each entry to all provided transports independently.
|
|
75
|
+
* A throw in one transport does not prevent others from receiving the entry.
|
|
76
|
+
* Pass `onError` to observe individual transport failures; without it, errors are silently swallowed.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* createLogger({
|
|
80
|
+
* transports: [pipe(consoleTransport(), remoteTransport({ handler }))],
|
|
81
|
+
* })
|
|
82
|
+
*
|
|
83
|
+
* // With error observer:
|
|
84
|
+
* pipe({ onError: (err) => console.error('[pipe]', err) }, consoleTransport(), remoteTransport({ handler }))
|
|
85
|
+
*/
|
|
86
|
+
export declare function pipe(...transports: Transport[]): Transport;
|
|
87
|
+
export declare function pipe(options: PipeOptions, ...transports: Transport[]): Transport;
|
|
88
|
+
//# sourceMappingURL=transports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transports.d.ts","sourceRoot":"","sources":["../src/transports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,qBAAqB,EAErB,oBAAoB,EAEpB,WAAW,EACX,sBAAsB,EACtB,aAAa,EACb,sBAAsB,EACtB,sBAAsB,EACtB,SAAS,EACV,MAAM,SAAS,CAAC;AAMjB,YAAY,EAAE,aAAa,EAAE,CAAC;AAiB9B;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,SAAS,CAuB1E;AAkBD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,SAAS,CA6B3E;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,WAAW,CA8D1E;AAID;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,SAAS,CAS1E;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,SAAS,CA8C1E;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,IAAI,CAAC,GAAG,UAAU,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC;AAC5D,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,UAAU,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { isLevelEnabled as e } from "./types.js";
|
|
2
|
+
import { isUnsafeObjectKey as t } from "./_prototype.js";
|
|
3
|
+
import { warn as n } from "./_dev.js";
|
|
4
|
+
//#region src/transports.ts
|
|
5
|
+
function r() {
|
|
6
|
+
return typeof window > "u" ? globalThis.process?.env?.NODE_ENV === "production" ? "production" : "development" : "production";
|
|
7
|
+
}
|
|
8
|
+
function i(t) {
|
|
9
|
+
let { handler: i } = t, a = t.level ?? "debug", o = t.env ?? r(), s = t.onError ?? ((e) => n(`remote transport error: ${String(e)}`));
|
|
10
|
+
return (t) => {
|
|
11
|
+
if (!e(a, t.level)) return;
|
|
12
|
+
let n = {
|
|
13
|
+
data: Object.keys(t.data).length > 0 ? t.data : void 0,
|
|
14
|
+
env: o,
|
|
15
|
+
level: t.level,
|
|
16
|
+
message: t.message,
|
|
17
|
+
namespace: t.namespace || void 0,
|
|
18
|
+
timestamp: t.timestamp.toISOString()
|
|
19
|
+
};
|
|
20
|
+
Promise.resolve().then(() => i(t.level, n)).catch((e) => s(e, n));
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function a() {
|
|
24
|
+
let e = /* @__PURE__ */ new WeakSet();
|
|
25
|
+
return (t, n) => {
|
|
26
|
+
if (typeof n == "object" && n) {
|
|
27
|
+
if (e.has(n)) return "[Circular]";
|
|
28
|
+
e.add(n);
|
|
29
|
+
}
|
|
30
|
+
return n;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function o(t = {}) {
|
|
34
|
+
let n = t.level ?? "debug", r = t.fields ?? {}, i = r.level ?? "level", o = r.time ?? "time", s = r.ns ?? "ns", c = r.msg ?? "msg", l = t.safe ?? !1, u = t.output ?? ((e) => {
|
|
35
|
+
globalThis.process?.stdout?.write(e + "\n");
|
|
36
|
+
});
|
|
37
|
+
return (t) => {
|
|
38
|
+
if (!e(n, t.level)) return;
|
|
39
|
+
let r = {
|
|
40
|
+
...t.data,
|
|
41
|
+
[i]: t.level,
|
|
42
|
+
[o]: t.timestamp.toISOString(),
|
|
43
|
+
...t.namespace && { [s]: t.namespace },
|
|
44
|
+
...t.message !== void 0 && { [c]: t.message }
|
|
45
|
+
};
|
|
46
|
+
u(JSON.stringify(r, l ? a() : void 0));
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function s(t) {
|
|
50
|
+
let n = t.level ?? "debug", r = t.maxSize ?? 50, i = t.maxBuffer, a = t.interval ?? 5e3, o = [], s, c = !1;
|
|
51
|
+
function l() {
|
|
52
|
+
if (o.length === 0) return;
|
|
53
|
+
let e = o;
|
|
54
|
+
o = [], Promise.resolve().then(() => t.onFlush(e)).catch((n) => t.onFlushError?.(e, n));
|
|
55
|
+
}
|
|
56
|
+
let u = (t) => {
|
|
57
|
+
c || e(n, t.level) && (s ||= setInterval(l, a), o.push(t), i !== void 0 && o.length > i && (o = o.slice(o.length - i)), o.length >= r && l());
|
|
58
|
+
}, d = {
|
|
59
|
+
dispose() {
|
|
60
|
+
c || (c = !0, s &&= (clearInterval(s), void 0), l());
|
|
61
|
+
},
|
|
62
|
+
get disposed() {
|
|
63
|
+
return c;
|
|
64
|
+
},
|
|
65
|
+
flush: l,
|
|
66
|
+
[Symbol.dispose]() {
|
|
67
|
+
d.dispose();
|
|
68
|
+
},
|
|
69
|
+
transport: u
|
|
70
|
+
};
|
|
71
|
+
return d;
|
|
72
|
+
}
|
|
73
|
+
function c(t) {
|
|
74
|
+
let { rate: n, transport: r } = t, i = t.level ?? "debug";
|
|
75
|
+
return (t) => {
|
|
76
|
+
e(i, t.level) && Math.random() < n && r(t);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function l(e) {
|
|
80
|
+
let { keys: r, maxDepth: i = 20, replacement: a = "[REDACTED]", transport: o } = e;
|
|
81
|
+
for (let e of r) e.includes(".") && n(`redactTransport: key "${e}" contains a dot. Dot-path notation is not supported — use the plain field name (e.g. 'password') to redact at any depth.`);
|
|
82
|
+
let s = new Set(r);
|
|
83
|
+
function c(e, t = 0) {
|
|
84
|
+
return t > i ? (n(`redactTransport: object nesting depth exceeded ${i} — redaction truncated at this level. Sensitive fields below depth ${i} may not be redacted.`), e) : Array.isArray(e) ? e.map((e) => c(e, t + 1)) : typeof e == "object" && e ? l(e, t + 1) : e;
|
|
85
|
+
}
|
|
86
|
+
function l(e, n = 0) {
|
|
87
|
+
let r = {};
|
|
88
|
+
for (let [i, o] of Object.entries(e)) t(i) || (r[i] = s.has(i) ? a : c(o, n));
|
|
89
|
+
return r;
|
|
90
|
+
}
|
|
91
|
+
return (e) => {
|
|
92
|
+
o({
|
|
93
|
+
...e,
|
|
94
|
+
data: l(e.data)
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function u(e, ...t) {
|
|
99
|
+
let n, r;
|
|
100
|
+
return typeof e == "function" ? (n = {}, r = [e, ...t]) : (n = e, r = t), (e) => {
|
|
101
|
+
for (let t of r) try {
|
|
102
|
+
t(e);
|
|
103
|
+
} catch (t) {
|
|
104
|
+
n.onError?.(t, e);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
export { s as batchTransport, o as jsonTransport, u as pipe, l as redactTransport, i as remoteTransport, c as sampleTransport };
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=transports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transports.js","names":[],"sources":["../src/transports.ts"],"sourcesContent":["import type {\n BatchHandle,\n BatchTransportOptions,\n Bindings,\n JsonTransportOptions,\n LogEntry,\n PipeOptions,\n RedactTransportOptions,\n RemoteLogData,\n RemoteTransportOptions,\n SampleTransportOptions,\n Transport,\n} from './types';\n\nimport { warn } from './_dev';\nimport { isUnsafeObjectKey } from './_prototype';\nimport { isLevelEnabled } from './types';\n\nexport type { RemoteLogData };\n\n/* ─── Environment detection ─── */\n\nfunction detectEnv(): 'development' | 'production' {\n if (typeof window === 'undefined') {\n return (globalThis as Record<string, unknown> & { process?: { env?: { NODE_ENV?: string } } }).process?.env\n ?.NODE_ENV === 'production'\n ? 'production'\n : 'development';\n }\n\n return (import.meta as ImportMeta & { env?: { PROD?: boolean } }).env?.PROD ? 'production' : 'development';\n}\n\n/* ─── remoteTransport ─── */\n\n/**\n * Forwards log entries asynchronously to a remote handler.\n * The handler is fire-and-forget. Use onError to observe delivery failures.\n * Console and remote thresholds are fully independent.\n *\n * **Security note:** serialized `Error` objects include the full `stack` trace,\n * which may expose internal file paths. Use `redactTransport` or a middleware\n * to strip `err.stack` before forwarding in production if this is a concern.\n *\n * @example\n * remoteTransport({\n * handler: async (type, data) => {\n * await fetch('/api/logs', { body: JSON.stringify(data), method: 'POST' });\n * },\n * level: 'error',\n * })\n */\nexport function remoteTransport(options: RemoteTransportOptions): Transport {\n const { handler } = options;\n const level = options.level ?? 'debug';\n const env = options.env ?? detectEnv();\n const onError = options.onError ?? ((err: unknown) => warn(`remote transport error: ${String(err)}`));\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n const hasData = Object.keys(entry.data).length > 0;\n const payload: RemoteLogData = {\n data: hasData ? entry.data : undefined,\n env,\n level: entry.level,\n message: entry.message,\n namespace: entry.namespace || undefined,\n timestamp: entry.timestamp.toISOString(),\n };\n\n Promise.resolve()\n .then(() => handler(entry.level, payload))\n .catch((err: unknown) => onError(err, payload));\n };\n}\n\n/* ─── jsonTransport ─── */\n\nfunction makeCircularReplacer(): (_key: string, value: unknown) => unknown {\n const seen = new WeakSet();\n\n return (_key: string, value: unknown): unknown => {\n if (typeof value === 'object' && value !== null) {\n if (seen.has(value)) return '[Circular]';\n\n seen.add(value);\n }\n\n return value;\n };\n}\n\n/**\n * Writes newline-delimited JSON (NDJSON) to stdout or a custom output function.\n * Useful for structured log aggregation pipelines in Node.js (ELK, Datadog, etc.).\n *\n * @example\n * jsonTransport({ level: 'info' })\n * jsonTransport({ safe: true }) // handles circular references gracefully\n * jsonTransport({ output: (line) => fs.appendFileSync('app.log', line + '\\n') })\n */\nexport function jsonTransport(options: JsonTransportOptions = {}): Transport {\n const level = options.level ?? 'debug';\n const f = options.fields ?? {};\n const fLevel = f.level ?? 'level';\n const fTime = f.time ?? 'time';\n const fNs = f.ns ?? 'ns';\n const fMsg = f.msg ?? 'msg';\n const safe = options.safe ?? false;\n const output =\n options.output ??\n ((line: string) => {\n (\n globalThis as Record<string, unknown> & { process?: { stdout?: { write: (s: string) => void } } }\n ).process?.stdout?.write(line + '\\n');\n });\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n const record: Record<string, unknown> = {\n ...entry.data,\n [fLevel]: entry.level,\n [fTime]: entry.timestamp.toISOString(),\n ...(entry.namespace && { [fNs]: entry.namespace }),\n ...(entry.message !== undefined && { [fMsg]: entry.message }),\n };\n\n output(JSON.stringify(record, safe ? makeCircularReplacer() : undefined));\n };\n}\n\n/* ─── batchTransport ─── */\n\n/**\n * Buffers log entries and flushes them in batches, reducing I/O overhead.\n * Flushes when the buffer reaches maxSize or after the interval elapses.\n *\n * Returns a `BatchHandle` with `.transport`, `.flush()`, and `.dispose()` methods:\n * - `.transport` — pass to `createLogger({ transports: [handle.transport] })`\n * - `.flush()` — immediately send buffered entries without stopping the timer\n * - `.dispose()` — stop the interval and flush remaining entries (call on shutdown)\n *\n * Use `onFlushError` to observe or retry failed flushes (e.g. dead-letter queue).\n *\n * @example\n * const batch = batchTransport({\n * onFlush: (entries) => sendToCollector(entries),\n * onFlushError: (entries, err) => deadLetter.push(entries),\n * interval: 10_000,\n * maxSize: 100,\n * });\n * createLogger({ transports: [batch.transport] });\n * batch.dispose(); // call on app shutdown\n */\nexport function batchTransport(options: BatchTransportOptions): BatchHandle {\n const level = options.level ?? 'debug';\n const maxSize = options.maxSize ?? 50;\n const maxBuffer = options.maxBuffer;\n const interval = options.interval ?? 5000;\n\n let buffer: LogEntry[] = [];\n let timer: ReturnType<typeof setInterval> | undefined;\n let batchDisposed = false;\n\n function flush(): void {\n if (buffer.length === 0) return;\n\n const entries = buffer;\n\n buffer = [];\n\n Promise.resolve()\n .then(() => options.onFlush(entries))\n .catch((err: unknown) => options.onFlushError?.(entries, err));\n }\n\n const transportFn: Transport = (entry: LogEntry): void => {\n if (batchDisposed) return;\n\n if (!isLevelEnabled(level, entry.level)) return;\n\n if (!timer) timer = setInterval(flush, interval);\n\n buffer.push(entry);\n\n if (maxBuffer !== undefined && buffer.length > maxBuffer) {\n buffer = buffer.slice(buffer.length - maxBuffer);\n }\n\n if (buffer.length >= maxSize) flush();\n };\n\n const handle: BatchHandle = {\n dispose(): void {\n if (batchDisposed) return;\n\n batchDisposed = true;\n\n if (timer) {\n clearInterval(timer);\n timer = undefined;\n }\n\n flush();\n },\n get disposed(): boolean {\n return batchDisposed;\n },\n flush,\n [Symbol.dispose](): void {\n handle.dispose();\n },\n transport: transportFn,\n };\n\n return handle;\n}\n\n/* ─── sampleTransport ─── */\n\n/**\n * Probabilistically forwards entries to a downstream transport.\n * Useful for reducing volume of high-frequency debug logs in production.\n *\n * @example\n * sampleTransport({ rate: 0.1, transport: remoteTransport({ handler }) })\n */\nexport function sampleTransport(options: SampleTransportOptions): Transport {\n const { rate, transport } = options;\n const level = options.level ?? 'debug';\n\n return (entry: LogEntry): void => {\n if (!isLevelEnabled(level, entry.level)) return;\n\n if (Math.random() < rate) transport(entry);\n };\n}\n\n/* ─── redactTransport ─── */\n\n/**\n * Strips sensitive fields from `data` before forwarding to a downstream transport.\n * Redaction is applied recursively at any depth, including inside arrays.\n *\n * @example\n * redactTransport({\n * keys: ['password', 'token', 'ssn'],\n * replacement: '[REDACTED]',\n * transport: remoteTransport({ handler }),\n * })\n */\nexport function redactTransport(options: RedactTransportOptions): Transport {\n const { keys, maxDepth = 20, replacement = '[REDACTED]', transport } = options;\n\n for (const key of keys) {\n if (key.includes('.')) {\n warn(\n `redactTransport: key \"${key}\" contains a dot. Dot-path notation is not supported — use the plain field name (e.g. 'password') to redact at any depth.`,\n );\n }\n }\n\n const keySet = new Set(keys);\n\n function redactValue(v: unknown, depth = 0): unknown {\n if (depth > maxDepth) {\n warn(\n `redactTransport: object nesting depth exceeded ${maxDepth} — redaction truncated at this level. Sensitive fields below depth ${maxDepth} may not be redacted.`,\n );\n\n return v;\n }\n\n if (Array.isArray(v)) return v.map((item) => redactValue(item, depth + 1));\n\n if (typeof v === 'object' && v !== null) return redactObject(v as Bindings, depth + 1);\n\n return v;\n }\n\n function redactObject(obj: Bindings, depth = 0): Bindings {\n const result: Bindings = {};\n\n for (const [k, v] of Object.entries(obj)) {\n // Guard against a `__proto__`/`constructor`/`prototype` field name hijacking result's own\n // prototype via the bracket-assignment accessor — see _prototype.ts.\n if (isUnsafeObjectKey(k)) continue;\n\n result[k] = keySet.has(k) ? replacement : redactValue(v, depth);\n }\n\n return result;\n }\n\n return (entry: LogEntry): void => {\n transport({ ...entry, data: redactObject(entry.data as Bindings) });\n };\n}\n\n/* ─── pipe — fault-tolerant fan-out ─── */\n\n/**\n * Fan-out: dispatches each entry to all provided transports independently.\n * A throw in one transport does not prevent others from receiving the entry.\n * Pass `onError` to observe individual transport failures; without it, errors are silently swallowed.\n *\n * @example\n * createLogger({\n * transports: [pipe(consoleTransport(), remoteTransport({ handler }))],\n * })\n *\n * // With error observer:\n * pipe({ onError: (err) => console.error('[pipe]', err) }, consoleTransport(), remoteTransport({ handler }))\n */\nexport function pipe(...transports: Transport[]): Transport;\nexport function pipe(options: PipeOptions, ...transports: Transport[]): Transport;\nexport function pipe(optionsOrTransport: PipeOptions | Transport, ...rest: Transport[]): Transport {\n let opts: PipeOptions;\n let transports: Transport[];\n\n if (typeof optionsOrTransport === 'function') {\n opts = {};\n transports = [optionsOrTransport, ...rest];\n } else {\n opts = optionsOrTransport;\n transports = rest;\n }\n\n return (entry: LogEntry): void => {\n for (const t of transports) {\n try {\n t(entry);\n } catch (err) {\n opts.onError?.(err, entry);\n }\n }\n };\n}\n"],"mappings":"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"}
|
package/dist/types.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/* ─── Log levels ─── */\n\nexport type LogType = 'debug' | 'error' | 'fatal' | 'info' | 'warn';\nexport type LogLevel = LogType | 'off';\n\n/** Numeric priority for each level. Lower = more verbose. Exported for transport authors. */\nexport const PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n error: 3,\n fatal: 4,\n info: 1,\n off: 5,\n warn: 2,\n};\n\n/** Returns true if `level` passes the `threshold`. Returns false when `level` is 'off'. Exported for transport/middleware authors. */\nexport function isLevelEnabled(threshold: LogLevel, level: LogLevel): boolean {\n if (level === 'off') return false;\n\n return PRIORITY[threshold] <= PRIORITY[level];\n}\n\n/* ─── Bindings ─── */\n\nexport type Bindings = Record<string, unknown>;\n\n/* ─── Log entry ─── */\n\n/**\n * The structured record produced by every log call and dispatched to all transports.\n * `data` is the merged result of pinned bindings and per-call context — transports\n * receive a single flat object and do not need to merge anything themselves.\n * Any `Error` instances — whether from a pinned binding (`bindings`/`withBindings()`) or\n * per-call context — are automatically serialized to `{ message, name, stack }`.\n * **Shallow only** — an `Error` nested inside a plain object (e.g. `{ meta: { err } }`) is left as-is;\n * only top-level fields of `data` are checked.\n */\nexport type LogEntry = {\n /**\n * Merged structured data: pinned bindings overlaid with per-call context.\n * Already shallow-copied and immutable — do not mutate.\n */\n data: Readonly<Bindings>;\n level: LogType;\n message?: string;\n namespace: string;\n /** Exact moment of the log call — shared across all transports for the same entry. */\n timestamp: Date;\n};\n\n/* ─── Transport ─── */\n\n/**\n * A transport receives a log entry and is responsible for its own delivery and formatting.\n * If a transport throws, the logger catches it, reports it via a dev-only warning (wrapped in\n * `RuneTransportError`), and continues dispatching the entry to remaining transports — a single\n * misbehaving transport can never crash the caller of `log.info()`/etc. or block its siblings.\n */\nexport type Transport = (entry: LogEntry) => void;\n\n/**\n * Middleware function that transforms or filters log entries before dispatch. Return null to drop the entry.\n * If middleware throws, the logger catches it, reports it via a dev-only warning, and drops the entry\n * (no transports run for it) rather than crashing the caller.\n */\nexport type LogMiddleware = (entry: LogEntry) => LogEntry | null;\n\n/* ─── Transport option types ─── */\n\nexport type RemoteLogData = {\n data?: Bindings;\n env: 'development' | 'production';\n level: LogType;\n message?: string;\n namespace?: string;\n timestamp: string;\n};\n\nexport type RemoteTransportOptions = {\n /** Override the detected runtime environment. Default: auto-detected. */\n env?: 'development' | 'production';\n /** Remote delivery handler — receives the log type and structured payload. */\n handler: (type: LogType, data: RemoteLogData) => void;\n /** Minimum level to forward. Default: 'debug'. */\n level?: LogLevel;\n /**\n * Called when the handler throws or rejects.\n * The async error path is separate from any synchronous errors in the emit call stack.\n * Default: a dev-only `console.warn` (gated by `__RUNE_PROD__`). In production builds,\n * unhandled remote transport errors are silently swallowed — pass an explicit `onError`\n * if you need delivery-failure observability in production.\n */\n onError?: (error: unknown, data: RemoteLogData) => void;\n};\n\nexport type JsonTransportOptions = {\n /**\n * Custom output field names. Useful for adapting to aggregator conventions\n * (Datadog, ELK, Loki, etc.).\n *\n * @example\n * jsonTransport({ fields: { level: 'severity', time: '@timestamp', msg: 'message' } })\n */\n fields?: {\n level?: string;\n msg?: string;\n ns?: string;\n time?: string;\n };\n /** Minimum level to output. Default: 'debug'. */\n level?: LogLevel;\n /** Custom output function. Default: process.stdout.write. */\n output?: (line: string) => void;\n /**\n * Replace circular references with `'[Circular]'` instead of throwing a TypeError.\n * Useful in environments where log payloads may contain complex object graphs.\n * Default: false.\n */\n safe?: boolean;\n};\n\n/** Handle returned by `batchTransport()`. Pass `handle.transport` to `createLogger({ transports })`. */\nexport type BatchHandle = {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose]: () => void;\n /** Stop the interval timer and flush remaining entries. Call on shutdown. Idempotent. */\n dispose: () => void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Immediately flush buffered entries to the downstream handler without stopping the timer. */\n flush: () => void;\n /** The transport function to pass to `createLogger({ transports: [handle.transport] })`. */\n transport: Transport;\n};\n\nexport type BatchTransportOptions = {\n /** Flush interval in milliseconds. Default: 5000. */\n interval?: number;\n /** Minimum level to buffer. Default: 'debug'. */\n level?: LogLevel;\n /**\n * Hard limit on the in-memory buffer size. When the buffer exceeds this value,\n * the oldest entries are dropped to prevent unbounded memory growth.\n * Unlike `maxSize`, this does NOT trigger a flush — it silently drops.\n * Default: unbounded.\n */\n maxBuffer?: number;\n /** Maximum buffer size before an early flush. Default: 50. */\n maxSize?: number;\n /**\n * Callback to receive flushed batches. May return a Promise — async rejections\n * are forwarded to `onFlushError` in addition to synchronous throws.\n */\n onFlush: (entries: LogEntry[]) => void | Promise<void>;\n /**\n * Called when onFlush throws synchronously or rejects asynchronously.\n * Allows retry/dead-letter logic. Default: silent.\n */\n onFlushError?: (entries: LogEntry[], error: unknown) => void;\n};\n\nexport type PipeOptions = {\n /**\n * Called when one of the piped transports throws.\n * Receives the thrown error and the log entry that triggered it.\n * Default: silent (errors are swallowed to protect remaining transports).\n */\n onError?: (error: unknown, entry: LogEntry) => void;\n};\n\nexport type SampleTransportOptions = {\n /** Minimum level to sample. Default: 'debug'. */\n level?: LogLevel;\n /** Fraction of entries to forward (0–1). */\n rate: number;\n /** Downstream transport to receive sampled entries. */\n transport: Transport;\n};\n\nexport type RedactTransportOptions = {\n /**\n * Field names to replace at any depth in `data`.\n * Matched by exact field name — dot-path notation (e.g. `'user.password'`) is NOT supported.\n * A key like `'password'` will redact every field named `'password'` at any nesting level.\n */\n keys: string[];\n /**\n * Maximum object nesting depth to traverse during redaction.\n * Objects deeper than this limit are returned as-is (not redacted).\n * A dev-only warning is emitted when the cap is hit.\n * Default: 20.\n * @security In production builds, the depth warning is suppressed — deeply-nested sensitive\n * fields beyond `maxDepth` will pass through unredacted without any indication. Ensure that\n * sensitive payloads are not nested beyond this limit, or lower `maxDepth` as needed.\n */\n maxDepth?: number;\n /** Replacement value for redacted fields. Default: '[REDACTED]'. */\n replacement?: string;\n /** Downstream transport to receive the redacted entry. */\n transport: Transport;\n};\n\n/* ─── Logger options ─── */\n\nexport type RuneOptions = {\n /** Initial pinned bindings for this logger instance. `Error` values are auto-serialized, same as per-call context. */\n bindings?: Bindings;\n /** Minimum log level for this logger instance. Default: 'debug'. */\n logLevel?: LogLevel;\n /** Middleware pipeline applied to every entry before dispatch to transports. */\n middleware?: LogMiddleware[];\n /**\n * Namespace for this logger. When passed to `child()`, it is automatically\n * dot-joined to the parent namespace (e.g. parent `'api'` + child `'auth'` → `'api.auth'`).\n */\n namespace?: string;\n /**\n * Transport pipeline. Each transport receives every entry that passes the level threshold.\n * Default: [consoleTransport()].\n */\n transports?: Transport[];\n};\n\n/* ─── Log method ─── */\n\n/**\n * Signature shared by all five log-level methods.\n *\n * - `log.info('message')` — string-only, most common.\n * - `log.info({ ...fields }, 'message')` — structured context + optional message.\n * `Error` values in `fields` are automatically serialized to `{ message, name, stack }`.\n * Serialization is shallow only — an `Error` nested inside a nested object is left as-is.\n * - `log.error(err, { ...fields }, 'message')` — Error first, then optional context + message.\n * Shorthand for the pattern where an Error is the primary subject of the log call.\n * The context object may be omitted entirely: `log.error(err, 'message')`.\n *\n * @example\n * log.error({ err: new Error('timeout'), requestId }, 'request failed')\n * log.error(new Error('timeout'), { requestId }, 'request failed')\n */\nexport type LogMethod = {\n (message: string): void;\n (error: Error, message?: string): void;\n (error: Error, context: Bindings, message?: string): void;\n (context: Bindings, message?: string): void;\n};\n\n/* ─── Logger interface ─── */\n\nexport type Logger = {\n /** Delegates to `dispose()`. Enables `using` declarations. */\n [Symbol.dispose]: () => void;\n /** Snapshot of currently pinned bindings. */\n readonly bindings: Readonly<Bindings>;\n /** Create a child logger with config overrides. Inherits all config and bindings by default. */\n child: (overrides?: RuneOptions) => Logger;\n debug: LogMethod;\n /** `AbortSignal` aborted when `dispose()` is called. Use to tie external lifetimes to this logger. */\n readonly disposalSignal: AbortSignal;\n /**\n * Marks the logger as disposed — all subsequent log calls become no-ops.\n * Aborts `disposalSignal`. Does NOT auto-discover or dispose batch transports;\n * hold a direct reference to `batchTransport` and call its `dispose()` on shutdown.\n * Idempotent — safe to call multiple times.\n */\n dispose: () => void;\n /** `true` after `dispose()` has been called. */\n readonly disposed: boolean;\n /** Returns true if entries at this level will pass the configured threshold. */\n enabled: (type: LogLevel) => boolean;\n error: LogMethod;\n fatal: LogMethod;\n /**\n * Wrap a callback in a console group, closing it even on throw/reject.\n * Pass a `level` to gate the group header on the configured log threshold\n * (e.g. `level: 'debug'` suppresses the group when `logLevel` is above `'debug'`).\n * Default: always renders (unless `logLevel` is `'off'`).\n */\n group: <T>(label: string, fn: () => T, level?: LogType) => T;\n /**\n * Same as `group`, using `console.groupCollapsed`.\n * Pass a `level` to gate the group on the configured log threshold.\n */\n groupCollapsed: <T>(label: string, fn: () => T, level?: LogType) => T;\n info: LogMethod;\n /** Active log level for this logger instance. */\n readonly logLevel: LogLevel;\n /** Middleware pipeline applied before dispatch. */\n readonly middleware: readonly LogMiddleware[];\n /** Namespace string for this logger instance. */\n readonly namespace: string;\n /**\n * Measure execution time of `fn` and emit a structured log entry.\n * The entry message is `label`; `data` contains `{ duration_ms }` (rounded to 2 dp).\n * When `fn` throws or rejects, `data` also includes `{ err }` with the serialized error.\n * @param label - Human-readable description of the operation.\n * @param fn - Synchronous or async function to time.\n * @param level - Log level for the timing entry. Default: `'debug'`.\n */\n time: <T>(label: string, fn: () => T, level?: LogType) => T;\n /** Transport pipeline for this logger instance. */\n readonly transports: readonly Transport[];\n /**\n * Add a middleware function to the pipeline. Returns a **new** logger — the original is unchanged.\n * Discarding the return value is a common mistake: always assign the result.\n * @example\n * const log = baseLog.use(tracingMiddleware); // ✓ keep the result\n */\n use: (middleware: LogMiddleware) => Logger;\n warn: LogMethod;\n /**\n * Derive a child logger with additional pinned bindings.\n * The returned logger is fully independent — disposing it does not affect the parent,\n * and disposing the parent does not affect child loggers.\n */\n withBindings: (bindings: Bindings) => Logger;\n};\n"],"mappings":"AAMA,IAAa,EAAqC,CAChD,MAAO,EACP,MAAO,EACP,MAAO,EACP,KAAM,EACN,IAAK,EACL,KAAM,CACR,EAGA,SAAgB,EAAe,EAAqB,EAA0B,CAG5E,OAFI,IAAU,MAAc,GAErB,EAAS,IAAc,EAAS,EACzC"}
|