OpenTelemetry Troubleshooting: No Data and How to Debug the Pipeline
The usual failure is not an error message. Everything starts, nothing crashes, and no data arrives. This page is about locating where it stops.
Work along the pipeline
Telemetry passes through four places, and each can drop it silently:
Check them in order. Confirming that the SDK produces spans takes a minute and rules out half the possibilities; guessing at exporter configuration first can take an afternoon.
Is the application producing anything?
Bypass the network entirely and print to stdout:
export OTEL_TRACES_EXPORTER=console
export OTEL_METRICS_EXPORTER=console
export OTEL_LOGS_EXPORTER=console
If nothing appears, the problem is before any export: no SDK installed, instrumentation not registered, or sampling set to always_off. Nothing downstream matters yet.
If spans appear here but not in the backend, the SDK works and the problem is in transport or in the Collector.
Two SDK-side causes are worth ruling out immediately:
- The process exits before the batch is flushed. Short-lived jobs, CLI tools, and serverless functions lose their last batch unless the provider is shut down explicitly. Call
Shutdownon exit. - The queue is full. The batch processor drops spans when
OTEL_BSP_MAX_QUEUE_SIZEis reached. It does not raise an error — it increments an internal counter. Symptom: data appears under light load and thins out under heavy load. See environment variables.
Is the Collector receiving?
Add the debug exporter to the pipeline you are investigating:
exporters:
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [debug, otlp/uptrace]
Verbosity has three levels:
| Level | Output |
|---|---|
basic (default) | One line summarizing each batch, with a record count |
normal | Roughly one line per record |
detailed | Every field of every record, several lines each |
Why the debug exporter goes quiet
The exporter samples its own output, which regularly convinces people it stopped working:
| Setting | Default | Meaning |
|---|---|---|
sampling_initial | 2 | Messages logged during the first second |
sampling_thereafter | 1 | Log every Mth message after that |
With sampling_thereafter: 1 sampling is effectively off. Raise it on a busy pipeline to keep the logs readable, and lower it when you are chasing a specific record:
exporters:
debug:
verbosity: detailed
sampling_initial: 5
sampling_thereafter: 200
Two cautions. The output format is explicitly not stable between releases, so do not parse it. And remove the exporter when you are done — at detailed verbosity it writes the full contents of every record.
Reading the result:
- Records appear in the debug output but not in the backend — the receiver and processors are fine. The problem is the real exporter: endpoint, credentials, or network.
- Nothing appears — the data is not reaching the pipeline, or a processor is dropping it. Check the receiver and any
filterprocessors.
The debug image tag
The published Collector images are distroless and contain no shell, which makes them awkward to inspect. For interactive debugging there is a variant tag with a shell and basic utilities:
docker run -it --entrypoint sh otel/opentelemetry-collector-contrib:0.159.0-debug
Use it to check what the container can reach — DNS resolution, whether the backend port answers — and switch back to the normal image afterwards. It exists to be exec'd into, not to run in production.
Ask the Collector about itself
The Collector reports its own metrics, and they answer the question "where did the data go" more precisely than any log line.
service:
telemetry:
metrics:
level: normal
readers:
- pull:
exporter:
prometheus:
host: '0.0.0.0'
port: 8888
logs:
level: INFO
Then scrape or curl localhost:8888/metrics and compare:
| Metric | Question it answers |
|---|---|
otelcol_receiver_accepted_spans | Is anything arriving at all |
otelcol_receiver_refused_spans | Is the receiver rejecting it — auth, size limits, malformed payloads |
otelcol_exporter_sent_spans | Is anything leaving |
otelcol_exporter_send_failed_spans | Is the backend rejecting it |
otelcol_exporter_enqueue_failed_spans | Is the sending queue full |
otelcol_exporter_queue_size / _queue_capacity | How close the queue is to overflowing |
The same names exist with _metric_points and _log_records suffixes for the other signals.
The diagnosis follows from which counters move:
- accepted rises, sent stays flat — the data is stuck between receiver and exporter. A processor is dropping it, or the queue is full.
- send_failed rises — the backend is refusing. Read the Collector's own logs for the status code.
- enqueue_failed rises, queue_size sits at capacity — the exporter cannot drain as fast as data arrives. The backend is slow, the network is saturated, or the queue is undersized.
- accepted stays at zero — nothing is arriving. The problem is upstream.
These metrics are also worth alerting on permanently. send_failed climbing is the earliest signal that an observability pipeline is losing data, and it is invisible from the application side.
Failure modes that produce no error
A component is defined but not in a pipeline
exporters:
otlp/uptrace:
endpoint: api.uptrace.dev:4317
service:
pipelines:
traces:
exporters: [debug] # otlp/uptrace is never used
A configured component that no pipeline references is ignored without warning. This is the single most common reason a Collector starts cleanly and forwards nothing.
Validate the file before restarting:
otelcol-contrib validate --config /etc/otelcol-contrib/config.yaml
The wrong port
| Transport | Port |
|---|---|
| OTLP/gRPC | 4317 |
| OTLP/HTTP, self-hosted | 4318 |
| OTLP/HTTP, Uptrace Cloud | 443 |
Sending gRPC to an HTTP port produces a connection that opens and then fails, which reads like a network problem rather than a configuration one.
The endpoint path
Over HTTP, OTEL_EXPORTER_OTLP_ENDPOINT has the signal path appended automatically, while OTEL_EXPORTER_OTLP_TRACES_ENDPOINT is used exactly as written. Setting the signal-specific variable to a bare host produces requests to / and a 404.
Sampling is dropping everything
OTEL_TRACES_SAMPLER=always_off, or a ratio low enough that a low-traffic service produces nothing in the window you are watching. At 1% and 50 requests a minute, the expected wait for a single trace is two minutes.
Set the sampler to always_on while debugging. See sampling.
Traces are split across Collector replicas
With more than one Collector replica behind an ordinary load balancer, the spans of one trace land on different instances. Data is not lost, but tail-based sampling decides on partial traces and the results have holes.
Signal-specific problems
The symptoms above are pipeline-level. Two situations have their own diagnostics:
- Logs arrive but carry no trace context — see log-trace correlation
- Spans arrive but do not form a single trace — see troubleshooting broken traces
A checklist
OTEL_TRACES_EXPORTER=console— does the application produce spans?- Is the sampler set to something that keeps them?
- Does the Collector's
otelcol_receiver_accepted_spansmove? - Does the debug exporter show the records?
- Does
otelcol_exporter_sent_spansmove, and doessend_failedstay flat? - Is every configured component actually listed in
service.pipelines? - Does the process call
Shutdownbefore it exits?
What's next?
- OpenTelemetry Collector — pipeline structure and installation
- Collector configuration — receivers, processors, exporters in detail
- OpenTelemetry environment variables — SDK-side configuration and queue sizing
- OpenTelemetry sampling — what gets kept and where the decision is made
- Collector exporters — retry, queue, and TLS settings behind export failures
- OpenTelemetry APM — the last stage of the pipeline