LinuxXLVI · OpenTelemetryOTel collectors
OTel logs on a Linux host - the journald and filelog receivers
What you'll learn
- Ingest the systemd journal into a Collector pipeline with the journald receiver
- Tail application log files with the filelog receiver, including multiline records
- Parse timestamps, severity and structured fields into OTLP log attributes
- Checkpoint file offsets so a Collector restart neither duplicates nor loses lines
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-11
Metrics and traces reach the Collector because something was instrumented to send them. Logs mostly do not. On a Linux host the logs that matter are already being written by software that has never heard of OTLP: systemd services write to the journal, and applications write lines to files.
Two receivers close that gap, and both of them have failure modes that show up as missing or duplicated data rather than as an error.
Three ways logs enter the pipeline
| Source | Receiver | Use it for |
|---|---|---|
| An instrumented application | otlp | Anything already emitting OTLP logs, usually alongside its traces |
| The systemd journal | journald | Everything a systemd unit writes, plus kernel messages |
| A file on disk | filelog | Applications that write their own log files and bypass syslog and the journal |
The syslog receiver covers a fourth case - terminating an
existing rsyslog forwarding estate - and is in
OTel collectors and agents.
All four are in the contrib distribution. A core-only build fails at start-up with an unknown-type error, which is a packaging problem rather than a configuration one.
The journald receiver
receivers:
journald:
directory: /var/log/journal
units:
- nginx.service
- myapp.service
priority: info
start_at: end
It reads by running journalctl as a subprocess, which has
three consequences worth knowing before you deploy it.
- The journalctl binary must exist on PATH in whatever runs the Collector - a minimal container image usually does not have it
- The Collector process must be able to read the journal: root, or a user in the systemd-journal group
- It can only read what journald kept. On a host with a volatile journal there is nothing from before the current boot to read
$ journalctl --header | grep -i storage; journalctl --list-boots | tail -3; id -nG otelcolStorage: persistent
-2 4f1c... Fri 2026-07-25 09:11:02 UTC—Sat 2026-08-09 02:59:41 UTC
-1 8c2d... Sat 2026-08-09 03:01:10 UTC—Tue 2026-08-11 03:59:02 UTC
0 a91b... Tue 2026-08-11 04:03:12 UTC—Tue 2026-08-11 09:20:44 UTC
otelcol systemd-journalIllustrative output
The filelog receiver
extensions:
file_storage/checkpoints:
directory: /var/lib/otelcol/file_storage
receivers:
filelog:
include:
- /var/log/myapp/*.log
exclude:
- /var/log/myapp/*.gz
start_at: end
storage: file_storage/checkpoints
multiline:
line_start_pattern: '^\d{4}-\d{2}-\d{2}T'
operators:
- type: regex_parser
regex: '^(?P<ts>\S+) (?P<sev>[A-Z]+) (?P<msg>.*)$'
timestamp:
parse_from: attributes.ts
layout: '%Y-%m-%dT%H:%M:%S.%fZ'
severity:
parse_from: attributes.sev
Four settings carry most of the weight:
start_at.endstarts from the current end of file;beginningreads the whole file. On first deployment against a 4 GB log,beginningsends four gigabytes into your log store in one burst and will be rate-limited, dropped, or billed.storage. Names afile_storageextension in which the receiver checkpoints its read offsets. Without it, offsets live only in memory.multiline. Without aline_start_pattern, every line of a Java or Python stack trace becomes a separate log record, and the one line naming the exception is separated from the one naming the file.operators. A pipeline that turns a line of text into fields.regex_parserandjson_parserare the common entry points; both accept nestedtimestampandseverityblocks.
Give every record a host
A log record with no resource attributes is nearly useless in a
central store: you can read the message but not say which host
produced it. The resourcedetection processor fills that in
without hard-coding anything per host.
processors:
resourcedetection:
detectors: [env, system]
system:
hostname_sources: ["os"]
transform/trim:
log_statements:
- context: log
statements:
- delete_key(attributes, "ts")
- delete_key(attributes, "sev")
The transform statements remove the raw fields once they have
been parsed into the record proper. Leaving them in ships every
timestamp twice - once in the OTLP record and once as a string
attribute - which is pure cost.
Wiring it together
service:
extensions: [file_storage/checkpoints]
pipelines:
logs:
receivers: [otlp, journald, filelog]
processors: [memory_limiter, resourcedetection, transform/trim, batch]
exporters: [otlphttp/loki]
exporters:
otlphttp/loki:
logs_endpoint: http://loki.example.com:3100/otlp/v1/logs
memory_limiter must be the first processor in the list. It
works by refusing data when the Collector is over its memory
ceiling, and it can only protect the processors that come after
it.
Verify before and after deployment
$ otelcol validate --config /etc/otel-collector/agent.yaml; systemctl restart otelcol-contrib; systemctl is-active otelcol-contrib; journalctl -u otelcol-contrib -n 20 --no-pageractive
Aug 11 09:31:02 web02 otelcol-contrib[4411]: info service@v0.108.0/service.go:169 Starting otelcol-contrib...
Aug 11 09:31:02 web02 otelcol-contrib[4411]: info fileconsumer/file.go:257 Started watching file path=/var/log/myapp/app.log
Aug 11 09:31:03 web02 otelcol-contrib[4411]: info service@v0.108.0/service.go:225 Everything is ready.Illustrative output
To see records rather than start-up messages, add the debug
exporter temporarily. It prints what is flowing through the
pipeline to the Collector’s own log.
exporters:
debug:
verbosity: detailed
Keep the local copy
Shipping logs off the host is not a reason to stop writing them
on it. Set MaxLevelStore and the journal size limits so the
local journal still holds a useful window, because the
Collector is a single point of failure for telemetry and the
incidents you care about most are the ones where the host lost
its network.
Knowledge check
Knowledge check · 5 questions
Q1. A filelog receiver is configured with start_at: end and no storage extension. The Collector is restarted by a package upgrade that takes 40 seconds. What happens to the log lines written during those 40 seconds?
Q2. The journald receiver is configured correctly, but after a host crash the Collector ships nothing from the boot that crashed. What is the most likely cause?
Q3. Which of these are genuine prerequisites for the journald receiver to work? Select all that apply.
Q4. A logrotate configuration using copytruncate can cause a filelog receiver to miss records even though nothing reports an error.
Q5. Why must memory_limiter be the first processor in a pipeline?
Passing score: 75%. Answers are checked in this browser.