Ship logs, metrics and traces to Observe without touching application code, using the OpenTelemetry Collector. Full config and a troubleshooting checklist included.
Two things make onboarding painful: rewriting application code just for instrumentation, and maintaining separate SDKs for logs, metrics and traces — each on its own version, none of them talking to the others. OpenTelemetry (OTel) solves both. It is a CNCF-incubated standard: one SDK emits all three signal types, and it ships a standalone Collector that receives, processes and forwards them. More importantly, OTel is vendor-neutral. If you ever switch observability backends, you change one exporter in the Collector config instead of touching every service.
This article assumes your services already produce signals through the OTel SDK or automatic instrumentation. If they do not yet, the fastest route is an auto-instrumentation agent: the Java agent attaches at JVM startup with a single -javaagent flag, and Node, Python and Go have equivalent options. No code edits, immediate spans. The rest of this guide is identical either way — all we need is a correctly configured Collector forwarding to Observe. The whole process takes about fifteen minutes.
The Collector can run on every host (agent mode) or as a central service (gateway mode). For a small-to-mid team, start with a single gateway — simple and low-maintenance. You can add host agents later when you need local buffering or host-level metrics:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
batch:
timeout: 1s
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/observe]
Do not skip the batch processor. Batching before sending cuts the number of export requests by an order of magnitude and protects downstream services from peak traffic. The timeout: 1s cap keeps latency bounded so a quiet stream does not sit in the buffer forever.
Add an OTLP exporter under exporters pointing at Observe, with your ingestion token:
exporters:
otlp/observe:
endpoint: ob.example.com:4317
headers:
authorization: Bearer <your-ingestion-token>
tls:
insecure: false
Then point the logs, metrics and traces pipelines at otlp/observe. A single exporter handles all three signals, so you do not need three separate export paths. Keep insecure: false — production traffic should always go over TLS.
After starting the Collector, first check its own logs for exporting failed. Then confirm data is flowing in the Observe console with a SQL-like query:
SELECT * FROM logs WHERE service = 'order-service' ORDER BY ts DESC LIMIT 20
Three pitfalls account for most stuck onboardings:
authorization header, or a missing space after Bearer. The exporter rejects every batch, and the only symptom is an error line in the Collector log.batch processor's queue keeps growing while export latency climbs, check the network path to ob.example.com:4317 — a misrouted or firewalled path usually shows up as slow exports, not hard failures.The signal that matters most is not the span — it is the resource attributes attached to it. Set service.name to something stable and unique per deployable, add deployment.environment (production, staging), and if you run multiple teams on one platform, add a team tag. Every query, dashboard and alert rule you write afterwards hangs off those dimensions. Without them, a year from now you will be staring at a wall of unrelated spans wondering which system produced which.
Once you are connected, resist the urge to add more. Agree on the naming convention and labels first, document them in one place, and only then start building dashboards. Those five minutes of discipline now will save five hours of debugging later — the difference between observability that scales with your team and a demo that quietly rots.
One more thing worth doing early: turn on the Collector's own health metrics and watch the otelcol_exporter_sent_spans counter. It is the fastest way to confirm data is actually leaving your infrastructure rather than piling up in a queue.