journald
Integration
Section titled “Integration”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.
Prerequisites
Section titled “Prerequisites”- Linux with systemd
otelcol-contrib(the contrib distribution includes the journald receiver)- Kopai running locally:
npx @kopai/app startJournal access
Section titled “Journal access”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-journalgroup, 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-journalin 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:
journalctl -n 1 --no-pagerIf you see -- No entries -- or a “you are currently not seeing messages from other users” hint, the collector won’t see them either.
Configure the collector
Section titled “Configure the collector”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.
Run the collector
Section titled “Run the collector”otelcol-contrib --config otel-collector/journald.yamlThe 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.
Verify
Section titled “Verify”Confirm logs arrived using the Kopai CLI:
# Recent entries with timestampsnpx @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 --jsonSending to Kopai.app in the cloud
Section titled “Sending to Kopai.app in the cloud”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.
Working Example
Section titled “Working Example”For a complete runnable example with a demo systemd unit and nix dev shell: