autotel
Version:
Write Once, Observe Anywhere
1 lines • 21.6 kB
Source Map (JSON)
{"version":3,"file":"messaging-adapters.cjs","names":["natsMsg","TraceFlags"],"sources":["../src/messaging-adapters.ts"],"sourcesContent":["/**\n * Pre-built adapter configurations for common messaging systems.\n *\n * These adapters provide ready-to-use hook configurations for systems\n * not explicitly supported by the core messaging module. Use them with\n * traceProducer/traceConsumer to get system-specific attributes.\n *\n * @example NATS consumer\n * ```typescript\n * import { traceConsumer } from 'autotel/messaging';\n * import { natsAdapter } from 'autotel/messaging/adapters';\n *\n * const processMessage = traceConsumer({\n * system: 'nats',\n * destination: 'orders',\n * ...natsAdapter.consumer,\n * })(ctx => async (msg) => {\n * // msg.subject, msg.info.stream are now captured as span attributes\n * await handleOrder(msg.data);\n * });\n * ```\n *\n * @example Datadog context propagation\n * ```typescript\n * import { traceConsumer } from 'autotel/messaging';\n * import { datadogContextExtractor } from 'autotel/messaging/adapters';\n *\n * const processMessage = traceConsumer({\n * system: 'kafka',\n * destination: 'events',\n * customContextExtractor: datadogContextExtractor,\n * })(ctx => async (msg) => {\n * // Parent span from Datadog trace headers is linked\n * });\n * ```\n *\n * @module\n */\n\nimport type { AttributeValue, SpanContext } from '@opentelemetry/api';\nimport { TraceFlags } from '@opentelemetry/api';\nimport type { ProducerContext, ConsumerContext } from './messaging';\n\n// ============================================================================\n// Adapter Types\n// ============================================================================\n\n/**\n * Producer adapter configuration\n */\nexport interface ProducerAdapter {\n /**\n * Hook to add system-specific attributes to producer spans\n */\n customAttributes?: (\n ctx: ProducerContext,\n args: unknown[],\n ) => Record<string, AttributeValue>;\n\n /**\n * Hook to inject custom headers beyond W3C traceparent\n */\n customHeaders?: (ctx: ProducerContext) => Record<string, string>;\n}\n\n/**\n * Consumer adapter configuration\n */\nexport interface ConsumerAdapter {\n /**\n * Extract headers from the message for trace context propagation\n */\n headersFrom?: (msg: unknown) => Record<string, string> | undefined;\n\n /**\n * Hook to add system-specific attributes to consumer spans\n */\n customAttributes?: (\n ctx: ConsumerContext,\n msg: unknown,\n ) => Record<string, AttributeValue>;\n\n /**\n * Hook to extract parent span context from non-W3C header formats\n */\n customContextExtractor?: (\n headers: Record<string, string>,\n ) => SpanContext | null;\n}\n\n/**\n * Combined producer and consumer adapter\n */\nexport interface MessagingAdapter {\n producer?: ProducerAdapter;\n consumer?: ConsumerAdapter;\n}\n\n// ============================================================================\n// NATS JetStream Adapter\n// ============================================================================\n\n/**\n * NATS JetStream message type (for reference)\n *\n * @internal Not exported - users bring their own NATS types\n */\ninterface NatsJetStreamMsg {\n subject: string;\n reply?: string;\n data: Uint8Array;\n headers?: {\n /** Convert headers to plain object (some NATS implementations) */\n toJSON?: () => Record<string, string> | unknown;\n /** Get a header value by key (Headers-like interface) */\n get?: (key: string) => string | undefined;\n /** Iterate over header entries */\n entries?: () => Iterable<[string, string]>;\n };\n info?: {\n stream: string;\n consumer: string;\n redeliveryCount?: number;\n pending?: number;\n timestampNanos?: bigint;\n };\n}\n\n/**\n * NATS JetStream adapter\n *\n * Captures NATS-specific attributes following NATS observability conventions.\n *\n * @example Producer\n * ```typescript\n * const publishOrder = traceProducer({\n * system: 'nats',\n * destination: 'orders.created',\n * ...natsAdapter.producer,\n * })(ctx => async (subject, payload, opts) => {\n * const headers = ctx.getTraceHeaders();\n * await nc.publish(subject, payload, { headers });\n * });\n * ```\n *\n * @example Consumer\n * ```typescript\n * const processOrder = traceConsumer({\n * system: 'nats',\n * destination: 'orders.created',\n * consumerGroup: 'order-processor',\n * ...natsAdapter.consumer,\n * })(ctx => async (msg: JsMsg) => {\n * await handleOrder(msg.data);\n * msg.ack();\n * });\n * ```\n */\nexport const natsAdapter: MessagingAdapter = {\n producer: {\n customAttributes: (_ctx, args) => {\n const msg = args[0] as\n | { subject?: string; replyTo?: string; stream?: string }\n | undefined;\n const attrs: Record<string, AttributeValue> = {};\n\n if (msg?.subject) attrs['nats.subject'] = msg.subject;\n if (msg?.replyTo) attrs['nats.reply_to'] = msg.replyTo;\n if (msg?.stream) attrs['nats.stream'] = msg.stream;\n\n return attrs;\n },\n },\n consumer: {\n headersFrom: (msg) => {\n const natsMsg = msg as NatsJetStreamMsg;\n const headers = natsMsg.headers;\n\n if (!headers) return;\n\n // Try toJSON() first (some NATS implementations)\n if (typeof headers.toJSON === 'function') {\n const json = headers.toJSON();\n if (json && typeof json === 'object') {\n return json as Record<string, string>;\n }\n }\n\n // Fallback: use .get() for common trace headers\n // This handles Headers-like objects that only expose .get()\n if (typeof headers.get === 'function') {\n const result: Record<string, string> = {};\n const traceHeaders = [\n 'traceparent',\n 'tracestate',\n 'baggage',\n 'x-b3-traceid',\n 'x-b3-spanid',\n 'x-b3-sampled',\n 'b3',\n ];\n for (const key of traceHeaders) {\n const value = headers.get(key);\n if (value) {\n result[key] = value;\n }\n }\n if (Object.keys(result).length > 0) {\n return result;\n }\n }\n\n // Fallback: try to iterate if it's iterable (e.g., entries())\n if (typeof headers.entries === 'function') {\n const result: Record<string, string> = {};\n for (const [key, value] of headers.entries()) {\n if (typeof key === 'string' && typeof value === 'string') {\n result[key] = value;\n }\n }\n if (Object.keys(result).length > 0) {\n return result;\n }\n }\n\n return;\n },\n customAttributes: (_ctx, msg) => {\n const natsMsg = msg as NatsJetStreamMsg;\n const attrs: Record<string, AttributeValue> = {};\n\n if (natsMsg.subject) attrs['nats.subject'] = natsMsg.subject;\n if (natsMsg.reply) attrs['nats.reply_to'] = natsMsg.reply;\n if (natsMsg.info?.stream) attrs['nats.stream'] = natsMsg.info.stream;\n if (natsMsg.info?.consumer)\n attrs['nats.consumer'] = natsMsg.info.consumer;\n if (natsMsg.info?.redeliveryCount !== undefined) {\n attrs['nats.delivered_count'] = natsMsg.info.redeliveryCount;\n }\n if (natsMsg.info?.pending !== undefined) {\n attrs['nats.pending'] = natsMsg.info.pending;\n }\n\n return attrs;\n },\n },\n};\n\n// ============================================================================\n// Temporal Adapter\n// ============================================================================\n\n/**\n * Temporal activity/workflow info type (for reference)\n *\n * @internal Not exported - users bring their own Temporal types\n */\ninterface TemporalActivityInfo {\n workflowId?: string;\n runId?: string;\n activityId?: string;\n taskQueue?: string;\n attempt?: number;\n workflowType?: string;\n activityType?: string;\n startToCloseTimeout?: string;\n scheduleToCloseTimeout?: string;\n}\n\n/**\n * Temporal adapter\n *\n * Captures Temporal-specific attributes for workflow activities.\n * Use this when instrumenting Temporal activity handlers.\n *\n * @example Activity handler\n * ```typescript\n * const processOrder = traceConsumer({\n * system: 'temporal',\n * destination: 'order-activities',\n * ...temporalAdapter.consumer,\n * })(ctx => async (info: ActivityInfo, input: OrderInput) => {\n * // Temporal attributes are captured automatically\n * return processOrderLogic(input);\n * });\n * ```\n *\n * @example Workflow signal/query\n * ```typescript\n * const sendSignal = traceProducer({\n * system: 'temporal',\n * destination: 'order-signals',\n * ...temporalAdapter.producer,\n * })(ctx => async (workflowId, signalName, payload) => {\n * await client.workflow.signal(workflowId, signalName, payload);\n * });\n * ```\n */\nexport const temporalAdapter: MessagingAdapter = {\n producer: {\n customAttributes: (_ctx, args) => {\n const info = args[0] as TemporalActivityInfo | undefined;\n const attrs: Record<string, AttributeValue> = {};\n\n if (info?.workflowId) attrs['temporal.workflow_id'] = info.workflowId;\n if (info?.runId) attrs['temporal.run_id'] = info.runId;\n if (info?.taskQueue) attrs['temporal.task_queue'] = info.taskQueue;\n if (info?.workflowType)\n attrs['temporal.workflow_type'] = info.workflowType;\n\n return attrs;\n },\n },\n consumer: {\n customAttributes: (_ctx, msg) => {\n const info = msg as TemporalActivityInfo;\n const attrs: Record<string, AttributeValue> = {};\n\n if (info.workflowId) attrs['temporal.workflow_id'] = info.workflowId;\n if (info.runId) attrs['temporal.run_id'] = info.runId;\n if (info.activityId) attrs['temporal.activity_id'] = info.activityId;\n if (info.taskQueue) attrs['temporal.task_queue'] = info.taskQueue;\n if (info.attempt !== undefined) attrs['temporal.attempt'] = info.attempt;\n if (info.activityType)\n attrs['temporal.activity_type'] = info.activityType;\n\n return attrs;\n },\n },\n};\n\n// ============================================================================\n// Cloudflare Queues Adapter\n// ============================================================================\n\n/**\n * Cloudflare Queue message type (for reference)\n *\n * @internal Not exported - users bring their own Cloudflare types\n */\ninterface CloudflareQueueMessage {\n id: string;\n timestamp: Date;\n body: unknown;\n attempts: number;\n}\n\n/**\n * Cloudflare Queues adapter\n *\n * Captures Cloudflare Queue-specific attributes.\n *\n * @example Queue consumer\n * ```typescript\n * export default {\n * async queue(batch: MessageBatch, env: Env) {\n * for (const msg of batch.messages) {\n * await processMessage(msg);\n * }\n * },\n * };\n *\n * const processMessage = traceConsumer({\n * system: 'cloudflare_queues',\n * destination: 'my-queue',\n * ...cloudflareQueuesAdapter.consumer,\n * })(ctx => async (msg: Message) => {\n * await handleMessage(msg.body);\n * msg.ack();\n * });\n * ```\n */\nexport const cloudflareQueuesAdapter: MessagingAdapter = {\n consumer: {\n customAttributes: (_ctx, msg) => {\n const cfMsg = msg as CloudflareQueueMessage;\n const attrs: Record<string, AttributeValue> = {};\n\n if (cfMsg.id) attrs['cloudflare.queue.message_id'] = cfMsg.id;\n if (cfMsg.timestamp) {\n attrs['cloudflare.queue.timestamp_ms'] = cfMsg.timestamp.getTime();\n }\n if (cfMsg.attempts !== undefined) {\n attrs['cloudflare.queue.attempts'] = cfMsg.attempts;\n }\n\n return attrs;\n },\n },\n};\n\n// ============================================================================\n// Context Extractors for Non-W3C Formats\n// ============================================================================\n\n/**\n * Datadog trace context extractor\n *\n * Extracts parent span context from Datadog-format trace headers.\n * Converts Datadog's decimal IDs to OpenTelemetry's hex format.\n *\n * Note: Datadog sends trace/span IDs as decimal strings, not hex.\n * This extractor converts decimal -> hex before formatting for OTel.\n *\n * @example\n * ```typescript\n * const processMessage = traceConsumer({\n * system: 'kafka',\n * destination: 'events',\n * customContextExtractor: datadogContextExtractor,\n * })(ctx => async (msg) => {\n * // Links to parent Datadog span automatically\n * });\n * ```\n */\nexport function datadogContextExtractor(\n headers: Record<string, string>,\n): SpanContext | null {\n const traceIdDecimal = headers['x-datadog-trace-id'];\n const spanIdDecimal = headers['x-datadog-parent-id'];\n const samplingPriority = headers['x-datadog-sampling-priority'];\n\n if (!traceIdDecimal || !spanIdDecimal) return null;\n\n // Datadog sends IDs as decimal strings - convert to hex\n // Use BigInt for 64-bit values that exceed Number.MAX_SAFE_INTEGER\n let otelTraceId: string;\n let otelSpanId: string;\n\n try {\n // Convert decimal to hex and pad to OTel format\n // OTel trace IDs are 32 hex chars (128-bit), Datadog uses 64-bit\n otelTraceId = BigInt(traceIdDecimal).toString(16).padStart(32, '0');\n // OTel span IDs are 16 hex chars (64-bit)\n otelSpanId = BigInt(spanIdDecimal).toString(16).padStart(16, '0');\n } catch {\n // Invalid decimal string\n return null;\n }\n\n // Sampling priority > 0 means sampled\n const sampled = samplingPriority\n ? Number.parseInt(samplingPriority, 10) > 0\n : true;\n\n return {\n traceId: otelTraceId,\n spanId: otelSpanId,\n traceFlags: sampled ? TraceFlags.SAMPLED : TraceFlags.NONE,\n isRemote: true,\n };\n}\n\n/**\n * B3 (Zipkin) trace context extractor\n *\n * Extracts parent span context from B3 format headers.\n * Supports both single-header (b3) and multi-header formats.\n *\n * @see https://github.com/openzipkin/b3-propagation\n *\n * @example Single-header format\n * ```typescript\n * // Header: b3: 80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1\n * const processMessage = traceConsumer({\n * system: 'rabbitmq',\n * destination: 'events',\n * customContextExtractor: b3ContextExtractor,\n * })(ctx => async (msg) => {\n * // Links to parent Zipkin span\n * });\n * ```\n *\n * @example Multi-header format\n * ```typescript\n * // Headers: X-B3-TraceId, X-B3-SpanId, X-B3-Sampled\n * ```\n */\nexport function b3ContextExtractor(\n headers: Record<string, string>,\n): SpanContext | null {\n // Try single-header format first: {TraceId}-{SpanId}-{SamplingState}-{ParentSpanId}\n const b3Single = headers['b3'] || headers['B3'];\n if (b3Single) {\n // Handle \"0\" (not sampled, no trace) case\n if (b3Single === '0') return null;\n\n const parts = b3Single.split('-');\n const traceId = parts[0];\n const spanId = parts[1];\n const sampledFlag = parts[2];\n\n if (traceId && spanId) {\n const sampled = sampledFlag !== '0' && sampledFlag !== 'd';\n\n return {\n traceId: traceId.padStart(32, '0'),\n spanId: spanId.padStart(16, '0'),\n traceFlags: sampled ? TraceFlags.SAMPLED : TraceFlags.NONE,\n isRemote: true,\n };\n }\n }\n\n // Fall back to multi-header format\n const traceId =\n headers['x-b3-traceid'] ||\n headers['X-B3-TraceId'] ||\n headers['X-B3-Traceid'];\n const spanId =\n headers['x-b3-spanid'] || headers['X-B3-SpanId'] || headers['X-B3-Spanid'];\n const sampledHeader =\n headers['x-b3-sampled'] ||\n headers['X-B3-Sampled'] ||\n headers['x-b3-flags'] ||\n headers['X-B3-Flags'];\n\n if (!traceId || !spanId) return null;\n\n // x-b3-sampled: \"1\" or \"true\" = sampled, \"0\" or \"false\" = not sampled\n // x-b3-flags: \"1\" = debug (implies sampled)\n const sampled =\n sampledHeader === '1' ||\n sampledHeader === 'true' ||\n sampledHeader === undefined; // Default to sampled if not specified\n\n return {\n traceId: traceId.padStart(32, '0'),\n spanId: spanId.padStart(16, '0'),\n traceFlags: sampled ? TraceFlags.SAMPLED : TraceFlags.NONE,\n isRemote: true,\n };\n}\n\n/**\n * AWS X-Ray trace context extractor\n *\n * Extracts parent span context from AWS X-Ray trace header.\n * Format: Root=1-{timestamp}-{random};Parent={parent-id};Sampled={0|1}\n *\n * @example\n * ```typescript\n * const processMessage = traceConsumer({\n * system: 'sqs',\n * destination: 'my-queue',\n * customContextExtractor: xrayContextExtractor,\n * })(ctx => async (msg) => {\n * // Links to parent X-Ray trace\n * });\n * ```\n */\nexport function xrayContextExtractor(\n headers: Record<string, string>,\n): SpanContext | null {\n const xrayHeader = headers['x-amzn-trace-id'] || headers['X-Amzn-Trace-Id'];\n\n if (!xrayHeader) return null;\n\n // Parse: Root=1-{8-char-timestamp}-{24-char-random};Parent={16-char-parent};Sampled=1\n const rootMatch = xrayHeader.match(/Root=1-([a-f0-9]{8})-([a-f0-9]{24})/i);\n const parentMatch = xrayHeader.match(/Parent=([a-f0-9]{16})/i);\n const sampledMatch = xrayHeader.match(/Sampled=([01])/);\n\n if (!rootMatch || !parentMatch) return null;\n\n // X-Ray trace ID format: 1-{timestamp}-{random} -> OTel: {timestamp}{random}\n const timestamp = rootMatch[1];\n const random = rootMatch[2];\n const parentId = parentMatch[1];\n\n if (!timestamp || !random || !parentId) return null;\n\n const traceId = `${timestamp}${random}`;\n const spanId = parentId;\n const sampled = sampledMatch ? sampledMatch[1] === '1' : true;\n\n return {\n traceId,\n spanId,\n traceFlags: sampled ? TraceFlags.SAMPLED : TraceFlags.NONE,\n isRemote: true,\n };\n}\n"],"mappings":"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"}