OpenTelemetry Protocol
ActiveOTLP (OpenTelemetry Protocol) is the standard data delivery protocol for OpenTelemetry telemetry: traces, metrics, logs, and profiles. It defines how SDKs export telemetry to collectors and backends. OTLP runs over gRPC (OTLP/gRPC) or HTTP/1.1 and HTTP/2 (OTLP/HTTP) using Protobuf encoding or JSON. OTLP is implemented by every major observability backend (Jaeger, Prometheus, Grafana Tempo, Datadog, Honeycomb, New Relic, AWS X-Ray, Google Cloud Trace).
In one line
OTLP 1.11.0 (OpenTelemetry specification) is the wire protocol for exporting traces, metrics, and logs. Two transports: OTLP/gRPC (default, port 4317) and OTLP/HTTP (port 4318). Payload encoding: Protobuf (binary) or JSON. Export is unary request-response: ExportTraceServiceRequest → ExportTraceServiceResponse. Partial success is possible – response carries rejected_spans count. All major observability vendors accept OTLP natively, replacing vendor-specific agents.
Quick Reference
| Field | Size | Description |
|---|---|---|
| OTLP/gRPC | Port 4317 | Default transport. Uses gRPC unary Export*ServiceRequest/Response. TLS optional (plain TCP default in collector, TLS in many managed backends). Endpoint: grpc://collector:4317. SDK env var: OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317. |
| OTLP/HTTP | Port 4318 | HTTP/1.1 or HTTP/2 POST to /v1/traces, /v1/metrics, /v1/logs. Content-Type: application/x-protobuf (binary) or application/json. SDK env var: OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318. |
| Protobuf encoding | application/x-protobuf | Binary encoding of ExportTraceServiceRequest, ExportMetricsServiceRequest, ExportLogsServiceRequest. Schema: github.com/open-telemetry/opentelemetry-proto. Smaller payload than JSON. |
| JSON encoding | application/json | Human-readable alternative to Protobuf. Same schema, JSON-encoded. Useful for debugging. Protobuf field names map to camelCase JSON keys. All backends that accept OTLP must support both encodings. |
| Partial success | rejected_* fields | The ExportTraceServiceResponse includes rejected_spans (int64) to indicate how many spans the server could not accept. A rejected_spans > 0 with error_message explains why. Clients should handle partial acceptance. |
| Retry semantics | RESOURCE_EXHAUSTED / UNAVAILABLE | Clients SHOULD retry on: RESOURCE_EXHAUSTED (429 equivalent), UNAVAILABLE (503 equivalent). Clients MUST NOT retry on: INVALID_ARGUMENT, UNIMPLEMENTED. Use exponential backoff for retries. |
| Signal paths | /v1/traces, /v1/metrics, /v1/logs | OTLP/HTTP endpoints by signal type. gRPC service methods: TraceService.Export, MetricsService.Export, LogsService.Export. Each signal has independent export. |
| Compression | gzip or none | Servers MUST support gzip and none. Clients choose per-request. OTEL_EXPORTER_OTLP_COMPRESSION=gzip enables compression on SDK exporters. Strongly recommended for production to reduce bandwidth. |
Key Characteristics
Vendor-neutral telemetry
OTLP is accepted by Jaeger, Prometheus (OTLP receiver), Grafana Tempo, Grafana Mimir, Datadog, Honeycomb, New Relic, AWS X-Ray, Google Cloud Trace, Azure Monitor, and OpenTelemetry Collector. One SDK, any backend.
Collector as middle layer
The OpenTelemetry Collector receives OTLP from SDKs, processes telemetry (filters, transforms, samples), and exports to backends. This decouples SDK configuration from backend configuration. Collectors can batch, compress, and retry.
Concurrent requests
High-throughput SDKs send concurrent unary OTLP requests without waiting for previous responses. Max concurrent requests is configurable. Throughput = max_concurrent × max_batch_size / (latency + response_time).
Profiles signal is in development
The profiles signal (continuous profiling data: CPU, memory, goroutine stacks) is in development status. Do not use in production. Traces, metrics, and logs are all stable.
Message Format
# OTLP/gRPC: ExportTraceServiceRequest (Protobuf, shown as JSON equivalent)
POST /opentelemetry.proto.collector.trace.v1.TraceService/Export
Content-Type: application/grpc
# ExportTraceServiceRequest structure:
{
"resourceSpans": [
{
"resource": {
"attributes": [
{ "key": "service.name", "value": { "stringValue": "order-service" } },
{ "key": "service.version", "value": { "stringValue": "1.2.3" } }
]
},
"scopeSpans": [
{
"scope": { "name": "order-service-instrumentation" },
"spans": [
{
"traceId": "<16-byte hex>",
"spanId": "<8-byte hex>",
"parentSpanId": "<8-byte hex>",
"name": "POST /api/orders",
"kind": 2, // SPAN_KIND_SERVER
"startTimeUnixNano": 1721908800000000000,
"endTimeUnixNano": 1721908800050000000,
"attributes": [
{ "key": "http.method", "value": { "stringValue": "POST" } },
{ "key": "http.status_code", "value": { "intValue": 201 } }
],
"status": { "code": 1 } // STATUS_CODE_OK
}
]
}
]
}
]
}# ExportTraceServiceResponse
{
"partialSuccess": {
"rejectedSpans": 0, // 0 = all accepted
"errorMessage": "" // empty if no rejection
}
}
# Partial failure example (server at capacity):
{
"partialSuccess": {
"rejectedSpans": 47,
"errorMessage": "quota exceeded for service order-service"
}
}
# Client should retry rejected spans with exponential backoff
# OTLP/HTTP endpoint configuration:
# Traces: POST http://collector:4318/v1/traces
# Metrics: POST http://collector:4318/v1/metrics
# Logs: POST http://collector:4318/v1/logs
# SDK environment variables:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 # OTLP/HTTP
OTEL_EXPORTER_OTLP_PROTOCOL=grpc # or http/protobuf or http/json
OTEL_EXPORTER_OTLP_COMPRESSION=gzip
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer token123"
OTEL_SERVICE_NAME=order-service