{"version":3,"sources":["../src/pretty-console-exporter.ts"],"names":["SpanStatusCode"],"mappings":"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","file":"chunk-CU6IDACR.cjs","sourcesContent":["/**\n * Pretty Console Exporter\n *\n * A developer-friendly span exporter that displays colorized, hierarchical\n * trace output in the terminal. Zero external dependencies - uses ANSI escape codes.\n *\n * @example Basic usage\n * ```typescript\n * init({\n *   service: 'my-app',\n *   debug: 'pretty'  // Uses PrettyConsoleExporter\n * })\n * ```\n *\n * @example Explicit usage with options\n * ```typescript\n * import { PrettyConsoleExporter } from 'autotel/exporters'\n *\n * init({\n *   service: 'my-app',\n *   spanExporters: [new PrettyConsoleExporter({\n *     colors: true,\n *     showAttributes: true,\n *     hideAttributes: ['http.user_agent']\n *   })]\n * })\n * ```\n */\n\nimport type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport { SpanStatusCode } from '@opentelemetry/api';\n\n/**\n * Export result code constants (avoid importing @opentelemetry/core)\n */\nconst ExportResultCode = {\n  SUCCESS: 0,\n  FAILED: 1,\n} as const;\n\n/**\n * Export result type for SpanExporter callback\n */\ninterface ExportResult {\n  code: number;\n  error?: Error;\n}\n\n/**\n * ANSI escape codes for terminal colors (zero dependencies)\n */\nconst ANSI = {\n  reset: '\\u001B[0m',\n  bold: '\\u001B[1m',\n  dim: '\\u001B[2m',\n  green: '\\u001B[32m',\n  red: '\\u001B[31m',\n  yellow: '\\u001B[33m',\n  blue: '\\u001B[34m',\n  cyan: '\\u001B[36m',\n  gray: '\\u001B[90m',\n} as const;\n\ntype AnsiColor = keyof typeof ANSI;\n\n/**\n * Configuration options for PrettyConsoleExporter\n */\nexport interface PrettyConsoleExporterOptions {\n  /**\n   * Enable ANSI colors in output\n   * @default auto-detect TTY\n   */\n  colors?: boolean;\n\n  /**\n   * Show span attributes in output\n   * @default true\n   */\n  showAttributes?: boolean;\n\n  /**\n   * Maximum length for attribute values before truncation\n   * @default 50\n   */\n  maxValueLength?: number;\n\n  /**\n   * Show instrumentation scope name (e.g., [http], [pg])\n   * @default true\n   */\n  showScope?: boolean;\n\n  /**\n   * Attribute keys to always hide from output\n   * @default []\n   */\n  hideAttributes?: string[];\n\n  /**\n   * Show trace ID for each root span\n   * @default false\n   */\n  showTraceId?: boolean;\n}\n\n/**\n * Internal node structure for building span trees\n */\ninterface SpanNode {\n  span: ReadableSpan;\n  children: SpanNode[];\n}\n\n/**\n * Pretty Console Exporter - colorized, hierarchical span output for development\n *\n * Features:\n * - Colorized status indicators (✓ green, ✗ red)\n * - Duration with color coding (fast=green, medium=yellow, slow=red)\n * - Hierarchical tree view showing parent-child relationships\n * - Attribute display with truncation\n * - Error message highlighting\n */\nexport class PrettyConsoleExporter implements SpanExporter {\n  private readonly options: Required<PrettyConsoleExporterOptions>;\n\n  constructor(options: PrettyConsoleExporterOptions = {}) {\n    this.options = {\n      colors: options.colors ?? process.stdout?.isTTY ?? false,\n      showAttributes: options.showAttributes ?? true,\n      maxValueLength: options.maxValueLength ?? 50,\n      showScope: options.showScope ?? true,\n      hideAttributes: options.hideAttributes ?? [],\n      showTraceId: options.showTraceId ?? false,\n    };\n  }\n\n  /**\n   * Export spans with pretty formatting\n   */\n  export(\n    spans: ReadableSpan[],\n    resultCallback: (result: ExportResult) => void,\n  ): void {\n    if (spans.length === 0) {\n      resultCallback({ code: ExportResultCode.SUCCESS });\n      return;\n    }\n\n    try {\n      // Group spans by trace ID\n      const traceGroups = this.groupByTrace(spans);\n\n      // Print each trace group\n      for (const [traceId, traceSpans] of traceGroups) {\n        this.printTrace(traceId, traceSpans);\n      }\n\n      resultCallback({ code: ExportResultCode.SUCCESS });\n    } catch {\n      // Fail-open: don't crash the app if formatting fails\n      resultCallback({ code: ExportResultCode.SUCCESS });\n    }\n  }\n\n  /**\n   * Group spans by their trace ID\n   */\n  private groupByTrace(spans: ReadableSpan[]): Map<string, ReadableSpan[]> {\n    const groups = new Map<string, ReadableSpan[]>();\n\n    for (const span of spans) {\n      const traceId = span.spanContext().traceId;\n      const group = groups.get(traceId) ?? [];\n      group.push(span);\n      groups.set(traceId, group);\n    }\n\n    return groups;\n  }\n\n  /**\n   * Print a single trace with all its spans as a tree\n   */\n  private printTrace(traceId: string, spans: ReadableSpan[]): void {\n    // Sort by start time\n    const sorted = [...spans].toSorted((a, b) => {\n      const aTime = hrTimeToMs(a.startTime);\n      const bTime = hrTimeToMs(b.startTime);\n      return aTime - bTime;\n    });\n\n    // Build tree structure\n    const tree = this.buildSpanTree(sorted);\n\n    // Print trace ID header if enabled\n    if (this.options.showTraceId && tree.length > 0) {\n      console.log(this.color(`trace: ${traceId}`, 'gray'));\n    }\n\n    // Print each root span and its children\n    for (const node of tree) {\n      this.printNode(node, 0, false);\n    }\n\n    // Add blank line between traces\n    console.log('');\n  }\n\n  /**\n   * Build a tree structure from flat spans using parent-child relationships\n   */\n  private buildSpanTree(spans: ReadableSpan[]): SpanNode[] {\n    const spanMap = new Map<string, SpanNode>();\n    const roots: SpanNode[] = [];\n\n    // Create nodes for all spans\n    for (const span of spans) {\n      const spanId = span.spanContext().spanId;\n      spanMap.set(spanId, { span, children: [] });\n    }\n\n    // Build parent-child relationships\n    for (const span of spans) {\n      const spanId = span.spanContext().spanId;\n      const parentId = span.parentSpanContext?.spanId;\n      const node = spanMap.get(spanId)!;\n\n      if (parentId && spanMap.has(parentId)) {\n        // Has parent in this batch - add as child\n        spanMap.get(parentId)!.children.push(node);\n      } else {\n        // No parent or parent not in batch - treat as root\n        roots.push(node);\n      }\n    }\n\n    return roots;\n  }\n\n  /**\n   * Print a span node with indentation and tree characters\n   */\n  private printNode(node: SpanNode, depth: number, isLast: boolean): void {\n    const { span } = node;\n\n    // Build tree prefix\n    const prefix =\n      depth === 0 ? '' : '  '.repeat(depth - 1) + (isLast ? '└─ ' : '├─ ');\n\n    // Status indicator\n    const isError = span.status.code === SpanStatusCode.ERROR;\n    const statusChar = isError ? '✗' : '✓';\n    const statusColor: AnsiColor = isError ? 'red' : 'green';\n\n    // Duration formatting\n    const durationMs = hrTimeToMs(span.duration);\n    const durationStr = formatDuration(durationMs);\n    const durationColor = getDurationColor(durationMs);\n\n    // Scope name (instrumentation library)\n    const scopeName = this.options.showScope\n      ? this.color(` [${this.getScopeName(span)}]`, 'gray')\n      : '';\n\n    // Build the main line\n    const line = [\n      prefix,\n      this.color(statusChar, statusColor),\n      ' ',\n      span.name.padEnd(Math.max(35 - prefix.length, 10)),\n      this.color(durationStr.padStart(8), durationColor),\n      scopeName,\n    ].join('');\n\n    console.log(line);\n\n    // Print attributes on next line (indented)\n    if (this.options.showAttributes) {\n      const attrs = this.formatAttributes(span);\n      if (attrs) {\n        const attrIndent = '  '.repeat(depth) + '     ';\n        console.log(this.color(`${attrIndent}${attrs}`, 'dim'));\n      }\n    }\n\n    // Print error message if present\n    if (isError && span.status.message) {\n      const errorIndent = '  '.repeat(depth) + '     ';\n      console.log(\n        this.color(`${errorIndent}Error: ${span.status.message}`, 'red'),\n      );\n    }\n\n    // Print children\n    const childCount = node.children.length;\n    let index = 0;\n    for (const child of node.children) {\n      this.printNode(child, depth + 1, index === childCount - 1);\n      index++;\n    }\n  }\n\n  /**\n   * Get short scope name from instrumentation scope\n   */\n  private getScopeName(span: ReadableSpan): string {\n    const name = span.instrumentationScope?.name ?? 'unknown';\n    // Extract short name from @opentelemetry/instrumentation-xxx format\n    const match = name.match(/@opentelemetry\\/instrumentation-(.+)/);\n    if (match?.[1]) return match[1];\n    // Fall back to last part of name or full name\n    const lastPart = name.split('/').at(-1);\n    return lastPart ?? name;\n  }\n\n  /**\n   * Format span attributes as a comma-separated string\n   */\n  private formatAttributes(span: ReadableSpan): string {\n    const attrs = span.attributes;\n    if (!attrs || Object.keys(attrs).length === 0) {\n      return '';\n    }\n\n    const pairs: string[] = [];\n    for (const [key, value] of Object.entries(attrs)) {\n      // Skip hidden attributes\n      if (this.options.hideAttributes.includes(key)) continue;\n\n      // Skip undefined/null values\n      if (value === undefined || value === null) continue;\n\n      // Format value\n      const strValue = this.truncate(\n        Array.isArray(value) ? `[${value.join(', ')}]` : String(value),\n        this.options.maxValueLength,\n      );\n      pairs.push(`${key}=${strValue}`);\n    }\n\n    return pairs.join(', ');\n  }\n\n  /**\n   * Truncate string to max length with ellipsis\n   */\n  private truncate(str: string, max: number): string {\n    if (str.length <= max) return str;\n    return str.slice(0, max - 3) + '...';\n  }\n\n  /**\n   * Apply ANSI color if colors are enabled\n   */\n  private color(text: string, color: AnsiColor): string {\n    if (!this.options.colors) return text;\n    return `${ANSI[color]}${text}${ANSI.reset}`;\n  }\n\n  /**\n   * Shutdown (no-op for console exporter)\n   */\n  shutdown(): Promise<void> {\n    return Promise.resolve();\n  }\n\n  /**\n   * Force flush (no-op for console exporter)\n   */\n  forceFlush(): Promise<void> {\n    return Promise.resolve();\n  }\n}\n\n/**\n * Convert HrTime [seconds, nanoseconds] to milliseconds\n */\nfunction hrTimeToMs(hrTime: [number, number]): number {\n  const [seconds, nanos] = hrTime;\n  return seconds * 1000 + nanos / 1_000_000;\n}\n\n/**\n * Format duration with appropriate units\n */\nfunction formatDuration(ms: number): string {\n  if (ms < 1) {\n    // Sub-millisecond: show as microseconds\n    return `${(ms * 1000).toFixed(0)}µs`;\n  }\n  if (ms < 1000) {\n    // Under 1 second: show as milliseconds\n    return `${ms.toFixed(0)}ms`;\n  }\n  // 1 second or more: show as seconds\n  return `${(ms / 1000).toFixed(2)}s`;\n}\n\n/**\n * Get color based on duration (fast=green, medium=yellow, slow=red)\n */\nfunction getDurationColor(ms: number): AnsiColor {\n  if (ms < 100) return 'green';\n  if (ms < 500) return 'yellow';\n  return 'red';\n}\n\n/**\n * Export utility functions for testing\n */\nexport { formatDuration, getDurationColor, hrTimeToMs };\n"]}