Skip to main content

OpenTelemetry (OTel)

CNCF-graduated open-source observability framework for distributed systems. Provides a vendor-neutral standard for collecting and exporting traces, metrics, and logs. Key property: instrument once, export anywhere. Instrumentation code is backend-agnostic; the export destination is a configuration decision, not a code decision. Repo: https://opentelemetry.io
Spec: https://opentelemetry.io/docs/specs/otel/
GenAI SIG: https://github.com/open-telemetry/semantic-conventions/tree/main/docs/gen-ai

Three Signals

Traces — distributed request flows. A trace is one complete user interaction; it contains spans in a parent-child tree. Each span represents one unit of work: an LLM call, a retrieval operation, a tool execution. Metrics — numeric time-series. Counters, histograms, gauges. Aggregate trends (token burn rate, p99 latency, error rate). Logs — structured log records with trace context correlation. For AI systems, prompt/completion content lives here (as span events, not attributes).

Core Concepts

Span

Unit of work in a trace. Contains:
  • trace_id — shared across all spans in one user request
  • span_id — unique to this operation
  • parent_span_id — creates the tree structure
  • name — e.g., "anthropic chat", "vector-retrieval", "agent.tool_call"
  • start_time / end_time
  • attributes — key-value metadata (GenAI attributes go here)
  • statusOK, ERROR, or UNSET
  • events — timestamped moments within a span (e.g., gen_ai.user.message)

TracerProvider

Global singleton that creates Tracer instances. Configure once at startup with: resource (service name/version), span processors, and exporters. Must call instrument() on auto-instrumentation packages before creating any SDK clients.

Collector

Standalone process (sidecar or gateway) that receives spans, applies processors (sampling, enrichment, batching), and routes to multiple backends. Decouples application from observability backends. Two modes:
  • Agent: runs alongside each app instance, low latency, good for smaller deployments
  • Gateway: centralized tier, enables sophisticated routing and tail sampling, preferred at scale

Context Propagation

W3C traceparent header carries {version}-{trace_id}-{parent_span_id}-{flags} across HTTP service boundaries. When HTTP clients are instrumented (HTTPXClientInstrumentor, RequestsInstrumentor), propagation is automatic. For message queues (Kafka, Celery, SQS): must manually inject(headers) at producer and extract(headers) at consumer — queues don’t use HTTP headers.

GenAI Semantic Conventions

Defined by the OTel GenAI Special Interest Group. Status: incubating (experimental but rapidly stabilizing as of 2026). Organizations should use abstraction layers to insulate against attribute changes.

Key Attributes

Standard Metric Names

Instrumentation Approaches

1. Auto-Instrumentation

Fastest path. Wraps SDK calls automatically without modifying business logic.
Call instrument() at startup, before creating any SDK clients:
Content capture is off by default. Enable selectively:

2. Manual SDK Instrumentation

Required for: custom retrieval steps, tool calls, evaluation scores, business logic, or any framework without an existing instrumentor.

3. OpenLLMetry (traceloop-sdk)

Third-party wrapper with broader framework support and simpler initialization:

4. OpenLIT

Zero-code CLI option; TypeScript support; broadest framework coverage including newer frameworks (AG2, Dynamiq, Mem0):

Collector Configuration (Minimal)

Sampling

Head sampling — decision at trace start, before any work. TraceIdRatioBased(0.10) = keep 10% randomly. Fast, low overhead, but can’t consider outcome (drops errors at same rate as successes). Tail sampling — decision after trace completes. Buffer all spans for decision_wait seconds, then decide based on: error status, latency threshold, user tier, attribute values. Higher memory cost but captures what matters. Always use for AI production workloads.

Compatible Backends

OTel exports to any OTLP-compatible backend via gRPC (port 4317) or HTTP (port 4318):
  • Arize Phoenix — local dev, AI-specific analysis, UMAP embeddings visualization
  • LangSmith — AI observability, prompt versioning, dataset curation (OTel endpoint available)
  • Langfuse — open-source AI observability alternative to LangSmith
  • Grafana Tempo — open-source distributed tracing backend
  • Jaeger — open-source tracing, good for local dev
  • Datadog — enterprise APM with GenAI semantic convention support
  • Honeycomb — high-cardinality event analytics
  • Dynatrace — enterprise APM, OTLP ingestion, AI observability app
  • Uptrace — open-source APM on ClickHouse, native gen_ai.* support
Strategic advantage: switching backends requires only Collector config changes, not application code rewrites.

Python SDK Setup (Minimal)

Critical for serverless: call provider.force_flush() and provider.shutdown() before process exit. BatchSpanProcessor buffers async — Lambda functions may terminate before flush, silently dropping all traces.

Language Support

  • Python: opentelemetry-sdk, OpenInference instrumentors for Anthropic/OpenAI/LangChain/LlamaIndex
  • JavaScript/TypeScript: @opentelemetry/sdk-node, OpenInference JS instrumentors
  • Go: go.opentelemetry.io/otel
  • Java: io.opentelemetry:opentelemetry-sdk (OpenInference has strong Java support)
  • Ruby: OpenLLMetry supports Ruby

Common Pitfalls

  • Don’t send to multiple backends directly from app — use Collector for routing/fan-out; direct multi-export adds latency and coupling
  • Don’t store prompt content in span attributes — always indexed, no size limit, PII risk; use span events
  • Don’t use custom attribute names — use gen_ai.* conventions or pre-built dashboards won’t work
  • Don’t forget to instrument HTTP clients — context propagation breaks at uninstrumented boundaries
  • Thread pool workers don’t inherit context — capture otel_context.get_current() in main thread, pass to workers, always detach(token) in finally