autotel
Version:
Write Once, Observe Anywhere
1 lines • 22.3 kB
Source Map (JSON)
{"version":3,"file":"diagnostics.cjs","names":["safeRequire","SeverityNumber","safeRequire","logs","trace","ATTR_HTTP_REQUEST_METHOD","ATTR_URL_PATH","ATTR_URL_SCHEME","ATTR_NETWORK_PROTOCOL_VERSION","ATTR_USER_AGENT_ORIGINAL","ATTR_SERVER_ADDRESS","ATTR_SERVER_PORT","propagation","otelContext","defaultTextMapGetter","SpanKind","ATTR_URL_FULL","defaultTextMapSetter","SpanStatusCode","ATTR_HTTP_RESPONSE_STATUS_CODE"],"sources":["../src/diagnostics/channel.ts","../src/diagnostics/console.ts","../src/diagnostics/http.ts"],"sourcesContent":["/**\n * Edge-safe wrappers over Node's `diagnostics_channel`.\n *\n * The module is loaded lazily through {@link safeRequire} — never a static\n * `node:` import — so merely importing this file is side-effect-free and bundles\n * cleanly for browser/edge targets, where every subscribe call degrades to a\n * no-op (returning an unsubscribe that does nothing). This is the shared\n * primitive behind autotel's diagnostics-channel integrations (console capture,\n * HTTP spans) and any app- or library-specific channel you want to bridge into\n * a span/event.\n *\n * `diagnostics_channel.subscribe` (Node 18.7+) and `tracingChannel` (Node 19+)\n * are used; autotel targets Node 22+, but on any runtime that lacks them the\n * loader returns `undefined` and the helpers no-op.\n */\n\nimport { safeRequire } from '../node-require.js';\n\ntype DiagnosticsChannelModule = typeof import('node:diagnostics_channel');\n\nlet cached: DiagnosticsChannelModule | null | undefined;\n\nfunction loadDiagnosticsChannel(): DiagnosticsChannelModule | undefined {\n if (cached !== undefined) return cached ?? undefined;\n cached =\n safeRequire<DiagnosticsChannelModule>('node:diagnostics_channel') ?? null;\n return cached ?? undefined;\n}\n\n/** Whether Node's `diagnostics_channel` is available in this runtime. */\nexport function diagnosticsChannelAvailable(): boolean {\n return loadDiagnosticsChannel() !== undefined;\n}\n\n/** Handler for a plain named channel. */\nexport type ChannelMessageHandler = (\n message: unknown,\n name: string | symbol,\n) => void;\n\n/**\n * Subscribe to a named diagnostics channel. Returns an idempotent unsubscribe\n * function; a no-op (that still returns a disposer) on unsupported runtimes.\n */\nexport function subscribeChannel(\n name: string,\n handler: ChannelMessageHandler,\n): () => void {\n const dc = loadDiagnosticsChannel();\n if (!dc?.subscribe) return () => {};\n dc.subscribe(name, handler);\n let active = true;\n return () => {\n if (!active) return;\n active = false;\n dc.unsubscribe?.(name, handler);\n };\n}\n\n/** Subscriber set for a {@link https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel TracingChannel}. */\nexport interface TracingChannelHandlers {\n start?(message: unknown): void;\n end?(message: unknown): void;\n asyncStart?(message: unknown): void;\n asyncEnd?(message: unknown): void;\n error?(message: unknown): void;\n}\n\n/**\n * Subscribe to a `tracingChannel` (the `tracing:${name}:{start,end,…}` set).\n * Returns an idempotent unsubscribe; a no-op on runtimes without\n * `tracingChannel` support.\n */\nexport function subscribeTracingChannel(\n name: string,\n handlers: TracingChannelHandlers,\n): () => void {\n const dc = loadDiagnosticsChannel();\n const channel = dc?.tracingChannel?.(name);\n if (!channel) return () => {};\n // Node's typings want all five handlers; we pass the subset provided.\n channel.subscribe(handlers as Parameters<typeof channel.subscribe>[0]);\n let active = true;\n return () => {\n if (!active) return;\n active = false;\n channel.unsubscribe(handlers as Parameters<typeof channel.unsubscribe>[0]);\n };\n}\n","/**\n * Capture `console.*` calls as wide events — without monkey-patching `console`.\n *\n * Node publishes every `console.log` / `info` / `debug` / `warn` / `error` call\n * on a built-in diagnostics channel. {@link captureConsole} subscribes to those\n * channels and turns each call into an OpenTelemetry **log record** (correlated\n * to the active span via trace context by the logs SDK) and/or a **span event**\n * on the active span. Nothing patches the global `console`, so there is no\n * load-order fragility and no interference with other tooling.\n *\n * Opt-in. Call once after `init()` and keep the returned disposer to stop:\n *\n * ```ts\n * import { captureConsole } from 'autotel/diagnostics';\n *\n * const stop = captureConsole(); // every console.* → correlated log record\n * // …later: stop();\n * ```\n *\n * The built-in `console.*` channels are a Stability-1 (experimental) Node API;\n * this module degrades to a no-op where they are unavailable.\n */\n\nimport { trace, type Attributes } from '@opentelemetry/api';\nimport { logs, SeverityNumber, type Logger } from '@opentelemetry/api-logs';\nimport { safeRequire } from '../node-require.js';\nimport { subscribeChannel } from './channel.js';\n\n/** Console methods that publish a diagnostics channel. */\nexport type ConsoleLevel = 'log' | 'info' | 'debug' | 'warn' | 'error';\n\nconst ALL_LEVELS: readonly ConsoleLevel[] = [\n 'log',\n 'info',\n 'debug',\n 'warn',\n 'error',\n];\n\nconst SEVERITY: Record<ConsoleLevel, SeverityNumber> = {\n debug: SeverityNumber.DEBUG,\n log: SeverityNumber.INFO,\n info: SeverityNumber.INFO,\n warn: SeverityNumber.WARN,\n error: SeverityNumber.ERROR,\n};\n\nexport interface CaptureConsoleOptions {\n /** Which console methods to capture. Defaults to all five. */\n levels?: readonly ConsoleLevel[];\n /**\n * Where to record captured output:\n * - `'log'` (default): emit an OpenTelemetry log record;\n * - `'span-event'`: add an event to the active span (nothing if no active span);\n * - `'both'`.\n */\n target?: 'log' | 'span-event' | 'both';\n /** Logger name for emitted records. Defaults to `'autotel.console'`. */\n loggerName?: string;\n /** Static attributes merged onto every captured record/event. */\n attributes?: Attributes;\n}\n\ntype ConsoleMessage = { args?: unknown[] };\n\nconst nodeUtil = safeRequire<typeof import('node:util')>('node:util');\n\n/** Format console arguments the way `console` itself would (printf + inspect). */\nfunction formatArgs(args: unknown[]): string {\n if (nodeUtil?.format) return nodeUtil.format(...args);\n return args\n .map((a) => (typeof a === 'string' ? a : safeStringify(a)))\n .join(' ');\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/**\n * Start capturing `console.*` calls as wide events. Returns a disposer that\n * stops capture. Safe to call on runtimes without the console channels (no-op).\n */\nexport function captureConsole(\n options: CaptureConsoleOptions = {},\n): () => void {\n const levels = options.levels ?? ALL_LEVELS;\n const target = options.target ?? 'log';\n const toLog = target === 'log' || target === 'both';\n const toSpan = target === 'span-event' || target === 'both';\n const logger: Logger = logs.getLogger(\n options.loggerName ?? 'autotel.console',\n );\n\n // Guard against re-entrancy: if recording a captured call itself triggers a\n // `console.*` (e.g. an exporter logging a warning), don't capture that.\n let recording = false;\n\n const disposers = levels.map((level) =>\n subscribeChannel(`console.${level}`, (message) => {\n if (recording) return;\n const args = (message as ConsoleMessage)?.args ?? [];\n const body = formatArgs(args as unknown[]);\n recording = true;\n try {\n const attributes: Attributes = {\n 'log.source': 'console',\n 'log.method': level,\n ...options.attributes,\n };\n if (toLog) {\n logger.emit({\n severityNumber: SEVERITY[level],\n severityText: level.toUpperCase(),\n body,\n attributes,\n });\n }\n if (toSpan) {\n trace\n .getActiveSpan()\n ?.addEvent('log', { 'log.message': body, ...attributes });\n }\n } finally {\n recording = false;\n }\n }),\n );\n\n let active = true;\n return () => {\n if (!active) return;\n active = false;\n for (const dispose of disposers) dispose();\n };\n}\n","/**\n * Lightweight HTTP spans via Node's built-in `diagnostics_channel` — no\n * monkey-patching, no `import-in-the-middle`.\n *\n * Node publishes `http.server.request.start` / `http.server.response.finish`\n * and `http.client.request.start` / `http.client.response.finish` /\n * `http.client.request.error`. {@link instrumentHttp} subscribes to those and\n * emits a `SERVER` span per inbound request (parented to an incoming W3C\n * `traceparent`) and a `CLIENT` span per outbound request (whose context it\n * injects into the outgoing headers for downstream propagation).\n *\n * ```ts\n * import { instrumentHttp } from 'autotel/diagnostics';\n *\n * const stop = instrumentHttp();\n * ```\n *\n * Scope & limitation. This is an opt-in, low-overhead alternative to\n * `@opentelemetry/instrumentation-http` for HTTP span coverage + W3C\n * propagation. Client-side propagation works (the `traceparent` is injected on\n * the `ClientRequest` object directly). What it does **not** do is establish an\n * *ambient* OpenTelemetry context for the duration of a server request handler,\n * so application spans created inside a handler will not become children of the\n * `SERVER` span.\n *\n * This is structural, not a \"wait for a newer Node\" gap. Node publishes the\n * `http.*` channels with a plain `channel.publish()` — not `runStores` /\n * `tracingChannel` — so a subscriber has no scope to bind a store to. The only\n * ways to get handler nesting both defeat the purpose of using a channel:\n * 1. `AsyncLocalStorage.enterWith()` in the start handler — no scoped exit, so\n * context leaks across requests sharing an event-loop tick / keep-alive\n * connection and misattributes spans. Strictly worse than no nesting.\n * 2. Patching `http.Server.prototype.emit` to wrap the `'request'` listener in\n * `context.with()` — monkey-patching, i.e. reimplementing\n * `@opentelemetry/instrumentation-http`.\n * If you need handler nesting, use `@opentelemetry/instrumentation-http`.\n *\n * The `http.*` channels are a Stability-1 (experimental) Node API; this module\n * degrades to a no-op where they are unavailable.\n */\n\nimport type { ClientRequest, IncomingMessage, ServerResponse } from 'node:http';\nimport {\n context as otelContext,\n defaultTextMapGetter,\n defaultTextMapSetter,\n propagation,\n SpanKind,\n SpanStatusCode,\n trace,\n type Attributes,\n type Span,\n type Tracer,\n} from '@opentelemetry/api';\nimport {\n ATTR_HTTP_REQUEST_METHOD,\n ATTR_HTTP_RESPONSE_STATUS_CODE,\n ATTR_NETWORK_PROTOCOL_VERSION,\n ATTR_SERVER_ADDRESS,\n ATTR_SERVER_PORT,\n ATTR_URL_FULL,\n ATTR_URL_PATH,\n ATTR_URL_SCHEME,\n ATTR_USER_AGENT_ORIGINAL,\n} from '@opentelemetry/semantic-conventions';\nimport { subscribeChannel } from './channel.js';\n\nexport interface InstrumentHttpOptions {\n /** Instrument inbound (server) requests. Default `true`. */\n server?: boolean;\n /** Instrument outbound (client) requests. Default `true`. */\n client?: boolean;\n /** Tracer to use. Defaults to `trace.getTracer('autotel.http-diagnostics')`. */\n tracer?: Tracer;\n}\n\ninterface ServerStartMessage {\n request?: IncomingMessage;\n response?: ServerResponse;\n}\ninterface ServerFinishMessage {\n request?: IncomingMessage;\n response?: ServerResponse;\n}\ninterface ClientStartMessage {\n request?: ClientRequest;\n}\ninterface ClientFinishMessage {\n request?: ClientRequest;\n response?: IncomingMessage;\n}\ninterface ClientErrorMessage {\n request?: ClientRequest;\n error?: unknown;\n}\n\nconst SERVER_SPANS = new WeakMap<object, Span>();\nconst CLIENT_SPANS = new WeakMap<object, Span>();\n\nfunction firstHeader(value: string | string[] | undefined): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n\nfunction splitHostPort(host: string | undefined): {\n address?: string;\n port?: number;\n} {\n if (!host) return {};\n const idx = host.lastIndexOf(':');\n if (idx === -1) return { address: host };\n const port = Number(host.slice(idx + 1));\n return {\n address: host.slice(0, idx),\n port: Number.isFinite(port) ? port : undefined,\n };\n}\n\n/**\n * Start emitting HTTP server/client spans from Node's HTTP diagnostics\n * channels. Returns a disposer; a no-op on runtimes without the channels.\n */\nexport function instrumentHttp(\n options: InstrumentHttpOptions = {},\n): () => void {\n const tracer = options.tracer ?? trace.getTracer('autotel.http-diagnostics');\n const disposers: Array<() => void> = [];\n\n if (options.server !== false) {\n disposers.push(\n subscribeChannel('http.server.request.start', (message) => {\n const request = (message as ServerStartMessage)?.request;\n if (!request) return;\n const method = request.method ?? 'HTTP';\n const host = firstHeader(request.headers.host);\n const { address, port } = splitHostPort(host);\n const path = (request.url ?? '/').split('?', 1)[0];\n const attributes: Attributes = {\n [ATTR_HTTP_REQUEST_METHOD]: method,\n [ATTR_URL_PATH]: path,\n [ATTR_URL_SCHEME]: 'http',\n [ATTR_NETWORK_PROTOCOL_VERSION]: request.httpVersion,\n [ATTR_USER_AGENT_ORIGINAL]: firstHeader(\n request.headers['user-agent'],\n ),\n [ATTR_SERVER_ADDRESS]: address,\n [ATTR_SERVER_PORT]: port,\n };\n const parent = propagation.extract(\n otelContext.active(),\n request.headers,\n defaultTextMapGetter,\n );\n const span = tracer.startSpan(\n method,\n { kind: SpanKind.SERVER, attributes },\n parent,\n );\n SERVER_SPANS.set(request, span);\n }),\n subscribeChannel('http.server.response.finish', (message) => {\n const { request, response } = (message as ServerFinishMessage) ?? {};\n if (!request) return;\n const span = SERVER_SPANS.get(request);\n if (!span) return;\n SERVER_SPANS.delete(request);\n finishHttpSpan(span, response?.statusCode, 500);\n }),\n );\n }\n\n if (options.client !== false) {\n disposers.push(\n subscribeChannel('http.client.request.start', (message) => {\n const request = (message as ClientStartMessage)?.request;\n if (!request) return;\n const method = request.method ?? 'HTTP';\n // `ClientRequest` exposes host/protocol/path on the public surface.\n const req = request as ClientRequest & {\n host?: string;\n protocol?: string;\n path?: string;\n };\n const { address, port } = splitHostPort(req.host);\n const scheme = (req.protocol ?? 'http:').replace(':', '');\n const attributes: Attributes = {\n [ATTR_HTTP_REQUEST_METHOD]: method,\n [ATTR_SERVER_ADDRESS]: address,\n [ATTR_SERVER_PORT]: port,\n [ATTR_URL_FULL]:\n address && req.path\n ? `${scheme}://${req.host}${req.path}`\n : undefined,\n };\n const span = tracer.startSpan(method, {\n kind: SpanKind.CLIENT,\n attributes,\n });\n CLIENT_SPANS.set(request, span);\n\n // Inject this span's context into the outbound headers so the\n // downstream service continues the trace.\n if (!request.headersSent) {\n const carrier: Record<string, string> = {};\n propagation.inject(\n trace.setSpan(otelContext.active(), span),\n carrier,\n defaultTextMapSetter,\n );\n for (const [key, value] of Object.entries(carrier)) {\n try {\n request.setHeader(key, value);\n } catch {\n // Headers already sent / immutable — propagation best-effort.\n }\n }\n }\n }),\n subscribeChannel('http.client.response.finish', (message) => {\n const { request, response } = (message as ClientFinishMessage) ?? {};\n if (!request) return;\n const span = CLIENT_SPANS.get(request);\n if (!span) return;\n CLIENT_SPANS.delete(request);\n finishHttpSpan(span, response?.statusCode, 400);\n }),\n subscribeChannel('http.client.request.error', (message) => {\n const { request, error } = (message as ClientErrorMessage) ?? {};\n if (!request) return;\n const span = CLIENT_SPANS.get(request);\n if (!span) return;\n CLIENT_SPANS.delete(request);\n if (error instanceof Error) span.recordException(error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : undefined,\n });\n span.end();\n }),\n );\n }\n\n let active = true;\n return () => {\n if (!active) return;\n active = false;\n for (const dispose of disposers) dispose();\n };\n}\n\n/** Set status code + error status (when `>= errorAt`) and end the span. */\nfunction finishHttpSpan(\n span: Span,\n statusCode: number | undefined,\n errorAt: number,\n): void {\n if (statusCode !== undefined) {\n span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, statusCode);\n if (statusCode >= errorAt) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n }\n }\n span.end();\n}\n"],"mappings":"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"}