{"version":3,"sources":["../src/trace-helpers.ts"],"names":["trace","context","requireModule","SpanStatusCode"],"mappings":"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","file":"chunk-GML3FBOT.cjs","sourcesContent":["/**\n * Trace context helpers - Core primitives for trace correlation\n *\n * These are the building blocks that allow users to bring their own logger\n * (bunyan, log4js, custom, etc.) and add trace correlation.\n *\n * @example Using with bunyan\n * ```typescript\n * import bunyan from 'bunyan';\n * import { enrichWithTraceContext } from 'autotel/trace-helpers';\n *\n * const bunyanLogger = bunyan.createLogger({ name: 'myapp' });\n *\n * const logger = {\n *   info: (msg: string, extra?: object) => {\n *     bunyanLogger.info(enrichWithTraceContext(extra || {}), msg);\n *   }\n * };\n * ```\n *\n * @example Using with log4js\n * ```typescript\n * import log4js from 'log4js';\n * import { getTraceContext } from 'autotel/trace-helpers';\n *\n * const log4jsLogger = log4js.getLogger();\n *\n * function logWithTrace(level: string, msg: string, extra?: object) {\n *   const context = getTraceContext();\n *   log4jsLogger[level](msg, { ...extra, ...context });\n * }\n * ```\n */\n\nimport { trace, context, SpanStatusCode } from '@opentelemetry/api';\nimport type { Span, Tracer, Context } from '@opentelemetry/api';\nimport { requireModule } from './node-require';\n\n/**\n * WeakMap to store span names for active spans\n * This allows us to retrieve the span name even though OpenTelemetry\n * doesn't expose it through the public API\n */\nconst spanNameMap = new WeakMap<Span, string>();\n\n/**\n * Store span name for a given span\n * Called internally when spans are created\n */\nexport function setSpanName(span: Span, name: string): void {\n  spanNameMap.set(span, name);\n}\n\n/**\n * Trace context extracted from active span\n */\nexport interface TraceContext {\n  /** Full 32-character hex trace ID */\n  traceId: string;\n  /** 16-character hex span ID */\n  spanId: string;\n  /** First 16 characters of trace ID (for log grouping/correlation) */\n  correlationId: string;\n  /** Function/operation name (OpenTelemetry semantic convention: code.function) */\n  'code.function'?: string;\n  /** Datadog trace ID in decimal format (lower 64 bits) for log-trace correlation */\n  'dd.trace_id'?: string;\n  /** Datadog span ID in decimal format for log-trace correlation */\n  'dd.span_id'?: string;\n}\n\n/**\n * Convert hex string to decimal string representation\n * Handles 64-bit unsigned integers for Datadog correlation\n *\n * @param hex - Hex string (up to 16 characters for 64-bit)\n * @returns Decimal string representation\n */\nfunction hexToDecimal(hex: string): string {\n  // For 64-bit values, use BigInt to avoid precision loss\n  return BigInt('0x' + hex).toString(10);\n}\n\n/**\n * Get current trace context from active span\n *\n * Returns null if no span is active (e.g., outside of trace operation)\n *\n * Includes both OpenTelemetry standard fields (hex) and Datadog-specific\n * fields (decimal) for maximum compatibility.\n *\n * @returns Trace context with traceId, spanId, correlationId, and Datadog decimal IDs, or null\n *\n * @example\n * ```typescript\n * import { getTraceContext } from 'autotel/trace-helpers';\n *\n * const context = getTraceContext();\n * if (context) {\n *   console.log('Current trace:', context.traceId);\n *   // Current trace: 4bf92f3577b34da6a3ce929d0e0e4736\n *   console.log('Datadog trace ID:', context['dd.trace_id']);\n *   // Datadog trace ID: 12007117331170166582 (decimal for log correlation)\n * }\n * ```\n */\nexport function getTraceContext(): TraceContext | null {\n  const span = trace.getActiveSpan();\n  if (!span) return null;\n\n  const spanContext = span.spanContext();\n  const traceId = spanContext.traceId;\n  const spanId = spanContext.spanId;\n\n  // Get span name from WeakMap (set when span is created)\n  // Map to OpenTelemetry semantic convention: code.function\n  const spanName = spanNameMap.get(span);\n\n  // Datadog uses the lower 64 bits of the 128-bit OpenTelemetry trace ID\n  // Convert from hex to decimal for Datadog's log-trace correlation\n  const traceIdLower64 = traceId.slice(-16); // Last 16 hex chars = lower 64 bits\n  const ddTraceId = hexToDecimal(traceIdLower64);\n  const ddSpanId = hexToDecimal(spanId);\n\n  return {\n    traceId,\n    spanId,\n    correlationId: traceId.slice(0, 16),\n    ...(spanName && { 'code.function': spanName }),\n    // Datadog-specific fields for log-trace correlation\n    'dd.trace_id': ddTraceId,\n    'dd.span_id': ddSpanId,\n  };\n}\n\n/**\n * Enrich object with trace context (traceId, spanId, correlationId, and Datadog fields)\n *\n * If no span is active, returns the object unchanged.\n * This prevents \"undefined\" or \"null\" values in logs.\n *\n * Automatically adds both OpenTelemetry standard fields (hex) and Datadog-specific\n * fields (decimal) for maximum compatibility with observability backends.\n *\n * @param obj - Object to enrich (e.g., log metadata)\n * @returns Object with trace context merged in, or unchanged if no active span\n *\n * @example\n * ```typescript\n * import { enrichWithTraceContext } from 'autotel/trace-helpers';\n *\n * // Inside a trace operation:\n * const enriched = enrichWithTraceContext({ userId: '123' });\n * // {\n * //   userId: '123',\n * //   traceId: '4bf92f3577b34da6a3ce929d0e0e4736',\n * //   spanId: '00f067aa0ba902b7',\n * //   correlationId: '4bf92f3577b34da6',\n * //   'dd.trace_id': '12007117331170166582',  // Datadog decimal format\n * //   'dd.span_id': '67667974448284583'       // Datadog decimal format\n * // }\n *\n * // Outside trace operation:\n * const unchanged = enrichWithTraceContext({ userId: '123' });\n * // { userId: '123' } - no trace fields added\n * ```\n */\nexport function enrichWithTraceContext<T extends Record<string, unknown>>(\n  obj: T,\n): T {\n  const context = getTraceContext();\n  return context ? ({ ...obj, ...context } as T) : obj;\n}\n\n/**\n * Check if currently in a trace context\n *\n * Useful for conditional logic based on trace presence\n *\n * @returns true if active span exists, false otherwise\n *\n * @example\n * ```typescript\n * import { isTracing } from 'autotel/trace-helpers';\n *\n * if (isTracing()) {\n *   // Add expensive debug metadata only when tracing\n *   logger.debug('Detailed context', expensiveDebugData());\n * }\n * ```\n */\nexport function isTracing(): boolean {\n  return trace.getActiveSpan() !== undefined;\n}\n\n/**\n * Get a tracer instance for creating custom spans\n *\n * Use this when you need low-level control over span lifecycle.\n * For most use cases, prefer trace(), span(), or instrument() instead.\n *\n * @param name - Tracer name (usually your service or module name)\n * @param version - Optional version string\n * @returns OpenTelemetry Tracer instance\n *\n * @example Basic usage\n * ```typescript\n * import { getTracer } from 'autotel';\n *\n * const tracer = getTracer('my-service');\n * const span = tracer.startSpan('custom.operation');\n * try {\n *   // Your logic\n *   span.setAttribute('key', 'value');\n * } finally {\n *   span.end();\n * }\n * ```\n *\n * @example With AI SDK\n * ```typescript\n * import { getTracer } from 'autotel';\n * import { generateText } from 'ai';\n *\n * const tracer = getTracer('ai-agent');\n * const result = await generateText({\n *   model: myModel,\n *   prompt: 'Hello',\n *   experimental_telemetry: {\n *     isEnabled: true,\n *     tracer,\n *   },\n * });\n * ```\n */\nexport function getTracer(name: string, version?: string): Tracer {\n  return trace.getTracer(name, version);\n}\n\n/**\n * Get the currently active span\n *\n * Returns undefined if no span is currently active.\n * Useful for adding attributes or events to the current span.\n *\n * @returns Active span or undefined\n *\n * @example Adding attributes to active span\n * ```typescript\n * import { getActiveSpan } from 'autotel';\n *\n * const span = getActiveSpan();\n * if (span) {\n *   span.setAttribute('user.id', userId);\n *   span.addEvent('User action', { action: 'click' });\n * }\n * ```\n *\n * @example Checking span status\n * ```typescript\n * import { getActiveSpan, SpanStatusCode } from 'autotel';\n *\n * const span = getActiveSpan();\n * if (span?.isRecording()) {\n *   span.setStatus({ code: SpanStatusCode.OK });\n * }\n * ```\n */\nexport function getActiveSpan(): Span | undefined {\n  return trace.getActiveSpan();\n}\n\n/**\n * Get the currently active OpenTelemetry context\n *\n * The context contains the active span and any baggage.\n * Useful for context propagation and custom instrumentation.\n *\n * @returns Current active context\n *\n * @example Propagating context\n * ```typescript\n * import { getActiveContext } from 'autotel';\n *\n * const currentContext = getActiveContext();\n * // Pass context to another function or service\n * ```\n *\n * @example With context injection\n * ```typescript\n * import { getActiveContext, injectTraceContext } from 'autotel';\n *\n * const headers = {};\n * injectTraceContext(headers);\n * // Headers now contain trace propagation data\n * ```\n */\nexport function getActiveContext(): Context {\n  // Check stored context first (from baggage setters), then fall back to active context\n  // This ensures ctx.setBaggage() changes are visible to OpenTelemetry operations\n  try {\n    const { getActiveContextWithBaggage } = requireModule<{\n      getActiveContextWithBaggage: () => Context;\n    }>('./trace-context');\n    return getActiveContextWithBaggage();\n  } catch {\n    // Fallback if trace-context isn't available\n    return context.active();\n  }\n}\n\n/**\n * Run a function with a specific span set as active\n *\n * This is a convenience wrapper around the two-step process of\n * setting a span in context and running code within that context.\n *\n * @param span - The span to set as active\n * @param fn - Function to execute with the span active\n * @returns The return value of the function\n *\n * @example Running code with a custom span\n * ```typescript\n * import { getTracer, runWithSpan } from 'autotel';\n *\n * const tracer = getTracer('my-service');\n * const span = tracer.startSpan('background.job');\n *\n * try {\n *   const result = await runWithSpan(span, async () => {\n *     // Any spans created here will be children of 'background.job'\n *     await processData();\n *     return { success: true };\n *   });\n *   console.log(result);\n * } finally {\n *   span.end();\n * }\n * ```\n *\n * @example Testing with custom spans\n * ```typescript\n * import { runWithSpan, otelTrace } from 'autotel';\n *\n * const tracer = otelTrace.getTracer('test');\n * const span = tracer.startSpan('test.operation');\n *\n * const result = runWithSpan(span, () => {\n *   // Code under test runs with this span as active\n *   return myFunction();\n * });\n *\n * span.end();\n * ```\n */\nexport function runWithSpan<T>(span: Span, fn: () => T): T {\n  const ctx = trace.setSpan(context.active(), span);\n  return context.with(ctx, fn);\n}\n\n/**\n * Finalize a span with appropriate status and optional error recording\n *\n * This is a convenience function that:\n * - Records exceptions if an error is provided\n * - Sets span status to ERROR if error exists, OK otherwise\n * - Ends the span\n *\n * @param span - The span to finalize\n * @param error - Optional error to record\n *\n * @example Without error (success case)\n * ```typescript\n * import { getTracer, finalizeSpan } from 'autotel';\n *\n * const tracer = getTracer('my-service');\n * const span = tracer.startSpan('operation');\n *\n * try {\n *   await doWork();\n *   finalizeSpan(span);\n * } catch (error) {\n *   finalizeSpan(span, error);\n *   throw error;\n * }\n * ```\n *\n * @example With error\n * ```typescript\n * import { getTracer, finalizeSpan } from 'autotel';\n *\n * const tracer = getTracer('my-service');\n * const span = tracer.startSpan('operation');\n *\n * try {\n *   await riskyOperation();\n *   finalizeSpan(span);\n * } catch (error) {\n *   finalizeSpan(span, error); // Records exception and sets ERROR status\n *   throw error;\n * }\n * ```\n *\n * @example In instrumentation\n * ```typescript\n * import { getTracer, runWithSpan, finalizeSpan } from 'autotel';\n *\n * function instrumentedQuery(query: string) {\n *   const tracer = getTracer('db');\n *   const span = tracer.startSpan('db.query');\n *\n *   return runWithSpan(span, () => {\n *     try {\n *       const result = executeQuery(query);\n *       finalizeSpan(span);\n *       return result;\n *     } catch (error) {\n *       finalizeSpan(span, error);\n *       throw error;\n *     }\n *   });\n * }\n * ```\n */\nexport function finalizeSpan(span: Span, error?: unknown): void {\n  if (error) {\n    if (error instanceof Error) {\n      span.recordException(error);\n    } else {\n      span.recordException(new Error(String(error)));\n    }\n    span.setStatus({ code: SpanStatusCode.ERROR });\n  } else {\n    span.setStatus({ code: SpanStatusCode.OK });\n  }\n  span.end();\n}\n\n/**\n * Creates a deterministic trace ID from a seed string.\n *\n * Generates a consistent 128-bit trace ID (32 hex characters) from an input seed\n * using SHA-256 hashing. Useful for correlating external system IDs (request IDs,\n * order IDs, session IDs) with OpenTelemetry trace IDs.\n *\n * **Use Cases:**\n * - Correlate external request IDs with traces\n * - Link customer support tickets to trace data\n * - Associate business entities (orders, sessions) with observability data\n * - Debug specific user flows by deterministic trace lookup\n *\n * **Important:** Only use this when you need deterministic trace IDs for correlation.\n * For normal tracing, let OpenTelemetry generate random trace IDs automatically.\n *\n * **Runtime Support:**\n * - Node.js 15+ (native crypto.subtle)\n * - All modern browsers\n * - Edge runtimes (Cloudflare Workers, Deno, etc.)\n *\n * @param seed - Input string to generate trace ID from (e.g., request ID, order ID)\n * @returns Promise resolving to a 32-character hex trace ID (128 bits)\n *\n * @example Correlate external request ID with trace\n * ```typescript\n * import { createDeterministicTraceId } from 'autotel/trace-helpers'\n * import { trace, context } from '@opentelemetry/api'\n *\n * // In middleware or request handler\n * const requestId = req.headers['x-request-id']\n * const traceId = await createDeterministicTraceId(requestId)\n *\n * // Use with manual span creation (advanced - not needed with trace/span functions)\n * const tracer = trace.getTracer('my-service')\n * const spanContext = {\n *   traceId,\n *   spanId: '0123456789abcdef', // Still random\n *   traceFlags: 1\n * }\n * ```\n *\n * @example Link customer support tickets to traces\n * ```typescript\n * import { createDeterministicTraceId } from 'autotel/trace-helpers'\n *\n * // Support dashboard integration\n * const ticketId = 'TICKET-12345'\n * const traceId = await createDeterministicTraceId(ticketId)\n *\n * // Generate direct link to traces in observability backend\n * const traceUrl = `https://your-otel-backend.com/traces/${traceId}`\n * console.log(`View related traces: ${traceUrl}`)\n * ```\n *\n * @example Session-based correlation\n * ```typescript\n * import { createDeterministicTraceId } from 'autotel/trace-helpers'\n *\n * // Track all operations for a user session\n * const sessionId = req.session.id\n * const traceId = await createDeterministicTraceId(sessionId)\n *\n * // All operations in this session share the same trace ID\n * // Makes it easy to find all activity for a specific session\n * ```\n *\n * @public\n */\nexport async function createDeterministicTraceId(\n  seed: string,\n): Promise<string> {\n  // Encode seed string to bytes\n  const encoder = new TextEncoder();\n  const data = encoder.encode(seed);\n\n  // Generate SHA-256 hash (256 bits)\n  const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n\n  // Convert to hex string and truncate to 32 characters (128 bits)\n  // OpenTelemetry trace IDs are 128 bits (16 bytes, 32 hex characters)\n  const hashArray = new Uint8Array(hashBuffer);\n  return [...hashArray]\n    .map((byte) => byte.toString(16).padStart(2, '0'))\n    .join('')\n    .slice(0, 32);\n}\n\n/**\n * Flattens nested metadata objects into dot-notation span attributes.\n *\n * Converts complex nested objects into flat key-value pairs suitable for\n * OpenTelemetry span attributes. Non-string values are JSON serialized.\n * Handles serialization failures gracefully with a fallback value.\n *\n * **Use Cases:**\n * - Structured metadata with nested objects\n * - User context with multiple properties\n * - Request/response metadata\n * - Business entity attributes\n *\n * **Note:** Filters out null/undefined values automatically to keep spans clean.\n *\n * @param metadata - Nested metadata object to flatten\n * @param prefix - Prefix for all attribute keys (default: 'metadata')\n * @returns Flattened attributes as { [key: string]: string }\n *\n * @example Basic metadata flattening\n * ```typescript\n * import { flattenMetadata } from 'autotel/trace-helpers'\n * import { trace } from 'autotel'\n *\n * export const processOrder = trace(ctx => async (orderId: string) => {\n *   const order = await getOrder(orderId)\n *\n *   // Flatten complex order metadata\n *   const flattened = flattenMetadata({\n *     user: { id: order.userId, tier: 'premium' },\n *     payment: { method: 'card', processor: 'stripe' },\n *     items: order.items.length\n *   })\n *\n *   ctx.setAttributes(flattened)\n *   // Results in:\n *   // {\n *   //   'metadata.user.id': 'user-123',\n *   //   'metadata.user.tier': 'premium',\n *   //   'metadata.payment.method': 'card',\n *   //   'metadata.payment.processor': 'stripe',\n *   //   'metadata.items': '5'\n *   // }\n * })\n * ```\n *\n * @example Custom prefix for semantic conventions\n * ```typescript\n * import { flattenMetadata } from 'autotel/trace-helpers'\n * import { trace } from 'autotel'\n *\n * export const fetchUser = trace(ctx => async (userId: string) => {\n *   const user = await db.users.findOne({ id: userId })\n *\n *   // Use semantic convention prefix\n *   const userAttrs = flattenMetadata(\n *     {\n *       id: user.id,\n *       email: user.email,\n *       plan: user.subscription.plan\n *     },\n *     'user'  // Custom prefix\n *   )\n *\n *   ctx.setAttributes(userAttrs)\n *   // Results in:\n *   // {\n *   //   'user.id': 'user-123',\n *   //   'user.email': 'user@example.com',\n *   //   'user.plan': 'enterprise'\n *   // }\n * })\n * ```\n *\n * @example With complex objects (auto-serialized)\n * ```typescript\n * import { flattenMetadata } from 'autotel/trace-helpers'\n * import { trace } from 'autotel'\n *\n * export const analyzeRequest = trace(ctx => async (req: Request) => {\n *   const metadata = flattenMetadata({\n *     headers: req.headers,  // Object - will be JSON serialized\n *     query: req.query,       // Object - will be JSON serialized\n *     timestamp: new Date()   // Non-string - will be JSON serialized\n *   })\n *\n *   ctx.setAttributes(metadata)\n *   // Results in:\n *   // {\n *   //   'metadata.headers': '{\"accept\":\"application/json\",...}',\n *   //   'metadata.query': '{\"page\":\"1\",\"limit\":\"10\"}',\n *   //   'metadata.timestamp': '\"2024-01-15T12:00:00.000Z\"'\n *   // }\n * })\n * ```\n *\n * @example Error handling\n * ```typescript\n * import { flattenMetadata } from 'autotel/trace-helpers'\n *\n * // Objects with circular references are handled gracefully\n * const circular: any = { a: 1 }\n * circular.self = circular\n *\n * const flattened = flattenMetadata({ data: circular })\n * // Results in:\n * // { 'metadata.data': '<serialization-failed>' }\n * ```\n *\n * @public\n */\n/**\n * Resolve a trace URL from a template string and trace ID.\n *\n * Templates use `{traceId}` as placeholder. Falls back to `OTEL_TRACE_URL_TEMPLATE` env var.\n *\n * @example\n * resolveTraceUrl('https://grafana.example.com/explore?traceId={traceId}', 'abc123')\n * // => 'https://grafana.example.com/explore?traceId=abc123'\n */\nexport function resolveTraceUrl(\n  template: string | undefined,\n  traceId: string,\n): string | undefined {\n  const t = template ?? process.env.OTEL_TRACE_URL_TEMPLATE;\n  if (!t) return undefined;\n  return t.replace(/\\{traceId\\}/g, traceId);\n}\n\nexport function flattenMetadata(\n  metadata: Record<string, unknown>,\n  prefix = 'metadata',\n): Record<string, string> {\n  const flattened: Record<string, string> = {};\n  const seen = new WeakSet<object>(); // Track visited objects to detect cycles\n\n  function flatten(obj: Record<string, unknown>, currentPrefix: string): void {\n    for (const [key, value] of Object.entries(obj)) {\n      // Skip null/undefined values\n      if (value == null) continue;\n\n      const attributeKey = `${currentPrefix}.${key}`;\n\n      // Handle primitives directly (string, number, boolean)\n      if (typeof value === 'string') {\n        flattened[attributeKey] = value;\n        continue;\n      }\n      if (typeof value === 'number' || typeof value === 'boolean') {\n        flattened[attributeKey] = String(value);\n        continue;\n      }\n\n      // Recursively flatten plain objects (with cycle detection)\n      if (\n        typeof value === 'object' &&\n        value !== null &&\n        value.constructor === Object\n      ) {\n        // Detect circular references\n        if (seen.has(value)) {\n          flattened[attributeKey] = '<circular-reference>';\n          continue;\n        }\n\n        // Mark as visited and recursively flatten\n        seen.add(value);\n        flatten(value as Record<string, unknown>, attributeKey);\n        continue;\n      }\n\n      // Serialize arrays and other non-plain objects to JSON\n      try {\n        flattened[attributeKey] = JSON.stringify(value);\n      } catch {\n        // Handle circular references or non-serializable objects\n        flattened[attributeKey] = '<serialization-failed>';\n      }\n    }\n  }\n\n  flatten(metadata, prefix);\n  return flattened;\n}\n"]}