Dash0 acquires Polar Signals

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:

python
12345678910
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
provider = 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:

yaml
12345
exporters:
otlp_http:
endpoint: https://ingress.example.com
headers:
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:

yaml
12345678910111213141516171819202122232425
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
check_interval: 1s
limit_mib: 512
exporters:
otlp_http:
endpoint: https://ingress.example.com
headers:
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.

ConcernSDK exporterCollector
BatchingA separate processor (BatchSpanProcessor), not the exportersending_queue.batch on the exporter, or the batch processor
RetriesLimited, and the behavior varies by languageOn by default via exporterhelper, with backoff
Buffering during an outageIn memory, then droppedQueued, and written to disk with a persistent queue
Infrastructure metadataWhatever the process can see about itselfk8sattributes and friends, from the Kubernetes API
Tail-based samplingNot possibletailsampling, once it has the whole trace
Backend credentialsIn every service that exportsIn one config
Switching backendsA redeployA 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 k8sattributes processor 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:

bash
1
curl -s localhost:8888/metrics | grep otelcol_exporter_sent_spans
text
1
otelcol_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.