loggily 0.6.0 → 0.6.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.
@@ -1 +0,0 @@
1
- {"version":3,"file":"worker.mjs","names":[],"sources":["../src/worker.ts"],"sourcesContent":["/**\n * Worker Thread Logger/Console Forwarding\n *\n * Provides utilities to forward loggily and console.* output from worker threads\n * to the main thread, ensuring proper integration with DEBUG_LOG and log files.\n *\n * ## Full Logger Forwarding (Recommended)\n *\n * @example Worker side:\n * ```typescript\n * import { createWorkerLogger } from \"loggily/worker\"\n * const log = createWorkerLogger(postMessage, \"km:worker:parse\")\n *\n * log.info(\"processing\", { file: \"test.md\" })\n * {\n * using span = log.span(\"parse\")\n * // ... work ...\n * span.spanData.lines = 100\n * }\n * ```\n *\n * @example Main thread side:\n * ```typescript\n * import { createWorkerLogHandler } from \"loggily/worker\"\n *\n * const handleLog = createWorkerLogHandler()\n * worker.onmessage = (e) => {\n * if (e.data.type === \"log\" || e.data.type === \"span\") handleLog(e.data)\n * }\n * ```\n *\n * ## Console Forwarding (Simple)\n *\n * @example Worker side:\n * ```typescript\n * import { forwardConsole } from \"loggily/worker\"\n * forwardConsole(postMessage)\n *\n * console.log(\"message\") // Forwarded to main thread\n * ```\n */\n\nimport {\n createLogger,\n createSpanDataProxy,\n enableSpans,\n writeSpan,\n type ConditionalLogger,\n type LazyMessage,\n type Logger,\n type SpanLogger,\n type SpanData,\n} from \"./index.ts\"\n\n// ============ Message Protocol ============\n\n/** Message sent from worker to main thread for console output */\nexport interface WorkerConsoleMessage {\n type: \"console\"\n level: \"log\" | \"debug\" | \"info\" | \"warn\" | \"error\" | \"trace\"\n namespace?: string\n args: unknown[]\n timestamp: number\n}\n\n/** Message sent from worker to main thread for structured log output */\nexport interface WorkerLogMessage {\n type: \"log\"\n level: \"trace\" | \"debug\" | \"info\" | \"warn\" | \"error\"\n namespace: string\n message: string\n data?: Record<string, unknown>\n timestamp: number\n}\n\n/** Message sent from worker to main thread for span events */\nexport interface WorkerSpanMessage {\n type: \"span\"\n event: \"start\" | \"end\"\n namespace: string\n spanId: string\n traceId: string\n parentId: string | null\n startTime: number\n endTime?: number\n duration?: number\n props: Record<string, unknown>\n spanData: Record<string, unknown>\n timestamp: number\n}\n\n/** Union type for all worker messages */\nexport type WorkerMessage = WorkerConsoleMessage | WorkerLogMessage | WorkerSpanMessage\n\n/** Type guard for WorkerConsoleMessage */\nexport function isWorkerConsoleMessage(msg: unknown): msg is WorkerConsoleMessage {\n return (\n typeof msg === \"object\" &&\n (msg as WorkerConsoleMessage)?.type === \"console\" &&\n typeof (msg as WorkerConsoleMessage).level === \"string\" &&\n Array.isArray((msg as WorkerConsoleMessage).args)\n )\n}\n\n/** Type guard for WorkerLogMessage */\nexport function isWorkerLogMessage(msg: unknown): msg is WorkerLogMessage {\n return (\n typeof msg === \"object\" &&\n (msg as WorkerLogMessage)?.type === \"log\" &&\n typeof (msg as WorkerLogMessage).level === \"string\" &&\n typeof (msg as WorkerLogMessage).namespace === \"string\"\n )\n}\n\n/** Type guard for WorkerSpanMessage */\nexport function isWorkerSpanMessage(msg: unknown): msg is WorkerSpanMessage {\n return (\n typeof msg === \"object\" &&\n (msg as WorkerSpanMessage)?.type === \"span\" &&\n typeof (msg as WorkerSpanMessage).event === \"string\"\n )\n}\n\n/** Type guard for any worker message */\nexport function isWorkerMessage(msg: unknown): msg is WorkerMessage {\n return isWorkerConsoleMessage(msg) || isWorkerLogMessage(msg) || isWorkerSpanMessage(msg)\n}\n\n// ============ Worker Side ============\n\ntype PostMessageFn = (message: WorkerConsoleMessage) => void\n\n/** Store original console methods for restoration */\nlet originalConsole: typeof console | null = null\n\n/**\n * Serialize a value for transmission via postMessage.\n * Handles non-serializable values like functions and circular references.\n */\nfunction serializeArg(arg: unknown, depth = 0): unknown {\n // Prevent infinite recursion\n if (depth > 5) return \"[max depth]\"\n\n if (arg === null || arg === undefined) return arg\n if (typeof arg === \"function\") return `[Function: ${arg.name || \"anonymous\"}]`\n if (typeof arg === \"symbol\") return arg.toString()\n if (typeof arg === \"bigint\") return arg.toString() + \"n\"\n\n if (arg instanceof Error) {\n return {\n name: arg.name,\n message: arg.message,\n stack: arg.stack,\n }\n }\n\n if (Array.isArray(arg)) {\n return arg.map((v) => serializeArg(v, depth + 1))\n }\n\n if (typeof arg === \"object\") {\n try {\n // Try structured clone first (handles most cases)\n structuredClone(arg)\n return arg\n } catch {\n // Fall back to manual serialization\n const result: Record<string, unknown> = {}\n const seen = new Set<object>()\n seen.add(arg)\n\n for (const [key, value] of Object.entries(arg)) {\n if (typeof value === \"object\" && value !== null && seen.has(value)) {\n result[key] = \"[Circular]\"\n } else {\n result[key] = serializeArg(value, depth + 1)\n }\n }\n return result\n }\n }\n\n return arg\n}\n\n/**\n * Forward console.* calls from worker to main thread.\n *\n * Monkey-patches console methods to send messages via postMessage.\n * Call this at the start of your worker script.\n *\n * @param postMessage - The worker's postMessage function\n * @param namespace - Optional namespace for log messages (e.g., \"km:worker:parse\")\n *\n * @example\n * ```typescript\n * // At top of worker file:\n * import { forwardConsole } from \"loggily/worker\"\n * forwardConsole(postMessage, \"km:worker:parse\")\n *\n * // Now all console.* calls are forwarded:\n * console.log(\"processing\", { file: \"test.md\" })\n * console.error(new Error(\"failed\"))\n * ```\n */\nexport function forwardConsole(postMessage: PostMessageFn, namespace?: string): void {\n // Store original console for restoration\n if (!originalConsole) {\n originalConsole = { ...console }\n }\n\n const levels = [\"log\", \"debug\", \"info\", \"warn\", \"error\", \"trace\"] as const\n\n for (const level of levels) {\n console[level] = (...args: unknown[]) => {\n const serializedArgs = args.map((arg) => serializeArg(arg))\n\n try {\n postMessage({\n type: \"console\",\n level,\n namespace,\n args: serializedArgs,\n timestamp: Date.now(),\n })\n } catch {\n // postMessage might fail if worker is shutting down\n // Fall back to original console\n originalConsole?.[level](...args)\n }\n }\n }\n}\n\n/**\n * Restore original console methods.\n * Call this if you need to disable console forwarding.\n */\nexport function restoreConsole(): void {\n if (originalConsole) {\n Object.assign(console, originalConsole)\n originalConsole = null\n }\n}\n\n// ============ Worker Logger (Full API) ============\n\ntype PostMessageAnyFn = (message: WorkerMessage) => void\n\nlet workerSpanIdCounter = 0\nlet workerTraceIdCounter = 0\n\nfunction generateWorkerSpanId(): string {\n return `wsp_${(++workerSpanIdCounter).toString(36)}`\n}\n\nfunction generateWorkerTraceId(): string {\n return `wtr_${(++workerTraceIdCounter).toString(36)}`\n}\n\n/** Reset worker ID counters (for testing) */\nexport function resetWorkerIds(): void {\n workerSpanIdCounter = 0\n workerTraceIdCounter = 0\n}\n\ninterface WorkerLoggerOptions {\n /** Parent span ID for nested spans */\n parentSpanId?: string | null\n /** Trace ID for distributed tracing */\n traceId?: string | null\n}\n\n/**\n * Create a logger instance for use in a worker thread.\n *\n * All log calls and span events are forwarded to the main thread via postMessage.\n * The main thread should use createWorkerLogHandler to process these messages.\n *\n * @param postMessage - The worker's postMessage function\n * @param namespace - Logger namespace (e.g., \"km:worker:parse\")\n * @param props - Optional initial props\n * @param options - Optional configuration\n *\n * @example\n * ```typescript\n * import { createWorkerLogger } from \"loggily/worker\"\n *\n * const log = createWorkerLogger(postMessage, \"km:worker:parse\")\n *\n * log.info(\"starting parse\", { file: \"test.md\" })\n *\n * {\n * using span = log.span(\"process\")\n * span.info(\"processing...\")\n * span.spanData.lineCount = 100\n * }\n * // Span end event automatically sent to main thread\n * ```\n */\nexport function createWorkerLogger(\n postMessage: PostMessageAnyFn,\n namespace: string,\n props: Record<string, unknown> = {},\n options: WorkerLoggerOptions = {},\n): Logger {\n const { parentSpanId = null, traceId = null } = options\n\n function log(\n level: \"trace\" | \"debug\" | \"info\" | \"warn\" | \"error\",\n message: LazyMessage,\n data?: Record<string, unknown>,\n ): void {\n const resolved = typeof message === \"function\" ? message() : message\n try {\n postMessage({\n type: \"log\",\n level,\n namespace,\n message: resolved,\n data: data ? { ...props, ...data } : Object.keys(props).length > 0 ? props : undefined,\n timestamp: Date.now(),\n })\n } catch (err) {\n // postMessage failed (e.g. uncloneable data) — send a diagnostic fallback\n try {\n postMessage({\n type: \"log\",\n level: \"error\",\n namespace,\n message: `postMessage failed for ${level} \"${resolved}\": ${err instanceof Error ? err.message : String(err)}`,\n timestamp: Date.now(),\n })\n } catch {\n // Worker might be shutting down — truly nothing we can do\n }\n }\n }\n\n function createSpan(spanNamespace?: string, spanProps?: Record<string, unknown>): SpanLogger {\n const fullNamespace = spanNamespace ? `${namespace}:${spanNamespace}` : namespace\n const mergedProps = { ...props, ...spanProps }\n const spanId = generateWorkerSpanId()\n const spanTraceId = traceId || generateWorkerTraceId()\n const startTime = Date.now()\n\n // Mutable span data that can be set by the user\n const customSpanData: Record<string, unknown> = {}\n\n // Send span start event\n try {\n postMessage({\n type: \"span\",\n event: \"start\",\n namespace: fullNamespace,\n spanId,\n traceId: spanTraceId,\n parentId: parentSpanId,\n startTime,\n props: mergedProps,\n spanData: {},\n timestamp: Date.now(),\n })\n } catch (err) {\n // postMessage failed (e.g. uncloneable props) — send a diagnostic fallback\n try {\n postMessage({\n type: \"log\",\n level: \"error\",\n namespace: fullNamespace,\n message: `postMessage failed for span start \"${fullNamespace}\": ${err instanceof Error ? err.message : String(err)}`,\n timestamp: Date.now(),\n })\n } catch {\n // Worker might be shutting down\n }\n }\n\n let ended = false\n\n const spanData: SpanData = createSpanDataProxy(\n () => ({\n id: spanId,\n traceId: spanTraceId,\n parentId: parentSpanId,\n startTime,\n endTime: ended ? Date.now() : null,\n duration: Date.now() - startTime,\n }),\n customSpanData,\n )\n\n function endSpan(): void {\n if (ended) return\n ended = true\n\n const endTime = Date.now()\n const duration = endTime - startTime\n\n try {\n postMessage({\n type: \"span\",\n event: \"end\",\n namespace: fullNamespace,\n spanId,\n traceId: spanTraceId,\n parentId: parentSpanId,\n startTime,\n endTime,\n duration,\n props: mergedProps,\n spanData: customSpanData,\n timestamp: Date.now(),\n })\n } catch (err) {\n // postMessage failed (e.g. uncloneable spanData) — send a diagnostic fallback\n try {\n postMessage({\n type: \"log\",\n level: \"error\",\n namespace: fullNamespace,\n message: `postMessage failed for span end \"${fullNamespace}\" (${duration}ms): ${err instanceof Error ? err.message : String(err)}`,\n timestamp: Date.now(),\n })\n } catch {\n // Worker might be shutting down\n }\n }\n }\n\n // Create child logger for the span\n const childLogger = createWorkerLogger(postMessage, fullNamespace, mergedProps, {\n parentSpanId: spanId,\n traceId: spanTraceId,\n })\n\n const spanLogger: SpanLogger = {\n ...childLogger,\n spanData,\n end: endSpan,\n [Symbol.dispose]: endSpan,\n }\n\n return spanLogger\n }\n\n const logger: Logger = {\n name: namespace,\n props: Object.freeze({ ...props }),\n spanData: null,\n\n trace: (msg, data) => log(\"trace\", msg, data),\n debug: (msg, data) => log(\"debug\", msg, data),\n info: (msg, data) => log(\"info\", msg, data),\n warn: (msg, data) => log(\"warn\", msg, data),\n error: (msgOrError: LazyMessage | Error, data?: Record<string, unknown>) => {\n if (msgOrError instanceof Error) {\n log(\"error\", msgOrError.message, {\n ...data,\n error_type: msgOrError.name,\n error_stack: msgOrError.stack,\n error_code: (msgOrError as NodeJS.ErrnoException).code,\n })\n } else {\n log(\"error\", msgOrError, data)\n }\n },\n\n logger(childNamespace?: string, childProps?: Record<string, unknown>): Logger {\n const fullNamespace = childNamespace ? `${namespace}:${childNamespace}` : namespace\n return createWorkerLogger(postMessage, fullNamespace, { ...props, ...childProps }, options)\n },\n\n span: createSpan,\n\n child(context: Record<string, unknown> | string): Logger {\n if (typeof context === \"string\") {\n return this.logger(context)\n }\n return createWorkerLogger(postMessage, namespace, { ...props, ...context }, options)\n },\n\n end(): void {\n // No-op for non-span logger\n },\n }\n\n return logger\n}\n\n// ============ Main Thread Side ============\n\nexport interface WorkerConsoleHandlerOptions {\n /** Default namespace if message doesn't include one */\n defaultNamespace?: string\n /** Custom logger to use (defaults to creating one with the namespace) */\n logger?: Logger\n}\n\n/** Safely stringify a value, handling circular refs and BigInt */\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n}\n\n/** Format console args into a message string and optional data object */\nfunction formatConsoleArgs(args: unknown[]): { message: string; data: Record<string, unknown> | undefined } {\n const message =\n args.length === 0\n ? \"\"\n : args.length === 1 && typeof args[0] === \"string\"\n ? args[0]\n : args.map((a) => (typeof a === \"string\" ? a : safeStringify(a))).join(\" \")\n\n const lastArg = args[args.length - 1]\n const data =\n args.length > 1 && typeof lastArg === \"object\" && lastArg !== null && !Array.isArray(lastArg)\n ? (lastArg as Record<string, unknown>)\n : undefined\n\n return { message, data }\n}\n\n/** Dispatch a message to a logger at the given console level */\nfunction dispatchToLogger(\n logger: ConditionalLogger,\n level: \"log\" | \"debug\" | \"info\" | \"warn\" | \"error\" | \"trace\",\n message: string,\n data?: Record<string, unknown>,\n): void {\n switch (level) {\n case \"trace\":\n logger.trace?.(message, data)\n break\n case \"debug\":\n logger.debug?.(message, data)\n break\n case \"info\":\n case \"log\":\n logger.info?.(message, data)\n break\n case \"warn\":\n logger.warn?.(message, data)\n break\n case \"error\":\n logger.error?.(message, data)\n break\n }\n}\n\n/**\n * Create a handler for worker console messages.\n *\n * Use this on the main thread to receive and output messages from workers.\n *\n * @param options - Handler options\n * @returns Handler function to call with worker messages\n *\n * @example\n * ```typescript\n * import { createWorkerConsoleHandler } from \"loggily/worker\"\n *\n * const handleConsole = createWorkerConsoleHandler({\n * defaultNamespace: \"km:worker:parse\"\n * })\n *\n * worker.onmessage = (e) => {\n * if (e.data.type === \"console\") {\n * handleConsole(e.data)\n * } else {\n * // Handle other message types\n * }\n * }\n * ```\n */\nexport function createWorkerConsoleHandler(\n options: WorkerConsoleHandlerOptions = {},\n): (message: WorkerConsoleMessage) => void {\n const loggers = new Map<string, ConditionalLogger>()\n\n function getLogger(namespace?: string): ConditionalLogger {\n const ns = namespace || options.defaultNamespace || \"worker\"\n\n let logger = loggers.get(ns)\n if (!logger) {\n logger = options.logger ? (options.logger as ConditionalLogger) : createLogger(ns)\n loggers.set(ns, logger)\n }\n return logger\n }\n\n return (message: WorkerConsoleMessage) => {\n const logger = getLogger(message.namespace)\n const { message: msg, data } = formatConsoleArgs(message.args)\n dispatchToLogger(logger, message.level, msg, data)\n }\n}\n\n// ============ Full Logger Handler ============\n\nexport interface WorkerLogHandlerOptions {\n /** Enable span output (default: uses spansAreEnabled()) */\n enableSpans?: boolean\n}\n\n/**\n * Create a handler for worker logger messages (logs and spans).\n *\n * Use this on the main thread to receive and output messages from workers\n * that use createWorkerLogger.\n *\n * @param options - Handler options\n * @returns Handler function to call with worker messages\n *\n * @example\n * ```typescript\n * import { createWorkerLogHandler, isWorkerMessage } from \"loggily/worker\"\n *\n * const handleLog = createWorkerLogHandler()\n *\n * worker.onmessage = (e) => {\n * if (isWorkerMessage(e.data)) {\n * handleLog(e.data)\n * } else {\n * // Handle other message types\n * }\n * }\n * ```\n */\nexport function createWorkerLogHandler(options: WorkerLogHandlerOptions = {}): (message: WorkerMessage) => void {\n const loggers = new Map<string, ConditionalLogger>()\n\n // Enable spans if requested\n if (options.enableSpans) {\n enableSpans()\n }\n\n function getLogger(namespace: string): ConditionalLogger {\n let logger = loggers.get(namespace)\n if (!logger) {\n logger = createLogger(namespace)\n loggers.set(namespace, logger)\n }\n return logger\n }\n\n return (message: WorkerMessage) => {\n if (isWorkerConsoleMessage(message)) {\n const logger = getLogger(message.namespace || \"worker\")\n const { message: msg, data } = formatConsoleArgs(message.args)\n dispatchToLogger(logger, message.level, msg, data)\n } else if (isWorkerLogMessage(message)) {\n const logger = getLogger(message.namespace)\n dispatchToLogger(logger, message.level, message.message, message.data)\n } else if (isWorkerSpanMessage(message)) {\n // Handle span events\n // For span end events, output the span with original worker timing data\n if (message.event === \"end\") {\n writeSpan(message.namespace, message.duration ?? 0, {\n span_id: message.spanId,\n trace_id: message.traceId,\n parent_id: message.parentId,\n ...message.props,\n ...message.spanData,\n })\n }\n // Start events are informational only on main thread\n // (the actual timing happens in the worker)\n }\n }\n}\n"],"mappings":"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"}
package/src/colors.ts DELETED
@@ -1,27 +0,0 @@
1
- /**
2
- * Vendored ANSI color functions — replaces picocolors dependency.
3
- * Supports NO_COLOR, FORCE_COLOR, and TTY detection.
4
- */
5
-
6
- const _process = typeof process !== "undefined" ? process : undefined
7
-
8
- const enabled =
9
- _process?.env?.["FORCE_COLOR"] !== undefined && _process?.env?.["FORCE_COLOR"] !== "0"
10
- ? true
11
- : _process?.env?.["NO_COLOR"] !== undefined
12
- ? false
13
- : (_process?.stdout?.isTTY ?? false)
14
-
15
- function wrap(open: string, close: string): (str: string) => string {
16
- if (!enabled) return (str) => str
17
- return (str) => open + str + close
18
- }
19
-
20
- export const colors = {
21
- dim: wrap("\x1b[2m", "\x1b[22m"),
22
- blue: wrap("\x1b[34m", "\x1b[39m"),
23
- yellow: wrap("\x1b[33m", "\x1b[39m"),
24
- red: wrap("\x1b[31m", "\x1b[39m"),
25
- magenta: wrap("\x1b[35m", "\x1b[39m"),
26
- cyan: wrap("\x1b[36m", "\x1b[39m"),
27
- }
package/src/context.ts DELETED
@@ -1,170 +0,0 @@
1
- /**
2
- * AsyncLocalStorage-based context propagation for loggily — Node.js/Bun only.
3
- *
4
- * Separated from core logger to allow tree-shaking in browser bundles.
5
- * When enabled, new spans automatically parent to the current context span,
6
- * and writeLog() auto-tags with trace_id/span_id from context.
7
- *
8
- * @example
9
- * ```typescript
10
- * import { enableContextPropagation, getCurrentSpan } from "loggily/context"
11
- *
12
- * enableContextPropagation()
13
- *
14
- * const log = createLogger("myapp")
15
- * {
16
- * using span = log.span("request")
17
- * // All logs and child spans within this async context
18
- * // automatically inherit trace_id and span_id
19
- * log.info("inside span") // auto-tagged with trace_id, span_id
20
- *
21
- * const current = getCurrentSpan()
22
- * // current === { spanId: "sp_1", traceId: "tr_1", parentId: null }
23
- * }
24
- * ```
25
- */
26
-
27
- import { AsyncLocalStorage } from "node:async_hooks"
28
- import { _setContextHooks, _clearContextHooks } from "./core.js"
29
-
30
- // ============ Types ============
31
-
32
- /** Minimal span context stored in AsyncLocalStorage */
33
- export interface SpanContext {
34
- readonly spanId: string
35
- readonly traceId: string
36
- readonly parentId: string | null
37
- }
38
-
39
- // ============ State ============
40
-
41
- let storage: AsyncLocalStorage<SpanContext> | null = null
42
- let contextEnabled = false
43
-
44
- /**
45
- * Map from spanId → the SpanContext that was active when the span was entered.
46
- * Used to restore the exact previous context on exit, avoiding corruption
47
- * from non-LIFO end() ordering.
48
- */
49
- const previousContexts = new Map<string, SpanContext | null>()
50
-
51
- // ============ API ============
52
-
53
- /**
54
- * Enable AsyncLocalStorage-based context propagation.
55
- * Once enabled, new spans automatically parent to the current context span,
56
- * and log messages are auto-tagged with trace_id/span_id.
57
- *
58
- * **Node.js/Bun only** — not available in browser environments.
59
- */
60
- export function enableContextPropagation(): void {
61
- if (!storage) {
62
- storage = new AsyncLocalStorage<SpanContext>()
63
- }
64
- contextEnabled = true
65
-
66
- // Register hooks with core.ts
67
- _setContextHooks({
68
- getContextTags,
69
- getContextParent() {
70
- const span = getCurrentSpan()
71
- if (!span) return null
72
- return { spanId: span.spanId, traceId: span.traceId }
73
- },
74
- enterContext: enterSpanContext,
75
- exitContext: exitSpanContext,
76
- })
77
- }
78
-
79
- /**
80
- * Disable context propagation.
81
- * Existing spans continue to work, but new spans won't auto-parent.
82
- */
83
- export function disableContextPropagation(): void {
84
- contextEnabled = false
85
- _clearContextHooks()
86
- }
87
-
88
- /** Check if context propagation is enabled */
89
- export function isContextPropagationEnabled(): boolean {
90
- return contextEnabled
91
- }
92
-
93
- /**
94
- * Get the current span context from AsyncLocalStorage.
95
- * Returns null if no span is active in the current async context,
96
- * or if context propagation is not enabled.
97
- */
98
- export function getCurrentSpan(): SpanContext | null {
99
- if (!contextEnabled || !storage) return null
100
- return storage.getStore() ?? null
101
- }
102
-
103
- /**
104
- * Enter a span context for the remainder of the current synchronous execution
105
- * and any async operations started from it. Used by the logger when creating
106
- * spans with `using` — since `using` doesn't wrap user code in a callback,
107
- * `enterWith()` is the right primitive.
108
- *
109
- * Captures the full previous SpanContext snapshot so it can be restored
110
- * exactly on exit, even with non-LIFO end() ordering.
111
- *
112
- * @internal
113
- */
114
- export function enterSpanContext(spanId: string, traceId: string, parentId: string | null): void {
115
- if (!contextEnabled || !storage) return
116
-
117
- // Capture the full previous context before overwriting
118
- const previous = storage.getStore() ?? null
119
- previousContexts.set(spanId, previous)
120
-
121
- storage.enterWith({ spanId, traceId, parentId })
122
- }
123
-
124
- /**
125
- * Restore the previous span context (called when a span ends).
126
- * Restores the exact SpanContext snapshot captured at enter time,
127
- * preventing corruption from non-LIFO end() ordering.
128
- *
129
- * @internal
130
- */
131
- export function exitSpanContext(spanId: string): void {
132
- if (!contextEnabled || !storage) return
133
-
134
- const previous = previousContexts.get(spanId)
135
- previousContexts.delete(spanId)
136
-
137
- if (previous) {
138
- storage.enterWith(previous)
139
- } else {
140
- // No previous context — exit entirely
141
- storage.enterWith(undefined as unknown as SpanContext)
142
- }
143
- }
144
-
145
- /**
146
- * Run a function within a span context.
147
- * Used for explicit context scoping (e.g., in request handlers).
148
- *
149
- * @param context - The span context to set
150
- * @param fn - The function to run within the context
151
- * @returns The return value of fn
152
- */
153
- export function runInSpanContext<T>(context: SpanContext, fn: () => T): T {
154
- if (!contextEnabled || !storage) return fn()
155
- return storage.run(context, fn)
156
- }
157
-
158
- /**
159
- * Get the context tags (trace_id, span_id) for the current async context.
160
- * Used by writeLog() to auto-tag log messages.
161
- * Returns empty object if context propagation is disabled or no span is active.
162
- */
163
- export function getContextTags(): Record<string, string> {
164
- const span = getCurrentSpan()
165
- if (!span) return {}
166
- return {
167
- trace_id: span.traceId,
168
- span_id: span.spanId,
169
- }
170
- }