Skip to content

journald

Ship logs from any systemd service on Linux to Kopai using the OpenTelemetry Collector’s native journald receiver. The collector reads binary journal entries directly — no file tailing, no parsing, no log rotation to manage. It picks up all journald metadata (_SYSTEMD_UNIT, _BOOT_ID, _MACHINE_ID, and dozens more) automatically, and cursor-based checkpointing guarantees no duplicates or loss across collector restarts.

Your application needs no code changes. If it already logs to stdout under a systemd unit, journald is already capturing it.

  • Linux with systemd
  • otelcol-contrib (the contrib distribution includes the journald receiver)
  • Kopai running locally:
Terminal window
npx @kopai/app start

The journald receiver runs journalctl under the hood, so the process running the collector must be able to read /var/log/journal. If it can’t, the collector will start, log Everything is ready, and then silently ingest no records — there is no error message.

Pick one of the following before starting the collector:

  • Run the collector as root (simplest for a one-off test):

    Terminal window
    sudo otelcol-contrib --config otel-collector/journald.yaml
  • Add your user to the systemd-journal group, then log out and back in (group membership only takes effect in a new login session):

    Terminal window
    sudo usermod -aG systemd-journal "$USER"
    # then log out and log back in, or start a new login shell
  • Run the collector as a systemd service with Group=systemd-journal in its unit file — recommended for production.

To confirm the collector can see the journal before launching it, run this as the user that will run the collector:

Terminal window
journalctl -n 1 --no-pager

If you see -- No entries -- or a “you are currently not seeing messages from other users” hint, the collector won’t see them either.

Point the journald receiver at your journal directory and filter by the systemd units you care about. Create otel-collector/journald.yaml:

receivers:
journald:
directory: /var/log/journal
units:
- my-service.service
priority: info
operators:
- type: severity_parser
parse_from: body.PRIORITY
mapping:
error: "3"
warn: "4"
info: "6"
debug: "7"
- type: move
from: body.MESSAGE
to: body
processors:
batch: {}
resource:
attributes:
- key: service.name
value: "my-service"
action: insert
exporters:
otlphttp/kopai:
endpoint: http://localhost:4318
tls:
insecure: true
service:
pipelines:
logs:
receivers: [journald]
processors: [resource, batch]
exporters: [otlphttp/kopai]

Replace my-service.service with the systemd unit you actually want to ship to Kopai (e.g. nginx.service, myapp.service). To tail the entire journal instead of a single unit, omit the units: key entirely. The severity_parser maps journald PRIORITY values (3=error, 4=warning, 6=info, 7=debug) to OTel severity levels.

Terminal window
otelcol-contrib --config otel-collector/journald.yaml

The collector uses journald’s native cursor mechanism to track its read position, so it resumes from the last entry after a restart — no duplicates, no gaps.

Confirm logs arrived using the Kopai CLI:

Terminal window
# Recent entries with timestamps
npx @kopai/cli logs search --service my-service \
--fields Timestamp,Body --sort ASC
# Filter to errors only (severity-min 17 = ERROR)
npx @kopai/cli logs search --service my-service --severity-min 17 --json

Swap the exporter endpoint and add a bearer token:

exporters:
otlphttp/kopai:
endpoint: https://otlp-http.kopai.app
headers:
authorization: "Bearer YOUR_BACKEND_TOKEN"

Drop the tls: insecure: true block — the cloud endpoint uses real TLS.

For a complete runnable example with a demo systemd unit and nix dev shell:

Journald Example