Halton Meter Docs
Docs /Operations /Logs (Linux)
OPERATIONS · 04 · LINUX Linux Beta

Logs (Linux)

Where Halton Meter's two systemd-user units log to on Linux, how to read journalctl --user, and the structlog event-name convention. Public beta.

Linux · systemd · Python 3.11+ ·4 min read ·Updated Jun 8, 2026

On Linux you have two equally valid log surfaces:

  • journalctl --user -u <unit>: systemd's structured journal. Indexed, queryable by time / priority / unit, supports -f to follow.
  • ~/.halton-meter/*.log: the same stdout / stderr also redirected to plain files via StandardOutput=append: / StandardError=append: on the unit. Useful for tail -F, jq, and external log shippers.

Both contain identical content. Pick whichever fits your workflow.

On-disk layout

FileProcessSource
~/.halton-meter/daemon.out.logdaemonstdout (systemd-redirected)
~/.halton-meter/daemon.err.logdaemonstructlog stderr
~/.halton-meter/edge.out.logedgestdout
~/.halton-meter/edge.err.logedgestructlog stderr

The .err.log files carry the structured events; .out.log files are mostly empty (the daemon prints almost nothing to stdout).

Tailing live

~: tail via journalctl
$ journalctl --user -u halton-meter.service -f      # proxy hot-path events
$ journalctl --user -u halton-meter-edge.service -f # edge CONNECT decisions

Or the redirected files:

~: tail via files
$ tail -F ~/.halton-meter/daemon.err.log
$ tail -F ~/.halton-meter/edge.err.log

Common queries

~: useful journalctl queries
$ journalctl --user -u halton-meter.service --since "10 minutes ago" --no-pager
$ journalctl --user -u halton-meter.service -p warning --no-pager
$ systemctl --user show halton-meter.service -p NRestarts -p RestartUSec
$ journalctl --user -u halton-meter.service -b --no-pager     # since last boot

Event-name convention

Events are dotted lowercase. The convention is <subsystem>.<verb> or <subsystem>.<noun>.<verb> for sub-areas:

EventWhere it fires
daemon.startup.readyAfter the daemon binds internal_port and /health returns 200
daemon.exitOn graceful shutdown (SIGTERM from systemd)
daemon.heartbeatPeriodic liveness ping; ~1 per minute
intercept.startWhen the proxy hot path begins handling a request
intercept.completeAfter the row lands in SQLite, with duration_ms
edge.sidecar_regen_requestedWhen the daemon signals the edge to refresh its config
attribution.resolvedAfter the attribution chain picks a slug; winning_layer field
attribution.evictedWhen the daemon's attribution cache evicts stale rows

Each event is a JSON object on its own line in daemon.err.log (when running under systemd; in dev with halton-meter daemon from a TTY the format is human-readable instead).

Filtering with jq

The redirected .err.log is line-delimited JSON, so jq works directly:

~: filter with jq
$ jq -c 'select(.event | startswith("intercept"))' ~/.halton-meter/daemon.err.log
$ jq -c 'select(.event == "attribution.resolved") | {at: .timestamp, slug: .project, layer: .winning_layer}' ~/.halton-meter/daemon.err.log
$ jq -c 'select(.level == "warning" or .level == "error")' ~/.halton-meter/daemon.err.log

For journal queries, use --output=json and pipe to jq:

$ journalctl --user -u halton-meter.service --output=json --no-pager | jq -c .

What logs do not contain

Log lines never carry prompt or response bodies. The proxy hot path records token counts, model ids, latency, and event names, not content. Body capture is a separate, opt-out-able feature that lands in SQLite (request_bodies table) with redaction; logs and bodies are not mixed.

See Local-only guarantee.

Rotation

The .err.log files use StandardError=append: so they grow without bound. systemd's journal has its own rotation (SystemMaxUse= in /etc/systemd/journald.conf). For the append-mode files, either:

  • Drop a logrotate.d entry covering ~/.halton-meter/*.log, or
  • Periodically truncate:
$ halton-meter stop && : > ~/.halton-meter/daemon.err.log && halton-meter start

In practice the .err.log files stay small in steady state because the proxy hot path emits only intercept.start / intercept.complete per request, tens of bytes each.

What's next