Instead of instrumenting every app separately, use the OpenTelemetry Collector as a single ingestion layer that receives logs, metrics and traces and forwards them to Observe. Includes a complete collector.yaml, application-side configuration and production tips.
Wiring the OpenTelemetry SDK directly into each application works, but once you have more than a few services it gets painful: every language needs its own SDK version and its own export configuration, and changing an endpoint means touching dozens of services. SDK upgrades and sampling-policy changes require redeploying every application. It is a permanent burden on both operators and developers.
A Collector closes all of that into one place — applications only hand their data to a local or cluster-level Collector, which handles receiving, processing and exporting. Change the export endpoint once and every application follows; add field filtering, redaction or sampling once, in the Collector only.
For Observe, you maintain a single Collector and all application logs, metrics and traces flow into the platform through it. The onboarding surface shrinks from "one configuration per app" to "one Collector configuration".
The common pattern is one Agent-mode Collector per host for local collection, plus an optional Gateway for centralized processing and forwarding. A minimal setup fits in one file:
receivers:
otlp:
protocols:
grpc: { endpoint: 0.0.0.0:4317 }
http: { endpoint: 0.0.0.0:4318 }
filelog:
include: [/var/log/app/*.log]
operators:
- type: json_parser
exporters:
otlphttp:
endpoint: https://ob.example.com/v1/otlp
headers: { Authorization: "Bearer <token>" }
service:
pipelines:
traces: { receivers: [otlp], exporters: [otlphttp] }
metrics: { receivers: [otlp], exporters: [otlphttp] }
logs: { receivers: [otlp, filelog], exporters: [otlphttp] }
The key detail: all three pipelines share one otlphttp exporter, pushing logs, metrics and traces to Observe together, and the platform stores each by its data type automatically. No classification work is needed on the platform side.
Applications do not care about export details; they just send data to the local Collector:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
OTEL_SERVICE_NAME=order-service
OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,env=prod
Java apps add -javaagent:opentelemetry-javaagent.jar for automatic instrumentation; Python wraps the process with opentelemetry-instrument; Go uses the default exporters from go.opentelemetry.io/otel. None require touching business code. Start instrumentation with the three categories that cover most failures — HTTP entry points, database access and message queues.
The Collector's processors are the most valuable part of the ingestion layer. Three high-frequency uses:
processors:
attributes:
actions:
- key: env
value: prod
action: upsert
redaction:
allow_all_keys: true
blocked_values: ["\\d{11}", "\\d{17}[\\dXx]"]
batch:
timeout: 5s
send_batch_size: 1024
service:
pipelines:
logs:
receivers: [otlp, filelog]
processors: [attributes, redaction, batch]
exporters: [otlphttp]
Redaction in particular must happen before ingestion — once a sensitive field lands in platform storage, removing it is painful. That is why handling it centrally in the Collector is more reliable than writing redaction logic separately in every application.
sending_queue and retry_on_failure on the exporter so nothing is lost during a brief platform outageenv, region and team once in the Collector instead of letting each app write its own, or dimensions will not line up when you searchtail_sampling in the Collector to keep only slow and failing requests, balancing cost and coverageAfter onboarding, confirm all three data types arrive with a SQL-like query in the console:
SELECT * FROM logs WHERE service = 'order-service' LIMIT 10
Then check the tracing view to see whether a full call chain can be assembled from a trace id, and confirm no spans are missing. When logs, metrics and traces are aligned in time, you no longer flip between tabs while debugging.