Expert OpenTelemetry guidance for collector configuration, pipeline design, and production telemetry instrumentation across Kubernetes, ECS, serverless, and standalone deployments. Use when configuring collectors, designing pipelines, instrumenting applications, implementing sampling, managing cardinality, securing telemetry, writing OTTL transformations, or setting up AI coding agent observability (Claude Code, Codex, Gemini CLI, GitHub Copilot).
93
92%
Does it follow best practices?
Impact
94%
1.36xAverage score across 18 eval scenarios
Passed
No findings from the security scan
Connectors are a first-class OpenTelemetry Collector component type that act as both an exporter and a receiver. A connector bridges two pipelines: it receives data as an exporter on one pipeline and emits data as a receiver on another. This enables cross-pipeline signal routing, aggregation, and transformation patterns that are impossible with standard pipeline stages.
A connector simultaneously acts as:
Pipeline A (Traces) → [connector as exporter] → [connector as receiver] → Pipeline B (Metrics)Without connectors, generating metrics from traces requires external tools (e.g., span-to-metrics exporters, separate agents). Connectors enable this natively inside the collector, reducing latency, operational complexity, and cost.
Connectors are declared in the service.pipelines section by listing the same connector as both an exporter in the source pipeline and a receiver in the destination pipeline:
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [span_metrics] # connector as exporter
metrics:
receivers: [span_metrics] # same connector as receiver
processors: [memory_limiter, batch]
exporters: [otlp]Configuration examples use the canonical component IDs span_metrics, service_graph, and signal_to_metrics. Collector Contrib still accepts the older compact IDs as deprecated aliases. Package/component names such as spanmetricsconnector and signaltometricsconnector are unchanged.
| Connector | Purpose | Source Signal | Output Signal | Stability |
|---|---|---|---|---|
spanmetricsconnector | R.E.D. metrics from traces | Traces | Metrics | Beta |
servicegraphconnector | Service dependency graph | Traces | Metrics | Beta |
routingconnector | Attribute-based pipeline routing | Any | Same signal | Alpha |
failoverconnector | Automatic pipeline failover | Any | Same signal | Alpha |
countconnector | Count signals as metrics | Any | Metrics | Alpha |
signaltometricsconnector | Convert any signal to metrics | Any | Metrics | Alpha |
Generates R.E.D. metrics (Rate, Errors, Duration) from trace spans without requiring a separate agent or post-processing step.
traces.span.metrics.calls (counter): Request rate and error ratetraces.span.metrics.duration (histogram): Latency distributionconnectors:
span_metrics:
histogram:
explicit:
buckets: [5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000] # milliseconds
dimensions:
- name: http.request.method
default: GET
- name: http.response.status_code
- name: service.name
exemplars:
enabled: true # Link metrics to traces via exemplars
metrics_flush_interval: 60s # How often to flush aggregated metrics
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp, span_metrics] # forward traces AND generate metrics
metrics:
receivers: [otlp, span_metrics] # receive both OTLP metrics and generated metrics
processors: [memory_limiter, batch]
exporters: [otlp]spanmetricsconnector is stateful — it aggregates metrics in-memory across spans. In a multi-replica gateway deployment, all spans for the same service or trace must route to the same collector instance.
# Agent-tier: use load_balancing exporter to stick spans to a gateway replica
exporters:
load_balancing:
routing_key: traceID # deterministic routing to gateway
protocol:
otlp:
tls:
insecure: true
resolver:
k8s:
service: otel-gateway-headless # ⚠️ must be Headless Service⚠️ Avoid high-cardinality dimensions in spanmetricsconnector. Adding user.id, request.id, or raw url.path as dimensions creates millions of time series. Apply the Rule of 100: only include dimensions with fewer than 100 unique values.
Generates service dependency graph metrics showing request rates and error rates between pairs of services.
traces.service.graph.request.total (counter): Total calls between service pairstraces.service.graph.request.failed.total (counter): Failed calls between service pairstraces.service.graph.request.duration (histogram): Latency between service pairstraces.service.graph.unpaired_spans_total (counter): Spans without matching pairs (incomplete traces)connectors:
service_graph:
latency_histogram_buckets: [1, 2, 6, 10, 100, 250] # milliseconds
dimensions:
- http.request.method
store:
ttl: 2s # Time to wait for matching spans
max_items: 10000 # Max in-flight span pairs
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp, service_graph] # forward traces AND generate graph metrics
metrics:
receivers: [otlp, service_graph]
processors: [memory_limiter, batch]
exporters: [otlp]Like spanmetricsconnector, the servicegraphconnector is stateful. It must see both the client span and the server span for a given request to compute the edge. Use the load_balancing exporter with routing_key: traceID to route all spans of a trace to the same gateway replica.
Routes signals to different pipelines based on attribute values, enabling per-tenant or per-environment routing.
connectors:
routing:
default_pipelines: [traces/default] # fallback if no rule matches
error_mode: ignore
table:
- statement: route() where attributes["tenant.id"] == "us-east"
pipelines: [traces/us_east]
- statement: route() where attributes["env"] == "prod"
pipelines: [traces/prod]
service:
pipelines:
traces/in:
receivers: [otlp]
processors: [memory_limiter]
exporters: [routing] # connector as exporter
traces/us_east:
receivers: [routing] # same connector as receiver
processors: [batch]
exporters: [otlp/us_east]
traces/prod:
receivers: [routing]
processors: [batch]
exporters: [otlp/prod]
traces/default:
receivers: [routing]
processors: [batch]
exporters: [otlp/default]✅ Use low-cardinality, deterministic attributes (tenant.id, env, cluster)
❌ Do not route on high-cardinality attributes (user.id, request.id) — this creates excessive pipeline fan-out
Provides automatic failover between pipelines based on health/error conditions. When the primary pipeline experiences errors, traffic shifts to the secondary pipeline.
connectors:
failover:
priority_levels:
- [traces/primary] # try this pipeline first
- [traces/secondary] # fallback if primary fails
retry_interval: 10m # how long before retrying the primary
retry_gap: 10s # gap between individual retry attempts
max_retries: 3 # attempts before moving to next priority level
service:
pipelines:
traces/in:
receivers: [otlp]
processors: [memory_limiter]
exporters: [failover] # connector as exporter
traces/primary:
receivers: [failover] # connector as receiver
processors: [batch]
exporters: [otlp/primary]
traces/secondary:
receivers: [failover]
processors: [batch]
exporters: [otlp/secondary] # backup backend (e.g., different region)Primary pipeline → us-east-1 backend
↓ (on failure)
Secondary pipeline → eu-west-1 backup backendCounts telemetry signals (spans, metric data points, or log records) and emits the counts as metrics. Useful for SLI instrumentation and billing.
connectors:
count:
spans:
- name: trace.span.count
description: Total spans processed
conditions:
- 'attributes["http.route"] != nil' # only count HTTP spans
attributes:
- key: http.request.method
- key: http.response.status_code
- key: service.name
logs:
- name: log.record.count
description: Total log records
attributes:
- key: severity_text
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp, count] # connector as exporter
metrics:
receivers: [otlp, count] # connector as receiver
processors: [memory_limiter, batch]
exporters: [otlp]Converts any signal type (traces, logs, or metrics) into metrics using OTTL expressions. More flexible than spanmetricsconnector for custom metric generation.
connectors:
signal_to_metrics:
spans:
- name: http.server.request.duration
description: HTTP server request duration from spans
unit: ms
histogram:
value: Milliseconds(end_time - start_time)
bucket_boundaries: [0, 5, 10, 25, 50, 100, 250, 500, 1000]
attributes:
- key: http.request.method
- key: http.route
- key: http.response.status_code
logs:
- name: log.body.size
description: Size of log body
unit: By
gauge:
value: Int(Len(body))
attributes:
- key: severity_text
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp, signal_to_metrics] # connector as exporter
metrics:
receivers: [otlp, signal_to_metrics] # connector as receiver
processors: [memory_limiter, batch]
exporters: [otlp]Each collector replica is a separate metric producer. Current signal_to_metrics releases emit sums, histograms, and exponential histograms with delta temporality and automatically add the resource attribute signal_to_metrics.service.instance.id, using the Collector's instance identity. That built-in attribute protects the generated streams only when the exporter and backend preserve or map it into the stored metric identity.
Review the backend's stored series, not only the Collector config. If signal_to_metrics.service.instance.id is dropped during resource-to-label translation, multiple replicas can collapse into the same histogram label set; a downstream delta-to-cumulative conversion can then create competing cumulative streams, resets, spikes, or incorrect totals. Preserve or map the built-in attribute. For an older/custom connector or another log/span-to-metric path that emits no producer identity, add a unique identity that is stable for the replica's lifetime. Use service.instance.id only when the generated metric resource intentionally represents the Collector; otherwise use a scoped attribute such as collector_instance.
Apply any fallback identity only to the connector-generated metric stream. If its destination pipeline also carries application metrics, split the generated metrics into a dedicated pipeline before enrichment rather than adding pod labels to every metric.
Dashboards and alerts must aggregate the producer identity away, for example with sum by (<business dimensions>) (...). Retain histogram-specific grouping such as le when querying classic histogram buckets. A histogram's _count already supplies the event count (and event rate after rate()), so do not add a separate counter solely for that purpose.
Prometheus scraping usually adds target identity such as the instance label on the scrape path. Direct OTLP/backend export does not guarantee that resource identity becomes part of the backend's stored series, so verify it explicitly when reviewing replica count, effective temporality, and query aggregation.
connectors:
span_metrics: {}
service_graph: {}
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, k8s_attributes, batch]
exporters: [otlp, span_metrics, service_graph]
metrics:
receivers: [otlp, span_metrics, service_graph]
processors: [memory_limiter, batch]
exporters: [otlp]connectors:
routing:
default_pipelines: [traces/default]
table:
- statement: route() where resource.attributes["tenant.id"] == "acme"
pipelines: [traces/acme]
service:
pipelines:
traces/in:
receivers: [otlp]
processors: [memory_limiter]
exporters: [routing]
traces/acme:
receivers: [routing]
processors: [batch]
exporters: [otlp/acme_backend]
traces/default:
receivers: [routing]
processors: [batch]
exporters: [otlp/shared_backend]⚠️ Check stability before production use:
| Connector | Stability | Notes |
|---|---|---|
spanmetricsconnector | Beta | Feature-complete, minor breaking changes possible |
servicegraphconnector | Beta | Feature-complete, minor breaking changes possible |
routingconnector | Alpha | Experimental — test thoroughly before production |
failoverconnector | Alpha | Experimental — test thoroughly before production |
countconnector | Alpha | Experimental — test thoroughly before production |
signaltometricsconnector | Alpha | Experimental — test thoroughly before production |
All connectors are in the opentelemetry-collector-contrib repository.
✅ Use spanmetricsconnector to generate R.E.D. metrics from traces without extra agents
✅ Use servicegraphconnector to build service dependency maps from trace data
✅ Use routingconnector for attribute-based multi-tenant or multi-environment pipeline routing
✅ Use failoverconnector for automatic cross-region or cross-backend failover
✅ Always pair stateful connectors (span_metrics, service_graph) with the load_balancing exporter and routing_key: traceID
✅ Check stability levels — only spanmetricsconnector and servicegraphconnector are Beta; others are Alpha
⚠️ Avoid high-cardinality dimensions in span_metrics/service_graph to prevent time series explosion
Connectors are the bridge between pipeline stages — use them to turn trace data into metrics and to route signals across pipelines without external tooling.
.claude-plugin
.codex-plugin
.cursor-plugin
.github
scripts
docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
references