Skip to content

How to Set Up OpenTelemetry

Key idea:

OpenTelemetry (OTel) — CNCF standard for unified observability (traces + metrics + logs). Replaces Jaeger / Zipkin-specific + Prometheus-specific code. 2026: auto-instrumentation for Node/Python/Java covers 90% of frameworks without code. Architecture: SDK in app → OTel Collector → backend (Jaeger, Grafana Tempo, Datadog, Honeycomb). Setup: 30 minutes for basic tracing.

Below: step-by-step, working examples, common pitfalls, FAQ.

Try it now — free →

Step-by-Step Setup

  1. Install SDK: npm install @opentelemetry/api @opentelemetry/sdk-node
  2. Enable auto-instrumentation for your framework (Express, FastAPI, Prisma)
  3. Configure exporter: OTLP (standard) to OTel Collector
  4. Deploy Collector as sidecar or standalone
  5. Configure backend (Jaeger — free, Grafana Tempo — free, Datadog — paid)
  6. Propagate trace context via HTTP headers (baggage + traceparent)
  7. Sample — not 100% (cost). 1-5% + errors 100%

Working Examples

ScenarioConfig
Node.js auto-instrument// tracing.js — load first const { NodeSDK } = require('@opentelemetry/sdk-node'); const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node'); const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); const sdk = new NodeSDK({ traceExporter: new OTLPTraceExporter({ url: 'http://otel-collector:4318/v1/traces' }), instrumentations: [ getNodeAutoInstrumentations() ] }); sdk.start();
Start app$ node -r ./tracing.js server.js # Or as preload: $ NODE_OPTIONS="--require ./tracing.js" node server.js
Custom spanconst { trace } = require('@opentelemetry/api'); const tracer = trace.getTracer('my-app'); async function processOrder(orderId) { return tracer.startActiveSpan('processOrder', async (span) => { span.setAttribute('order.id', orderId); try { const result = await doWork(orderId); span.setStatus({ code: 1 }); return result; } finally { span.end(); } }); }
OTel Collector config# otel-collector-config.yaml receivers: otlp: protocols: { http: {}, grpc: {} } exporters: jaeger: { endpoint: jaeger:14250, tls: { insecure: true } } prometheus: { endpoint: 0.0.0.0:8889 } service: pipelines: traces: { receivers: [otlp], exporters: [jaeger] } metrics: { receivers: [otlp], exporters: [prometheus] }
Python setup$ pip install opentelemetry-distro[otlp] $ opentelemetry-bootstrap -a install $ opentelemetry-instrument \ --traces_exporter otlp \ --service_name my-service \ python app.py

Common Pitfalls

  • Sampling = 100% in prod → huge storage cost. 1% tail-based (keep slow traces) balances
  • Missing context propagation — traces fragment to single-service. Use propagators for all HTTP/gRPC clients
  • Collector down → lose spans. Configure retry + queue in SDK
  • Too many attributes per span (full request body) — blow up storage. Hash / redact sensitive data
  • OTel-LGTM (Grafana stack) is free, but maintenance. Managed (Grafana Cloud, Honeycomb) simpler

Learn more

Frequently Asked Questions

OTel or vendor-specific SDK (Datadog APM)?

OTel: vendor-neutral, migrate backends easily. Datadog APM: deeper integration but vendor lock. For new projects — OTel, for Datadog shops — native.

Traces vs Metrics vs Logs?

Traces: request flow across services. Metrics: aggregated numbers (CPU, requests/sec). Logs: discrete events. OTel unifies all three.

Cost?

Self-host Grafana Tempo (free) + S3 storage ~$10/mo for a small app. Datadog $15/host/mo. Honeycomb $70+/mo pro tier.

Monitor endpoint?

<a href="/en/check">Enterno HTTP checker</a> for backend health. <a href="/en/monitors">Scheduled monitoring</a> for uptime SLA.