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

One Collector, Three Signals: Unified Ingestion with OpenTelemetry Collector

Use a single OpenTelemetry Collector to receive logs, metrics and traces, normalize and scrub them, then forward to Observe — instead of running a separate agent per signal. Includes a full config and troubleshooting checklist.

Why one Collector instead of an agent per signal

The classic onboarding mistake is running three agents: Filebeat for logs, node_exporter for metrics and a Jaeger agent for traces. Three configs to maintain, three upgrades, three places to troubleshoot — and the operational cost grows linearly with the number of services. Worse, the three agents produce different formats, timestamps and label conventions, so you end up re-aligning everything at the backend anyway.

The OpenTelemetry Collector unifies all three signals in one component: receivers accept data, processors clean, scrub and sample it, and exporters ship it out. Editing a single YAML changes the whole pipeline. It's fully open source and vendor-neutral, so you aren't locked to any backend — if you switch backends later, you swap the exporter and leave the collection side untouched.

A side note: OpenTelemetry is a CNCF open standard and has become the de facto standard for observability data collection in recent years, supported by the major language SDKs and cloud vendors. Choosing it for your ingestion layer decouples collection from the backend — the team maintains one collection config, and the backend choice can evolve independently.

Install and configure

Install the Collector first. Using the Linux binary:

wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/latest/download/otelcol_linux_amd64.tar.gz
tar -xzf otelcol_linux_amd64.tar.gz
sudo mv otelcol /usr/local/bin/

Then write config.yaml. This configuration receives all three signals and forwards them to Observe:

receivers:
  otlp:
    protocols: { grpc: { endpoint: "0.0.0.0:4317" }, http: { endpoint: "0.0.0.0:4318" } }
  filelog:
    include: ["/var/log/app/*.log"]
    start_at: end
  prometheus:
    config:
      scrape_configs:
        - job_name: "node"
          static_configs: [{ targets: ["localhost:9100"] }]

processors:
  batch:
    timeout: 5s
    send_batch_size: 1024
  attributes:
    actions:
      - key: "card_number"
        action: "delete"        # scrub sensitive fields
      - key: "env"
        action: "upsert"
        value: "prod"

exporters:
  otlphttp:
    endpoint: "https://ob.example.com/otel"
    headers: { "Authorization": "Bearer <your-ingestion-token>" }

service:
  pipelines:
    traces:  { receivers: [otlp], processors: [batch, attributes], exporters: [otlphttp] }
    metrics: { receivers: [otlp, prometheus], processors: [batch, attributes], exporters: [otlphttp] }
    logs:    { receivers: [otlp, filelog], processors: [batch, attributes], exporters: [otlphttp] }

Key points, one by one:

  • The filelog receiver tails application log files directly, so you can drop Filebeat; start_at: end means it only picks up new lines, avoiding a full replay of historical files on first start
  • The prometheus receiver scrapes existing exporters, so existing monitoring assets aren't wasted — during migration the Collector can take over scraping duties from Prometheus
  • The attributes processor scrubs data before it leaves the network — fields like card_number are deleted outright, and labels like env: prod are added uniformly, fixing naming inconsistencies across services
  • Each signal goes through its own pipeline, so heavy log volume won't slow down traces

What the application needs to do

The app doesn't need to know Collector details — it just sends data to localhost:4317/4318. For Spring Boot, attach the OpenTelemetry Java agent:

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

Other languages follow the same pattern: opentelemetry-distro for Python, the otel SDK for Go, @opentelemetry/sdk-node for Node.js — all share one convention, "send to the local OTLP port", while the Collector handles everything downstream.

Sending logs over OTLP requires a Logback otel appender, but a simpler route is to keep writing local files and let the Collector's filelog receiver tail them — zero application changes, all collection logic centralized in the Collector. We recommend starting with the latter: minimal change, easiest rollback.

Organizing environments and services

Separate production, staging and test environments by namespace or an env label rather than standing up a separate Collector for each. The Collector itself can be deployed per environment (one for production, one for staging), but it should always attach the env label on export so the backend can filter by environment. Keep service naming consistent too: use a stable identifier for service.name and avoid baking in version numbers or hostnames, or a single logical service will splinter into a dozen nodes in your traces.

On Kubernetes, deploy the Collector as a DaemonSet so it runs on every node: the filelog receiver tails logs written to disk by Pods, the otlp receiver accepts application traffic, and workloads discover the local Collector via the node address. This avoids cross-node network hops and spreads collection load evenly.

Performance and troubleshooting

The defaults run fine, but tune two things for production: the batch processor's send_batch_size and the exporter's queue length. At high log volume, batching dramatically cuts the number of uploads and CPU overhead.

  • No data arriving: check the exporter endpoint and Authorization header, then look for 4xx/5xx in the Collector logs
  • Traces but no metrics: verify the metrics pipeline actually has a receiver — a common miss is forgetting to add prometheus
  • Memory growing: usually a missing batch processor or filelog reading a huge file; double-check start_at and the batch settings
  • Scrubbing not working: processor order matters — make sure attributes runs before the exporter
  • Sampling dropped a critical trace: review the sampler rules; errors and key interfaces should be set to always-sample
  • Timestamps look wrong: confirm the app, Collector and backend all agree on timezone; OTLP reports timestamps by default, and a timezone mismatch will sort results under the wrong time

Once connected, logs, metrics and traces should appear in a single service view in the Observe console, all searchable with the same SQL-like query language.