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

Ingest Logs, Metrics and Traces with the OpenTelemetry Collector

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.

Why a Collector instead of instrumenting every service

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".

Deploying the Collector: one file for all three data types

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.

Three environment variables on the application 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.

Using processors for attribute injection and redaction

The Collector's processors are the most valuable part of the ingestion layer. Three high-frequency uses:

  1. Inject resource attributes uniformly: use the attributes processor to tag all data with env, region and team, so dimensions line up when you search
  2. Redact logs: use the redaction processor to mask phone numbers, ID numbers and tokens before they are sent
  3. Batch sends: use the batch processor to accumulate and flush in bulk, reducing requests and platform load
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.

Four production recommendations

  1. Capture legacy logs with filelog: apps without an SDK are fine — the Collector reads and parses log files directly, so logs reach the platform immediately, then migrate to OTLP gradually
  2. Buffer as a safety net: configure sending_queue and retry_on_failure on the exporter so nothing is lost during a brief platform outage
  3. Standardize resource attributes: inject dimensions like env, region and team once in the Collector instead of letting each app write its own, or dimensions will not line up when you search
  4. Sample when volume is high: if trace volume is large, add tail_sampling in the Collector to keep only slow and failing requests, balancing cost and coverage

Verifying the connection

After 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.

FAQ

  • Logs never arrive: check the Collector exporter for 4xx/5xx responses first, then verify the token and network reachability, and make sure the OTLP HTTP and gRPC ports are not swapped
  • Timestamps look wrong: check the timezone of the app and server — the Collector passes timestamps through, it does not rewrite them
  • Traces have gaps: confirm downstream HTTP and messaging clients also inject and propagate trace context, otherwise the chain breaks in the middle
  • Memory usage climbs: give the Collector a memory_limiter processor to cap memory and keep it from dragging down the host