DevgainsDevgainsDevgains
All articles

The OpenTelemetry Collector Explained: Receivers, Processors, and Exporters

·10 min read

The OpenTelemetry Collector is a vendor-neutral proxy that receives telemetry from your services, processes it, and exports it to one or more observability backends. Instead of every app talking directly to Datadog, Grafana, or Honeycomb — each with its own SDK, agent, and API key — your apps emit telemetry in one standard format and hand it to the Collector, which does the routing, batching, and enrichment. It is the single most useful piece of infrastructure you can add when adopting OpenTelemetry, and it's also the piece most teams misconfigure. This guide explains the Collector's architecture, how its pipelines actually work, the two deployment models, and a production-ready configuration you can copy.

This is a supporting page in the Devgains observability cluster. Start with the observability pillar — metrics, logs, and traces for how the three signals fit together, and see Prometheus metric types for the metrics side of what the Collector ends up shipping.

Quick answer: what is the OpenTelemetry Collector?

The OpenTelemetry Collector is a standalone binary that sits between your instrumented applications and your telemetry backends. It has three core jobs, expressed as three component types:

  • Receivers — accept incoming telemetry (from OTLP, Prometheus scrapes, host metrics, logs).
  • Processors — transform it in flight (batch, drop, sample, add resource attributes).
  • Exporters — send it onward to one or more backends.

You wire these together into pipelines — one per signal (traces, metrics, logs). The payoff: your application code depends only on the vendor-neutral OTLP protocol, and switching or adding a backend becomes a config change on the Collector, not a redeploy of every service.

Why the Collector matters

You can export telemetry straight from an app's SDK to a backend. For a single service in a demo, that's fine. In production it falls apart for a few concrete reasons:

  • Coupling. Every service hard-codes a backend endpoint and credentials. Changing vendors means touching and redeploying everything.
  • No central control. You can't drop noisy spans, redact PII, or add environment=prod to all telemetry without editing each service.
  • Backpressure and retries live in every app instead of one well-tuned place, so a slow backend can stall your request path.
  • Cost. Sending raw, unbatched, unsampled data straight to a per-GB vendor is how observability bills explode.

The Collector centralizes all of this. Apps stay dumb — they emit OTLP and forget — while the Collector owns batching, retry, sampling, redaction, and routing. That separation is why the Collector is treated as core infrastructure rather than an optional add-on.

Architecture: components and pipelines

The Collector is built from five component types. Three form the data path; two support it.

ComponentRole in the pipelineExamples
ReceiverEntry point — accepts or scrapes incoming telemetryotlp, prometheus, filelog
ProcessorTransforms data between receiver and exportermemory_limiter, batch, filter
ExporterExit point — sends data to a backendotlp, prometheusremotewrite, debug
ConnectorJoins two pipelines (acts as exporter of one, receiver of another)spanmetrics, count
ExtensionCollector-wide capabilities, not on the data pathhealth_check, pprof, zpages

A pipeline is an ordered chain: receivers → processors → exporters, defined per signal. The key mental model is that processors run in the order you list them, and that order matters a lot. Data enters a receiver, flows through each processor in sequence, and fans out to every exporter on the pipeline.

Connectors are the newer, subtler component: a connector consumes data at the end of one pipeline and emits it at the start of another. The classic example is spanmetrics, which reads your traces pipeline and produces RED metrics (rate, errors, duration) into a metrics pipeline — no extra instrumentation required.

Agent vs gateway: the two deployment models

There are two ways to run the Collector, and mature setups usually run both.

AspectAgent (per host/pod)Gateway (central cluster)
Runs asDaemonSet / sidecar next to the appStandalone Deployment, horizontally scaled
Primary jobCollect local telemetry, add host/pod metadataAggregate, sample, and route to backends
ScalingOne per node, scales with the fleetScale replicas independently of the workload
Best forEnrichment, host metrics, low-latency hand-offTail-based sampling, central policy, egress

The common pattern: agents run close to workloads (a DaemonSet on every Kubernetes node), attach resource attributes like pod name and namespace, and forward via OTLP to a gateway. The gateway pool does the expensive, stateful work — tail-based sampling, cross-service aggregation — and holds the single set of backend credentials. If you run Kubernetes, the agent-as-DaemonSet model pairs naturally with the node/pod topology described in the Kubernetes architecture guide.

Step-by-step: a production-ready Collector config

The Collector reads a single YAML file. You define components in top-level sections, then activate them by listing them under service.pipelines. Defining a component without referencing it in a pipeline does nothing — a frequent source of "why is no data flowing?"

# otel-collector.yaml
receivers:
  otlp:
    protocols:
      grpc:                       # apps send OTLP/gRPC on 4317
        endpoint: 0.0.0.0:4317
      http:                       # or OTLP/HTTP on 4318
        endpoint: 0.0.0.0:4318
 
processors:
  memory_limiter:                 # MUST be first — sheds load before OOM
    check_interval: 1s
    limit_percentage: 80
    spike_limit_percentage: 25
  resource:                       # tag everything with the environment
    attributes:
      - key: deployment.environment
        value: production
        action: upsert
  batch:                          # MUST be last — batch after all processing
    send_batch_size: 8192
    timeout: 5s
 
exporters:
  otlp/backend:
    endpoint: otel-gateway.observability.svc:4317
    tls:
      insecure: false
  debug:                          # prints to stdout; use only while debugging
    verbosity: normal
 
extensions:
  health_check:
    endpoint: 0.0.0.0:13133
 
