Files
skills/docs/observability/monitor/opentelemetry/src/utils/span-utils.md
2026-05-29 08:20:56 +01:00

3.6 KiB

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

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

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?)

function withSpan<T>(
  tracer: Tracer,
  name: string,
  fn: (span: Span) => Promise<T>,
  options?: { attributes?: Attributes }
): Promise<T>

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)

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)

function addAttributes(span: Span, attrs: Attributes): void

Sets each key/value pair from attrs on the span. undefined values are skipped.

getTraceContext()

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.