Two years ago the honest advice was: use OTel for traces, keep Fluentd or Vector for logs. That has flipped. Today the OTel Collector's filelog receiver, OTLP log exporter, and SDK log bridges are stable enough to be the only log pipeline you need. The catch is that the developer experience shifts in ways your team should understand before flipping the switch.
What "OpenTelemetry logs" actually means
OTel logs are not a single thing. There are three distinct surfaces, each with its own role:
- The data model. A log record in OTel has a body, severity, timestamp, resource attributes (service, host, environment), and instrumentation-scope attributes (logger name, module). Trace and span IDs are first-class fields, so logs emitted inside an active span automatically correlate to the trace.
- The wire format. OTLP carries log records over HTTP or gRPC.
POST /v1/logsfor HTTP. Same protocol that carries traces and metrics, same authentication header. - The SDK bridge. Language SDKs map their native logging library (Python
logging, Java SLF4J, Goslog, .NETILogger, Node.js Winston/Pino) onto the OTel log API. Existinglogger.info(...)calls become OTLP log records without code changes.
The pipeline level is the Collector. The developer level is the SDK bridge. Most migrations touch both.
What OTel replaces (and doesn't)
Map your current pipeline against the OTel equivalents before you start:
- Filebeat / Fluent Bit / Vector as a log shipper → OTel Collector with
filelogreceiver. Tails files, parses, batches, ships. - Fluentd as a log aggregator → OTel Collector as a gateway. Receives from many shippers, applies processors, exports to backend.
- Logstash for parsing → OTel Collector
transformprocessor with OTTL (OpenTelemetry Transformation Language). Less mature than Grok but improving rapidly. - Vendor agent libraries (Datadog dd-trace, New Relic agent) for application logs → OTel SDK log bridge in your language.
- Custom log forwarders writing to S3 / Kafka → Collector
kafkaexporter orfileexporter; works, but adopt last after the main pipeline is stable.
OTel does not replace your log storage backend. You still need a place that indexes and queries the logs: Telemtra, Grafana Loki, Elastic, OpenSearch, ClickHouse, etc. OTel is how logs get there, not where they live.
The minimum viable OTel log pipeline
Three Collector blocks ship logs from a Linux host to an OTLP backend:
receivers:
filelog:
include: [/var/log/app/*.log]
operators:
- type: json_parser
timestamp:
parse_from: attributes.ts
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
- type: severity_parser
parse_from: attributes.level
processors:
resource:
attributes:
- key: service.name
value: checkout-api
action: insert
- key: deployment.environment
value: production
action: insert
batch:
timeout: 5s
send_batch_size: 1024
exporters:
otlphttp/telemtra:
endpoint: https://ingest.telemtra.io
headers:
x-api-key: ${env:TELEMTRA_API_KEY}
service:
pipelines:
logs:
receivers: [filelog]
processors: [resource, batch]
exporters: [otlphttp/telemtra] That's it. Drop into /etc/otelcol/config.yaml, restart the Collector, point your app to write JSON logs to /var/log/app/. The SDK-level bridge is optional. This file-tail approach works for any app that already writes structured logs to disk.
Three migration archetypes
How you move depends on what you're starting from.
Parallel run (lowest risk)
Run OTel Collector alongside your existing shipper for one to two billing cycles. Both ship the same logs to different backends. Compare cost, query ergonomics, and parsing correctness. Switch when you trust the new pipeline. The cost of running two shippers for a month is dwarfed by the cost of a botched cut-over.
Receiver-first (medium effort)
Replace the shipper with the OTel Collector but keep exporting to your existing backend's OTLP-compatible endpoint (most vendors support OTLP-in today, even if they prefer their own agent). This lets you adopt OTel data model and pipeline tooling without changing your storage. Vendor-switch later if you want.
SDK-first (touches application code)
Replace vendor-specific logging libraries with the OTel SDK log bridge. Application code keeps using its native logger (Python logging, Java SLF4J, etc.) but emits via OTLP directly. Skip the file tail. Most useful when your apps are already containerised and you want to drop the filesystem dependency.
The gotchas that bite
- Severity mapping is not automatic. Your existing logs probably encode severity as a string (
"ERROR","info","WARN"). OTel has a 24-level numeric severity. Theseverity_parseroperator handles common cases (DEBUG/INFO/WARN/ERROR/FATAL) but custom levels need a preset. Mis-mapped severity breaks alerts and dashboards silently. - Trace correlation requires the SDK bridge, not the file tail. If you ship logs from disk, the file doesn't contain
trace_idandspan_idunless your application logger writes them itself. The SDK bridge attaches them automatically; the file-tail approach requires manual instrumentation of your logger format. - Resource attributes vs log attributes. Things that describe the source (service name, host, environment) belong in
resource. Things that describe the event (HTTP status, user action) belong in log attributes. Mixing the two creates duplicate metadata and inflates cardinality on the backend. - OTTL is not Grok. If you have a complex Logstash parsing pipeline, the OTTL rewrite will take time. For Java stack traces, multi-line records, and irregular formats, this is real work. Plan for it.
- The Collector's filelog receiver does not handle log rotation perfectly across all platforms. Test rotation specifically on your distro and rotation tool (logrotate, rsyslog, Docker's json-file driver) before going to production.
- Body parsing is per-receiver. If your apps emit JSON and your nginx emits plain text, you need two filelog receivers with different operators, not one with conditional logic.
What gets better after the switch
- One pipeline for three signals. The same Collector instance ships traces, metrics, and logs. One config, one binary, one operational story.
- Trace-log correlation for free. When the SDK bridge is in use, every log emitted during a span automatically carries the trace ID. In Telemtra (and other OTel backends) you click a span and see the exact log lines. Not "logs from around that time", the actual lines from that request.
- Backend portability. Switching log storage no longer means re-instrumenting applications. Change one exporter block in the Collector and the data flows somewhere else.
- Less per-language fragmentation. The SDK bridge means Python
logging, Goslog, Java SLF4J, .NETILoggerall produce the same wire format. No more "we have to handle three different log schemas in the backend."
When to keep what you have
Two scenarios where the migration may not be worth the effort:
- You have a heavy Logstash investment with Grok patterns that took years to tune. Rewriting them in OTTL is a real project. If parsing works and the cost is acceptable, deferring the log pipeline migration is fine; do traces and metrics first.
- Your only log backend is a SIEM (Splunk, Sumo, Chronicle). Many SIEMs accept OTLP but their query languages and pricing assume their native ingest path. Migration value is smaller because backend lock-in dominates.
Further reading
See OpenTelemetry signals for the three-signal data model, the Collector quickstart for a working pipeline, and OpenTelemetry best practices for production for the operational lessons. Start a Telemtra workspace to send your first OTLP log record in under a minute.