service:
  extensions: [health_check]
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, resource, batch]
      exporters: [otlp/backend]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, resource, batch]
      exporters: [otlp/backend]

Two rules are doing the heavy lifting here. memory_limiter is the first processor so that, under a load spike, the Collector refuses new data (applying backpressure to receivers) before it runs out of memory and crashes. batch is the last processor so that filtering and enrichment happen on individual items, and batching groups the finished data for efficient export. Put batch first and you'd waste CPU processing items you were about to drop.

Run it with the official contrib image and mount the config:

docker run --rm \
  -p 4317:4317 -p 4318:4318 -p 13133:13133 \
  -v "$(pwd)/otel-collector.yaml:/etc/otelcol/config.yaml" \
  otel/opentelemetry-collector-contrib:latest \
  --config /etc/otelcol/config.yaml

Your application only needs one environment variable to start shipping telemetry to it — the SDK handles the rest:

# point any OpenTelemetry SDK at the local Collector
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
export OTEL_SERVICE_NAME="checkout-api"

Hit http://localhost:13133 and you should get a 200 OK from the health_check extension — the same kind of health signal you wire into liveness and readiness probes so an unhealthy Collector is restarted or pulled from rotation.

Best practices

  • Always run memory_limiter first and batch last. Also set the GOMEMLIMIT environment variable to ~80% of the container's memory limit so Go's garbage collector cooperates with the memory limiter instead of fighting it.
  • Accept OTLP over both gRPC (4317) and HTTP (4318). Browsers and some runtimes can only do HTTP; giving apps a choice avoids awkward workarounds.
  • Enrich with a resource processor, not in app code. Add deployment.environment, region, and cluster once, centrally.
  • Do tail-based sampling in the gateway, never the agent — a single agent can't see all spans of a trace, so it can't make a whole-trace keep/drop decision.
  • Pin the image tag in production (otel/opentelemetry-collector-contrib:0.x.y), not latest, so a surprise release can't change behavior under you.
  • Monitor the Collector itself. It exposes its own metrics; alert on dropped spans and refused data so you learn about saturation before your telemetry silently disappears.

Common mistakes

  • Defining a component but forgetting the pipeline. If it isn't listed under service.pipelines, it is inert. No error — just no data.
  • Omitting memory_limiter. Under a traffic spike the Collector buffers, balloons, and gets OOM-killed, taking your telemetry down exactly when you need it most.
  • Sampling on the agent. You get inconsistent, partial traces because no single agent sees the whole request.
  • Leaving debug/logging exporter on in production. It writes every span to stdout and wrecks throughput. It's a debugging aid, not a backend.
  • Batching before filtering. Wasted CPU and memory processing data you were about to discard.

Takeaways

  • The OpenTelemetry Collector decouples your apps from backends: apps emit OTLP; the Collector routes, batches, and enriches.
  • Its data path is receivers → processors → exporters, wired into per-signal pipelines; processor order is significant.
  • Run agents close to workloads for enrichment and a gateway pool for aggregation, sampling, and egress.
  • memory_limiter first, batch last — and set GOMEMLIMIT — is the single most important production rule.
  • A component only runs if it's referenced under service.pipelines.

FAQ

What is the OpenTelemetry Collector used for? It receives telemetry (traces, metrics, logs) from your services in the vendor-neutral OTLP format, processes it — batching, filtering, sampling, enrichment — and exports it to one or more observability backends, so your applications never depend on a specific vendor.

What is the difference between a receiver, processor, and exporter? A receiver is the entry point that accepts or scrapes incoming data, a processor transforms that data in flight (batching, dropping, tagging), and an exporter is the exit point that sends it to a backend. They chain together in order to form a pipeline.

Do I need the OpenTelemetry Collector? Not strictly — SDKs can export directly to a backend. But once you have more than a couple of services, the Collector centralizes routing, sampling, cost control, and vendor independence in one place, which is why it's recommended for any real production setup.

What is the difference between agent and gateway mode? An agent runs next to your workload (as a sidecar or DaemonSet) to collect local telemetry and add host metadata. A gateway is a central, independently scaled pool that aggregates data from many agents and does expensive work like tail-based sampling before exporting.

Why must memory_limiter be the first processor? So it can apply backpressure to receivers and shed load before the Collector runs out of memory. If it ran later, data would already be buffered in memory and an out-of-memory crash could happen first.

What is OTLP? OTLP (OpenTelemetry Protocol) is the native, vendor-neutral wire protocol for OpenTelemetry data. It runs over gRPC (port 4317) or HTTP (port 4318) and is how SDKs talk to the Collector and how Collectors talk to each other and to backends.

Conclusion

The OpenTelemetry Collector is the hinge of a vendor-neutral observability stack: it lets your applications emit telemetry once, in one format, and leaves the routing, batching, sampling, and enrichment to a single, well-tuned tier you control. Get the pipeline order right — memory_limiter first, batch last — decide deliberately between agent and gateway roles, and remember that a component only runs when a pipeline references it. Do that, and you get portable, cost-controlled telemetry that you can point at any backend without touching a line of application code.

From here, connect what the Collector ships to the rest of your stack: the observability pillar for how the three signals interlock, Prometheus metric types for the metrics it forwards, and the broader observability and DevOps clusters — including deploying Docker containers to production, where the Collector is just another container you run reliably.


References: OpenTelemetry Collector documentation, Collector architecture, Collector configuration, memory_limiter processor, and the CNCF landscape, of which OpenTelemetry is a graduated project.

10 min read

Read next