Skip to content

Let container logs reach journald, so the Logs tab has something to show - #414

Merged
onel merged 1 commit into
devfrom
fix/app-logs-journald-driver
Aug 13, 2026
Merged

Let container logs reach journald, so the Logs tab has something to show#414
onel merged 1 commit into
devfrom
fix/app-logs-journald-driver

Conversation

@onel

@onel onel commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

No issue — maintainer-driven fix for a fully broken user-facing feature, found while debugging a real box. Branch name carries no issue number for that reason.

The bug

The dashboard's per-app Logs tab is empty on every real box — hosted and appliance, every app, since the tail shipped. It opens the stream and sits on "Waiting for log output…" forever.

It is a missing image file, not a code bug.

LOGGING.md # Docker daemon uses the journald log driver calls the driver switch "the single biggest configuration decision", and host-agent-real is built straight on it — internal/hostagent/journalsource runs journalctl CONTAINER_NAME=<container> -f -o json, and CONTAINER_NAME is a field only the journald driver sets. But no image ever wrote /etc/docker/daemon.json. Both real profiles ran Docker's json-file default, the match returned nothing, and journalctl -f blocked on a stream that could never produce a line.

Why nothing caught it

Two blind spots, both the same mistake — reading logs a way that works on any driver, so the query the feature actually depends on was never exercised:

  • the inner loop's fake host-agent uses docker logs -f (cmd/host-agent/dockerlogsource.go), so make dev shows logs fine;
  • the cloud boot proof greps the brain through docker logs, and cloud-assertions.sh:373 even documents the daemon as being on json-file.

The change

  • dev/cloud/mkosi.extra/etc/docker/daemon.json{"log-driver": "journald"}. Committed, so the boot-proof image inherits it (dev/cloud/test/mkosi.conf does Include=..).
  • .../docker.service.d/10-malmo-logging.confLogRateLimitIntervalSec=0 / LogRateLimitBurst=0, per LOGGING.md # Tuning. journald counts against _SYSTEMD_UNIT, so routing every container through dockerd puts them all in one bucket; under the 10000-per-30s default a single chatty container silently starves every other container's lines. Disabled per-unit, not raised globally, so sshd brute-force spam still caps. (My first pass raised the global journald burst instead — the spec's approach is better and I switched to it.)
  • dev/test-qemu/bootstrap.sh — stages both byte-identically into the appliance lane's generated tree. CANARY_VERSION v26 → v27 so the medium lane rebuilds instead of booting a cached image without them.
  • dev/cloud/cloud-assertions.sh — new assertion 5b: docker info reports journald, and journalctl CONTAINER_NAME=malmo-brain returns lines. Deliberately not a docker logs read — that is exactly how this shipped. Polled, for the journald-ingest lag wait_brain_log already documents.
  • dev/cloud/mkosi.conf — the ExtraTrees=mkosi.extra comment now explains the logging wiring, since JSON takes no comments and daemon.json is two lines with no hint of what depends on it.
  • docs/specs/LOGGING.md — drop-in filename synced to the 10- prefix used everywhere else.

No Go changes. No new dependencies.

Testing

  • make check green.
  • CI / Cloud image with publish=false — the real proof, since assertion 5b only runs on a booted image.
  • Not verified on a booted appliance image: the medium lane is local-only (root + /dev/kvm). That change is a byte-identical copy of the hosted one and the canary is bumped, but it is unbooted. Called out in the progress entry.

Known gaps

  • The journal is still volatile. Neither image sets Storage=persistent or creates /var/log/journal, so scrollback dies on reboot while # Per-app logs promises "scrollback up to journald's cap". Left out deliberately: it is a disk-sizing decision on a root partition that grows at first boot, and it would have held up a fix for a fully broken feature. Queued as Up next Per-issue health audit records #1.
  • SystemMaxUse is therefore unset, so the size-based backpressure # Tuning assumes once docker's rate limit is off is journald's default runtime cap instead. Bounded and self-rotating, not a disk-fill risk.
  • Sidecar logs stay unreachable. The tail follows main_service only. Separate decision, noted in the entry's "what's next".

Full detail in docs/progress/container-logs-journald-driver.md.

The per-app Logs tab was empty on every real box, for every app, since it
shipped: it opened the stream and waited forever.

host-agent-real tails an app by running
`journalctl CONTAINER_NAME=<container> -f`, and CONTAINER_NAME is a field
only Docker's journald log driver sets. LOGGING.md calls that driver switch
"the single biggest configuration decision", but no image ever wrote
/etc/docker/daemon.json, so both real profiles ran Docker's json-file
default and the match returned nothing.

Two blind spots hid it, both the same mistake: the inner loop's fake
host-agent reads `docker logs -f`, and the cloud boot proof greps the brain
through `docker logs`. Both work on any driver, so nothing exercised the
query the feature depends on.

Ship the driver on both images, disable journald's per-unit rate limit for
dockerd (every container now shares docker.service's one bucket, so a chatty
container would silently starve the rest), and assert in the boot proof that
`journalctl CONTAINER_NAME=malmo-brain` returns lines -- not via docker logs,
since reading it that way is why this shipped.

The journal is still volatile; persistence is the next entry.
@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

The migration gap should be fixed before merging because existing boxes and their existing app containers can remain on json-file and keep showing empty Logs tabs.

The new image configuration works for freshly created containers and is covered by the cloud boot assertion, but no host-update or reconciliation path installs the configuration and recreates containers that already carry json-file LogConfig.

Files Needing Attention: dev/cloud/mkosi.extra/etc/docker/daemon.json, dev/test-qemu/bootstrap.sh

Important Files Changed

Filename Overview
dev/cloud/mkosi.extra/etc/docker/daemon.json Sets journald as the default for newly created containers, but provides no migration for deployed boxes or their existing containers.
dev/cloud/mkosi.extra/etc/systemd/system/docker.service.d/10-malmo-logging.conf Disables Docker's shared journald rate limit as an explicitly documented tradeoff while retaining journald's bounded storage behavior.
dev/cloud/cloud-assertions.sh Correctly verifies both the configured driver and the exact CONTAINER_NAME journal query on a freshly booted cloud image.
dev/test-qemu/bootstrap.sh Stages matching logging configuration into the medium test image and invalidates its cache, but this does not migrate existing production containers.
dev/cloud/mkosi.conf Documents why the committed ExtraTrees logging files are required by the host-agent log source.
docs/progress/container-logs-journald-driver.md Records the implementation, testing limits, volatile-journal gap, and intentionally accepted rate-limit tradeoff.
docs/specs/LOGGING.md Synchronizes the documented Docker drop-in filename with the implemented configuration.

Reviews (1): Last reviewed commit: "Let container logs reach journald, so th..." | Re-trigger Greptile

Comment on lines +1 to +3
{
"log-driver": "journald"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Existing containers retain json-file

When an existing box receives this daemon default without recreating its app containers, those containers retain their creation-time json-file logging configuration, causing journalctl CONTAINER_NAME=<container> to return nothing and their Logs tabs to remain empty. The update and startup-reconciliation paths do not inspect or migrate container LogConfig, so the fix currently covers fresh containers and images only.

@onel
onel merged commit d900caf into dev Aug 13, 2026
3 checks passed
@onel
onel deleted the fix/app-logs-journald-driver branch August 13, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant