← Back to blog
Guide 4 min read 炬鲸团队

One Config for Logs, Metrics and Traces: Onboarding via OpenTelemetry Collector

Point-to-point reporting is a maintenance burden. This guide onboards logs, metrics and traces to Observe with a single Collector config, covering agent vs gateway deployment, tail sampling and correlation checks.

Why a Collector instead of direct reporting

Having each application report straight to the backend looks simple but has real downsides. Every language needs its own SDK, and each SDK has its own quirks; changing the backend address or rotating a token means touching every application in the fleet; there is no buffering or retry when the network hiccups, so a brief outage turns into silently dropped telemetry; and sampling policies end up scattered across a dozen services where nobody can see or reason about them. OpenTelemetry Collector collapses all of that into a single ingestion layer built on a receive-process-export pipeline. Applications just send; policy lives in one place.

You can run the Collector two ways. As an agent on each host, it collects locally, batches writes, and offloads protocol translation from the backend — the right choice when you have many small services packed onto shared machines. As a centralized gateway, it owns authentication, sampling, and routing for the whole fleet. A single gateway is enough while you are small; at scale, layer agents in front of a gateway so each host ships to a local collector and the gateway fans out to your backends.

Deploying the Collector

The Collector ships as a single static binary with no runtime to install. Run it under systemd or in a container and point it at the config file, otel-collector-config.yaml:

receivers:
  otlp:
    protocols:
      grpc: { endpoint: 0.0.0.0:4317 }
      http: { endpoint: 0.0.0.0:4318 }
processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
  batch:
    timeout: 1s
    send_batch_size: 1024
exporters:
  otlphttp:
    endpoint: https://ob.example.com/otlp
    headers:
      Authorization: "Bearer <ingestion-token>"
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlphttp]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlphttp]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlphttp]

Start it with:

otelcol --config otel-collector-config.yaml

A few details worth knowing: the otlp receiver listens on both gRPC (4317) and HTTP (4318) so every SDK can reach it; memory_limiter stops a runaway collector from taking down the host; batch amortizes network round-trips by grouping spans before export. The exporter sends OTLP over HTTP to Observe with the bearer token in the header, so the token never sits inside your application code. If you prefer containers, the official otel/opentelemetry-collector image works out of the box — mount the config file and expose ports 4317 and 4318.

Instrumenting the application

For Java, the javaagent is the least-effort path — no code changes, just startup flags:

java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.traces.exporter=otlp \
  -Dotel.metrics.exporter=otlp \
  -Dotel.logs.exporter=otlp \
  -Dotel.exporter.otlp.endpoint=http://collector:4317 \
  -Dotel.service.name=order-service \
  -jar app.jar

Go, Python, and Node.js work the same way: install the SDK, set OTEL_EXPORTER_OTLP_ENDPOINT to the collector, and you are done. Two rules worth following: keep service.name consistent across a service's instances, since it is what determines grouping in the console, and set service.version so you can tell which release a trace came from when you diff before and after a deploy.

Sampling and filtering: control the cost

Full capture in production is expensive, and most of it is noise. The Collector supports layered sampling:

  • Head sampling drops a share of traces at the SDK — cheapest, but it can discard important ones before you ever see them
  • Tail sampling waits until the whole trace completes before deciding, so you can keep 100% of error traces and sample healthy ones at, say, 1%

A sensible default: keep every error trace and sample normal traffic at 1%. On the logs side, use a processor to drop DEBUG and trace level lines, redact sensitive fields such as phone numbers or tokens before they leave your network, and strip stack traces you do not need. This cuts storage and keeps PII out of the index entirely.

Verify the correlation

Fire a few erroring requests, then look up a trace by trace id in the console and confirm that logs, metrics, and traces correlate through that id. Then stop one downstream service and confirm the alert fires. Pass both checks and the integration is done — correlation is the whole point of sending three signals through one pipeline, and if it is broken you want to know now rather than during an incident. Once it works, pin your sampling and filtering decisions in the config file so they are version-controlled and reviewable.