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

Onboarding Polyglot Services with an OpenTelemetry Collector

The right way to onboard polyglot services: use an OpenTelemetry Collector to centralize sampling, batching and export. Includes a complete configuration, per-language notes for Java, Go, Python and Node.js, and the pitfalls that trip up most teams.

Why use a Collector instead of SDK-to-backend

There are two ways to onboard telemetry: send it straight from SDKs to the backend, or put an OpenTelemetry Collector in between. For a polyglot shop — Java here, Go there, a Python cron job and a Node gateway — the Collector wins for three reasons:

  1. SDK-direct means every service stores its own endpoint and token; changes scatter across repos and upgrades become painful. With a Collector, credentials live in one config file.
  2. A Collector centralizes sampling, scrubbing, batching and retries. Applications just push to a local agent and stay simple.
  3. Swapping backends (say, migrating to Observe) means changing one exporter, not dozens of services.

The Collector is a single binary you run once per host or per cluster; it accepts OTLP from every language and forwards it in one consistent stream.

Install and base config

Download the binary and point it at otel-collector-config.yaml:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 5s
    send_batch_size: 1024

exporters:
  otlphttp:
    endpoint: https://ob.example.com/otlp
    headers:
      Authorization: Bearer <your-ingestion-token>

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp]

Applications then only need to send to 4317 (gRPC) or 4318 (HTTP) on localhost. The three pipelines — traces, logs, metrics — are declared separately, so you can enable them one at a time and, say, leave metrics off until you actually need them.

Where the Collector sits in your topology

There are two common placements. Run one Collector per host (a systemd unit or a sidecar) and have apps target 127.0.0.1:4318; or run a small set of Collectors behind a load balancer and point every app at one stable address. For most teams, per-host is simpler and avoids a single point of failure — if one host's Collector dies, only that host's telemetry is affected.

In Kubernetes, the natural fit is a DaemonSet (one Collector per node) plus, optionally, a Deployment for gateway-style processing. The DaemonSet does the light work — receiving and batching — while the gateway handles heavier jobs like tail sampling and attribute scrubbing before forwarding to Observe. Start with just the DaemonSet; add the gateway later, when you need a centralized sampling policy across many services.

Per-language notes

  • Java: use opentelemetry-javaagent. Add -javaagent:opentelemetry-javaagent.jar and set OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318. Zero code changes; it auto-instruments HTTP, DB and MQ calls.
  • Go: wrap your router with otelhttp, then start and end spans around each handler. Go's biggest trap is context: every span child must take the parent context.Context, or the trace silently breaks into pieces.
  • Python: use opentelemetry-instrument for auto-injection, plus one line to instrument the framework: FlaskInstrumentor().instrument_app(app).
  • Node.js: configure a NodeTracerProvider from @opentelemetry/sdk-trace-node and register it early, before routes are defined; then make sure the traceparent header is forwarded across HTTP calls.

The single most important step: correlate logs with traces. Put trace_id into your log lines so you can jump from one log entry to the whole call chain. The Java agent injects the trace id into the MDC automatically — add %X{trace_id} to your logging.pattern and you're done. For Go and Python you'll need a few lines to write the current span's trace id into each log record.

Verify

  1. Start the Collector and confirm all three pipelines report Ready in its logs.
  2. Fire a request, then find the full chain in the Observe tracing view by trace id.
  3. Open any span and confirm you can jump to its correlated log lines.

Common pitfalls

  • Nothing arriving: curl the Collector's health endpoint first, then read the exporter errors — it's usually the token or the network.
  • Broken traces: a middleware or gateway is dropping the traceparent header between services. Grep for it at each hop.
  • Log volume exploding: batch and sampling aren't enabled. Configure tail_sampling and drop high-frequency, low-value requests like health checks.