OpenTelemetry Architecture
This page covers how the pieces of OpenTelemetry fit together and where each one runs. For what OpenTelemetry is and how to start using it, see what is OpenTelemetry.
The data path
Telemetry is produced through the API, processed by the SDK, and exported over OTLP. The Collector in the middle is optional — an SDK can export straight to a backend — but it is where most production deployments put their processing.
API and SDK
The split between API and SDK is the design decision that makes the rest work.
The API is what code calls: start a span, record a measurement, emit a log. It ships with a no-op implementation. A library instrumented against the API adds no behavior and almost no overhead until an application installs an SDK — which is why library authors can instrument their code without imposing a telemetry pipeline on every user.
The SDK is the implementation: it decides what to sample, attaches resource attributes, batches records, and hands them to exporters. It is installed once, by the application, at startup.
The practical consequence: a library depends on the API package only. An application depends on both.
Instrumentation libraries
Most telemetry in a typical service is produced by instrumentation libraries rather than by code anyone wrote deliberately — HTTP servers, HTTP clients, database drivers, message queue clients. They call the API on your behalf and produce spans that already follow semantic conventions.
They come in two forms:
- Zero-code — an agent or a runtime hook that instruments an application without modifying it. The Java agent and .NET auto-instrumentation work this way, as does eBPF-based instrumentation, which observes from the kernel and requires no language support at all.
- Library-specific — a wrapper or middleware you install explicitly, common in Go where there is no bytecode manipulation to hook into.
See integration guides for what exists per framework.
Signals
OpenTelemetry defines four telemetry signals:
| Signal | Model |
|---|---|
| Traces | Spans forming a tree per request, with parent-child relationships |
| Metrics | Instruments producing time series, aggregated in the SDK before export |
| Logs | Records with severity, body, and attributes, usually bridged from an existing logging library |
| Profiles | Sampled stack traces with values, in public alpha |
Baggage is not a signal. It is a context mechanism: key-value pairs that travel with a request across services, alongside the trace context. Nothing is exported for baggage on its own, and its values do not appear on spans unless you copy them there explicitly. See context propagation.
What makes the signals more than four parallel pipelines is a shared model. All of them carry the same resource attributes and the same context, so a log record, a span, and a profile sample produced during the same request can be joined on trace_id and on service.name.
OTLP
OTLP is the wire protocol. Its data schema covers all four signals; profiles were added in proto v1.10.0.
| gRPC | HTTP | |
|---|---|---|
| Default port | 4317 | 4318 |
| Encoding | Protobuf | Protobuf or JSON |
| Typical use | Service-to-Collector, high throughput | Through proxies and firewalls, browsers |
The protocol specifies more than the payload shape: which errors are retryable, how partial success is reported, and how backpressure is signalled. That is what makes exporters interchangeable — a backend that speaks OTLP works with every SDK without a vendor-specific exporter.
Uptrace Cloud terminates OTLP/HTTP on port 443, not 4318. Port 4318 is the default for self-hosted deployments.
Exporters for other formats exist — Prometheus for metrics, Zipkin for traces — mainly for feeding systems that predate OTLP. The Jaeger exporter has been removed from the SDKs; Jaeger accepts OTLP directly.
Resources
A resource is the set of attributes describing what produced the telemetry: service.name, service.version, host.name, k8s.pod.name. It is resolved once at startup and attached to everything the process emits.
Resources are what make telemetry from different signals joinable, and they are stored once per export batch rather than per record — which is why identifying attributes belong in the resource rather than on individual spans.
Detectors fill parts of the resource automatically from the environment: cloud provider metadata services, container runtimes, Kubernetes downward API.
Collector
The Collector is a separate process with a receiver-processor-exporter pipeline. It exists because some work does not belong in the application:
- Processing that needs a view of many traces at once, such as tail-based sampling
- Enrichment from sources the application cannot see, such as Kubernetes pod metadata
- Collecting from things that cannot be instrumented — host metrics, log files, syslog, Prometheus endpoints
- Isolating backend configuration and credentials from every service
Deployment topologies
Three arrangements, in increasing order of operational cost.
Direct export. SDK exports straight to the backend. Fewest moving parts, correct for small deployments and for getting started. The cost is that backend credentials and endpoints live in every service, and there is nowhere to add processing later without touching applications.
Agent. A Collector next to each application — a sidecar, or a DaemonSet on each node. The application exports to localhost, which keeps export latency negligible and survives backend outages better. The agent adds resource detection and can enrich with host or pod metadata.
Gateway. A pool of Collectors that receives from agents or applications and forwards to backends. This is where fleet-wide processing goes: sampling policies, redaction, routing to multiple backends, and a single place holding credentials.
Agent and gateway are often combined: an agent per node for local collection, forwarding to a gateway pool for the expensive decisions. Tail-based sampling constrains this design — every span of a trace must reach the same gateway instance.
Kubernetes
The OpenTelemetry Operator manages this arrangement natively: it runs Collector instances as custom resources, injects auto-instrumentation into pods through an admission webhook, and lets the topology change without rebuilding applications.
For Kubernetes-specific setup, see monitoring Kubernetes.
Backends
Storage and querying are deliberately outside the project. OpenTelemetry produces and delivers telemetry; what stores it is a separate choice, and the whole point of the protocol is that the choice stays reversible.
See top OpenTelemetry backends.
What's next?
- What is OpenTelemetry — the overview
- OpenTelemetry Collector — receivers, processors, exporters in detail
- OpenTelemetry context propagation — how context crosses process boundaries
- Get started — install the SDK for your language
- OpenTelemetry APM — the backend the pipeline ends at