All files / web/src/lib tracing.ts

0% Statements 0/116
0% Branches 0/1
0% Functions 0/1
0% Lines 0/116

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117                                                                                                                                                                                                                                         
/**
 * OpenTelemetry Tracing Utilities
 *
 * This file exports helper functions for working with traces in the application.
 * The OpenTelemetry SDK is initialized via instrumentation.js (loaded with --require).
 *
 * IMPORTANT: @opentelemetry/api is imported lazily (inside functions) to avoid
 * pulling it into the Next.js vendor chunk graph at module scope, which causes
 * "Cannot find module './vendor-chunks/@opentelemetry+api@..." errors in dev.
 *
 * Usage:
 *   import { getCurrentTraceId, recordError } from '@/lib/tracing'
 *
 *   // In error handlers:
 *   const traceId = getCurrentTraceId()
 *   return NextResponse.json({ error: 'Something failed', traceId }, { status: 500 })
 */

type OtelApi = typeof import('@opentelemetry/api')

function getOtel(): OtelApi {
  // eslint-disable-next-line @typescript-eslint/no-require-imports
  return require('@opentelemetry/api')
}

/**
 * Get the current trace ID (useful for error responses)
 */
export function getCurrentTraceId(): string | null {
  const { trace } = getOtel()
  const span = trace.getActiveSpan()
  if (!span) return null
  return span.spanContext().traceId
}

/**
 * Get the current span context for logging
 */
export function getTraceContext(): { traceId: string; spanId: string } | null {
  const { trace } = getOtel()
  const span = trace.getActiveSpan()
  if (!span) return null
  const ctx = span.spanContext()
  return {
    traceId: ctx.traceId,
    spanId: ctx.spanId,
  }
}

/**
 * Add an error to the current span
 */
export function recordError(error: Error, attributes?: Record<string, string>): void {
  const { trace, SpanStatusCode } = getOtel()
  const span = trace.getActiveSpan()
  if (!span) return
  span.recordException(error)
  span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
  if (attributes) {
    span.setAttributes(attributes)
  }
}

/**
 * Create a custom span for tracing a specific operation
 */
export function withSpan<T>(name: string, fn: () => T, attributes?: Record<string, string>): T {
  const { trace, SpanStatusCode } = getOtel()
  const tracer = trace.getTracer('abaci-app')
  return tracer.startActiveSpan(name, (span) => {
    try {
      if (attributes) {
        span.setAttributes(attributes)
      }
      const result = fn()
      span.end()
      return result
    } catch (error) {
      if (error instanceof Error) {
        span.recordException(error)
        span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
      }
      span.end()
      throw error
    }
  })
}

/**
 * Create a custom span for tracing an async operation
 */
export async function withSpanAsync<T>(
  name: string,
  fn: () => Promise<T>,
  attributes?: Record<string, string>
): Promise<T> {
  const { trace, SpanStatusCode } = getOtel()
  const tracer = trace.getTracer('abaci-app')
  return tracer.startActiveSpan(name, async (span) => {
    try {
      if (attributes) {
        span.setAttributes(attributes)
      }
      const result = await fn()
      span.end()
      return result
    } catch (error) {
      if (error instanceof Error) {
        span.recordException(error)
        span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
      }
      span.end()
      throw error
    }
  })
}