Last updated: September 11, 2026
OpenTelemetry Collector vs Exporter: Key Differences Explained
If you’re wondering about the difference between an exporter and a Collector, start here: a Collector uses exporters. An exporter sends telemetry from your application’s OpenTelemetry SDK or from a Collector pipeline. The Collector is a separate process that receives and transforms telemetry before using exporters to send it onward. The terms are easy to confuse at first because exporters appear in both places: inside application SDKs and inside Collectors.
Once you see that a Collector contains exporters, the question underneath it gets clearer: should telemetry go straight from your SDK's exporter to your backend, or pass through a Collector first? That depends on what has to happen to your telemetry before it lands. The rest of this is the mechanics, plus the places where mixing up the two words costs you data.
The word "exporter" means two different things
In an SDK, an exporter is a small interface with one meaningful method: take a batch of finished spans (or metric data points, or log records) and ship them somewhere. The span exporter in a tracing pipeline is the canonical example. Here's the Python version, wiring an OTLP/HTTP exporter into a tracer provider:
12345678910from opentelemetry.sdk.trace import TracerProviderfrom opentelemetry.sdk.trace.export import BatchSpanProcessorfrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterprovider = TracerProvider()provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces")))
Notice that the exporter doesn't do the batching. BatchSpanProcessor
accumulates spans and hands the exporter a batch on a timer. The exporter itself
only serializes and transports. That split trips people up during debugging,
because "my exporter isn't sending anything" is usually a processor
misconfiguration. The Collector-side
batch processor
plays the same role, though
its days are numbered
now that batching is moving into the exporter layer itself.
In the Collector, an exporter is a named block of YAML that terminates a pipeline:
12345exporters:otlp_http:endpoint: https://ingress.example.comheaders:Authorization: Bearer ${env:OTLP_AUTH_TOKEN}
Same concept, wildly different amount of machinery behind it. Every Collector
exporter is wrapped by
exporterhelper,
which hands it a sending queue, retry with exponential backoff, per-attempt
timeouts, and optional batching without you configuring anything. Retries are on
by default, starting at a 5 second interval, backing off to 30 seconds, and
giving up after 5 minutes. The queue holds 1000 requests. An SDK exporter gives
you a fraction of that. See our
guide to batching, queuing, and retries
for the full defaults table and how to tune them.
What the Collector actually is
The Collector is a single binary that runs as its own process. Telemetry moves through it in pipelines, and a pipeline is three ordered stages: receivers that accept data, optional processors that modify it, and exporters that send it out. Here's a complete config that receives OTLP and forwards it to one backend:
12345678910111213141516171819202122232425receivers:otlp:protocols:http:endpoint: 0.0.0.0:4318processors:memory_limiter:check_interval: 1slimit_mib: 512exporters:otlp_http:endpoint: https://ingress.example.comheaders:Authorization: Bearer ${env:OTLP_AUTH_TOKEN}sending_queue:batch: {}service:pipelines:traces:receivers: [otlp]processors: [memory_limiter]exporters: [otlp_http]
The exporter here is one of four component types, and it's the only part of the Collector that maps onto the SDK concept. The OTLP receiver that accepts the data, the memory limiter that protects the process from a traffic spike, and the connectors that chain pipelines together have no SDK equivalent. An SDK gives you a way out. A Collector gives you somewhere to do work on the way out. Our Collector guide builds this pipeline up from scratch, and the top 10 components covers what's available to put in one.
One wrinkle worth knowing before you go component shopping: there is no single Collector binary. Which components exist in your process depends on which distribution you run, and the Contrib distribution is the one that has nearly everything.
Which layer owns what
When telemetry goes missing, the first useful question is which layer had custody of it when it disappeared.
| Concern | SDK exporter | Collector |
|---|---|---|
| Batching | A separate processor (BatchSpanProcessor), not the exporter | sending_queue.batch on the exporter, or the batch processor |
| Retries | Limited, and the behavior varies by language | On by default via exporterhelper, with backoff |
| Buffering during an outage | In memory, then dropped | Queued, and written to disk with a persistent queue |
| Infrastructure metadata | Whatever the process can see about itself | k8sattributes and friends, from the Kubernetes API |
| Tail-based sampling | Not possible | tailsampling, once it has the whole trace |
| Backend credentials | In every service that exports | In one config |
| Switching backends | A redeploy | A config change |
The right-hand column doubles as the argument for running a Collector at all. Missing data during a backend outage is a queue problem. Missing attributes are a processor problem. Neither one is an exporter problem, which is where people tend to look first.
Do you actually need a Collector?
Exporting straight from the SDK to your backend is a legitimate architecture, and the OpenTelemetry project documents it as the No Collector pattern. For a single service in development, or a small deployment sending to one backend with no processing requirements, it's the right call. You have fewer moving parts and nothing extra to operate.
Add a Collector when you hit one of these, and you will hit them:
- Your app doesn't know its own pod name, node, or namespace. The
k8sattributesprocessor queries the Kubernetes API and adds that context to every span and log, and no SDK can do this alone. - Tail-based sampling means keeping a trace because it contains an error, which means seeing every span in it first. A single process only ever sees its own.
- Some signals have no SDK at all. Tailing container log files, scraping Prometheus endpoints, or reading host metrics all happen outside your application, so something has to sit there and gather them.
- Your API token lives in one config instead of being copied into every service that exports telemetry.
- Outage buffering matters more than people expect. An SDK just drops data when your backend is down for ten minutes. A Collector with a persistent queue writes to disk instead and replays once the backend is back.
- And then there's changing your mind: switching backends, adding a second one during a migration, or dropping a noisy attribute becomes a config change instead of a redeploy.
That last point is the one that gets undersold. Direct export couples your application code to your backend choice, and unwinding that later means touching every service you own.
Where the distinction bites in practice
Each of these comes from treating a Collector exporter like an SDK exporter, or from assuming the word carries the same meaning everywhere it appears.
Configuring an exporter doesn't enable it
This is the single most common Collector mistake. An exporter defined under
exporters: but never referenced in a service.pipelines block is simply not
instantiated. The Collector starts cleanly, logs nothing alarming, and your data
goes nowhere. Confirm what's running by checking the internal metrics endpoint:
1curl -s localhost:8888/metrics | grep otelcol_exporter_sent_spans
1otelcol_exporter_sent_spans{exporter="otlp_http"} 4821
If your exporter name is missing from that output, it isn't in a pipeline.
Prometheus-style counters conventionally carry a _total suffix, and some
Collector builds expose these metrics that way, which is why the grep above
is deliberately loose rather than matching the exact name. The
internal telemetry docs
cover the full metric set.
Two exporters in one pipeline means two copies, not a split
The last processor fans out to every exporter, and each one receives the complete data stream. Teams adding a second backend "to compare" sometimes expect the load to be shared and are surprised by doubled egress and doubled ingest bills.
The prometheus exporter doesn't export anywhere
It opens an HTTP endpoint (conventionally 0.0.0.0:8889, though there's no
built-in default and you must set endpoint explicitly) and waits to be
scraped. In the Prometheus world an exporter is a thing that exposes metrics
for collection, which is the opposite of what the word means everywhere else in
OpenTelemetry. If you want to push to a Prometheus-compatible store, you want
prometheusremotewrite instead.
The sending queue is in memory by default
Kill a Collector pod mid-flight and whatever is queued is gone. That sounds like
an edge case until you remember that a DaemonSet Collector restarts on every
node upgrade. Point sending_queue.storage at a file_storage extension if
that data matters. Our
guide to batching, queuing, and retries
walks through the setup.
Component names have been quietly renamed
Configs you find on Stack Overflow will use logging for the
debug exporter, an
older name the upstream project has since retired in favor of debug. More
recently, an upstream snake-case effort has been renaming components like otlp
to otlp_grpc, otlphttp to otlp_http, and hostmetrics to host_metrics.
The old keys still resolve, so both spellings work today and both appear in the
wild, but the official docs and component READMEs have moved to the new names.
Check the upstream changelog for the version each rename landed in before you
assume a given build takes both spellings. When you write new configs, use the
new names.
For protocol-level detail on each, see our guides to the OTLP HTTP exporter and the OTLP gRPC exporter.
Final thoughts
Picture nested dolls. Your SDK has an exporter, that exporter talks to a Collector, and the Collector has its own exporters talking to your backend. Nothing forces you to use all three layers, and plenty of setups shouldn't. But when telemetry goes missing, knowing which layer owned batching, retries, enrichment, and sampling is the difference between a five-minute fix and an afternoon.
Whichever layer you export from, everything downstream depends on the backend receiving OTLP without mangling it. Dash0 is OpenTelemetry-native: it ingests OTLP directly, keeps your resource attributes and semantic conventions intact rather than flattening them into a proprietary model, and correlates logs, metrics, and distributed traces from the same pipeline you already run.
Start a free trial to point your exporter at a
backend that speaks the same protocol you do. No credit card required.

