"Something is slow" is the least actionable sentence in operations. Is it the app? The database? The network between them? OpenTelemetry (OTel) is how you stop guessing: a vendor-neutral standard for collecting traces, metrics, and logs from your applications — so you can see exactly where time and errors go.
It has become the default instrumentation layer for new projects, and for good reason: you instrument once and send data to Jaeger, Grafana Tempo, Prometheus, Datadog, or whatever backend you choose. No vendor lock-in at the SDK level.
The three signals, in plain English
- Traces — the path of a single request through your system: which services it touched and how long each step took. This is what answers "slow where?"
- Metrics — numbers over time: request rate, error rate, latency percentiles, memory. This is what dashboards and alerts are made of.
- Logs — the events you already know, but correlated with traces so you can jump from "this slow request" to its exact log lines.
You don't need all three on day one. Start with traces — they deliver the biggest "oh, that's the problem" moments.
The architecture in 30 seconds
Your app uses an OTel SDK (or auto-instrumentation) to produce telemetry. It exports to the OpenTelemetry Collector — a small agent that receives, batches, and forwards data to your backend. The Collector is the key piece: it keeps your app config trivial and your backend choices flexible.
App (SDK) → OTel Collector → Jaeger / Grafana / Prometheus / vendor
A minimal working setup with Docker
Run Jaeger (an all-in-one trace backend with a UI) and the Collector:
services:
otel-collector:
image: otel/opentelemetry-collector:latest
command: ["--config=/etc/otelcol/config.yaml"]
volumes:
- ./otelcol-config.yaml:/etc/otelcol/config.yaml
ports:
- "4318:4318" # OTLP HTTP receiver
jaeger:
image: jaegertracing/all-in-one:latest
environment:
COLLECTOR_OTLP_ENABLED: "true"
ports:
- "16686:16686" # Jaeger UI
The Collector config (otelcol-config.yaml) receives OTLP and exports to Jaeger:
receivers:
otlp:
protocols:
http:
exporters:
otlp:
endpoint: jaeger:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
exporters: [otlp]
Instrument a Node.js app
Auto-instrumentation covers HTTP, Express, and many libraries out of the box:
npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
// tracing.js — import this BEFORE anything else
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const sdk = new NodeSDK({
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
Point the exporter at the Collector (default is OTLP HTTP on localhost:4318), restart the app, generate some traffic, and open http://localhost:16686. Your requests appear as traces: every HTTP call, every DB query, with durations. That slow endpoint now shows you exactly which hop eats the time.
Python, Go, Java, and .NET have equivalent auto-instrumentation packages — same pattern, same backend.
What to do with it
- Find your slowest endpoint in the trace list, open a trace, look at the longest span. That's your optimization target, with proof.
- Add custom spans around your own expensive operations (image processing, report generation) so they show up too.
- Add metrics later: the same SDKs expose latency and error-rate metrics for dashboards and alerts.
Keep the telemetry bill under control
Tracing every request forever can create more data than you can afford to store, especially once several services are instrumented. Start with a small service and learn what a useful trace looks like. Then configure sampling appropriate to your traffic: enough successful requests to understand normal behavior, while preserving errors and slow outliers where your backend supports it.
Be careful with span attributes and metric labels. Values such as user IDs, full URLs with unique identifiers, or request IDs create high-cardinality data that is expensive to index. Never put passwords, authorization headers, session tokens, or personal request payloads into telemetry just because the instrumentation makes it easy.
The Collector is also part of your production system. Monitor its queue, dropped data, memory use, and exporter errors. If it cannot reach the backend, your application should keep serving users rather than becoming a casualty of the observability pipeline.
The practical takeaway
OpenTelemetry isn't a monitoring product — it's the plumbing that makes any monitoring product useful without locking you in. Start with one service, one Collector, and Jaeger. The first time you watch a slow request break down hop by hop, you'll wonder how you ever debugged with logs alone.
Top comments (0)