OpenTelemetry Without the Overwhelm
16 Feb 2026OpenTelemetry’s full documentation covers distributed tracing across dozens of languages, multiple exporters, and a collector architecture designed for organizations running hundreds of services. If you run one service and want to see why a specific request took 800ms, almost none of that applies to you yet. Here’s the smallest setup that’s actually useful.
What you actually want
One question, answered: for a slow request, which part was slow — the database query, an external API call, or your own code? Tracing answers that by wrapping each meaningful operation in a “span” with a start and end time, nested under the request’s overall span.
The smallest useful setup
Auto-instrumentation, one service, exporting to a free self-hosted backend. Skip the OpenTelemetry Collector entirely at this scale — it’s a separate process meant for routing telemetry across many services, and with one service it’s a component you’re running for no benefit yet.
1. Install the auto-instrumentation packages (Node example)
npm install @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http
2. Set it up before your app code loads
// tracing.js — must be required before anything else
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://localhost:4318/v1/traces' }),
instrumentations: [getNodeAutoInstrumentations()],
})
sdk.start()
node -r ./tracing.js app.js
getNodeAutoInstrumentations() automatically wraps common libraries — your HTTP framework, your database client, outgoing fetch/axios calls — with spans. You don’t write span code by hand for the common cases; this is most of the value for the least effort.
3. Run a backend to actually look at the traces
Grafana Tempo or Jaeger, both self-hostable, both free:
docker run -d --name jaeger \
-p 16686:16686 -p 4318:4318 \
jaegertracing/all-in-one:latest
Point your exporter at http://localhost:4318/v1/traces (matches the config above), then open http://localhost:16686 to see traces. Pick any slow request in the UI and you get a waterfall: HTTP handler → database query (200ms) → external API call (550ms) → response. The bottleneck is visible without adding a single manual timing log.
Adding your own spans, once auto-instrumentation isn’t enough
Auto-instrumentation covers library boundaries. It doesn’t know that your own 40-line calculateShipping() function is the slow part. Wrap what you actually need visibility into:
const { trace } = require('@opentelemetry/api')
const tracer = trace.getTracer('myapp')
async function calculateShipping(order) {
return tracer.startActiveSpan('calculateShipping', async (span) => {
try {
const result = await doExpensiveCalculation(order)
return result
} finally {
span.end()
}
})
}
Add these opportunistically, for the specific functions you’ve had to guess about during a real slow-request investigation — not preemptively across your whole codebase.
What to skip until you have more than one service
- The OpenTelemetry Collector. Useful once you’re aggregating telemetry from multiple services and need to batch, sample, or route it before it hits a backend. With one service, export directly to your backend.
- Distributed context propagation across services. This is the actual “distributed” part of distributed tracing — passing trace IDs across service boundaries via headers. Irrelevant with one service; there’s no boundary to cross yet.
- Metrics and logs correlation via OTel. OpenTelemetry does more than traces, but adding all three (traces, metrics, logs) on day one is more surface area than you need to answer “why was this request slow.”
Takeaway
Auto-instrumentation plus Jaeger or Tempo, no Collector, gets you request-level tracing in under an hour and answers the question that actually matters early: which part of this request was slow. Add manual spans and the Collector later, when you have specific gaps or a second service to correlate across.