IAnswerable

OpenTelemetry Without the Overwhelm

16 Feb 2026

OpenTelemetry’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

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.