# span-utils ## Purpose Provides ergonomic helper functions for working with OpenTelemetry spans. Rather than writing the same try/catch/finally/recordException/setStatus boilerplate in every traced function, callers use `withSpan` to declaratively wrap work in a span with correct error handling built in. These utilities depend only on `@opentelemetry/api` and are therefore compatible with any OTel SDK implementation (or the no-op fallback), making them safe to import in library code that should not force a specific SDK on its consumers. ## Features - `withSpan` — wraps an async function in an active span; records exceptions and ends the span unconditionally - `recordException` — normalizes any thrown value to an Error and records it with ERROR status - `addAttributes` — sets a bulk `Attributes` map on a span in a single call - `getTraceContext` — extracts `traceId`, `spanId`, and `traceFlags` from the currently active span ## Usage: Basic Example ```ts import { withSpan } from "@suaveplan/opentelemetry/span-utils"; import { trace } from "@opentelemetry/api"; const tracer = trace.getTracer("my-service"); const result = await withSpan(tracer, "user.fetch", async (span) => { span.setAttribute("user.id", userId); return await db.findUser(userId); }); ``` ## Usage: Advanced Example ```ts import { withSpan, addAttributes, getTraceContext } from "@suaveplan/opentelemetry/span-utils"; import { trace } from "@opentelemetry/api"; const tracer = trace.getTracer("payment-service", "1.0.0"); async function processPayment(orderId: string, amount: number) { return withSpan( tracer, "payment.process", async (span) => { addAttributes(span, { "payment.order_id": orderId, "payment.amount": amount, "payment.currency": "USD", }); const result = await chargeGateway(orderId, amount); const ctx = getTraceContext(); console.log("trace:", ctx.traceId, "span:", ctx.spanId); return result; }, { attributes: { "payment.initial": true } } ); } ``` ## API Reference ### `withSpan(tracer, name, fn, options?)` ```ts function withSpan( tracer: Tracer, name: string, fn: (span: Span) => Promise, options?: { attributes?: Attributes } ): Promise ``` Starts an active child span, calls `fn` with it, ends the span in `finally`, and records any exception + sets ERROR status before re-throwing. ### `recordException(span, error)` ```ts function recordException(span: Span, error: unknown): void ``` Records `error` on `span` as a span event and sets `SpanStatusCode.ERROR`. Non-Error values are wrapped in `new Error(String(error))` before recording. ### `addAttributes(span, attrs)` ```ts function addAttributes(span: Span, attrs: Attributes): void ``` Sets each key/value pair from `attrs` on the span. `undefined` values are skipped. ### `getTraceContext()` ```ts function getTraceContext(): { traceId?: string; spanId?: string; traceFlags?: number } ``` Returns the active span's context fields. All fields are `undefined` when no span is active. ## Implementation Notes `withSpan` uses `tracer.startActiveSpan` so that child spans created inside `fn` are automatically parented to this span via the context API — no manual context propagation required. The `finally` block ensures `span.end()` is always called even when `fn` throws, which is the single most common OTel correctness bug in hand-written tracing code. `recordException` follows the OTel spec by calling the API's built-in `span.recordException` rather than manually building the event, ensuring the `exception.type`, `exception.message`, and `exception.stacktrace` attributes are populated correctly.