diff --git a/nix/agent-modules/otel.nix b/nix/agent-modules/otel.nix index dbf62a38..e4ff3200 100644 --- a/nix/agent-modules/otel.nix +++ b/nix/agent-modules/otel.nix @@ -221,6 +221,21 @@ in # tiers; see nix/host-modules/otel.nix for the measurement. validateConfigFile = true; settings = { + # `journalctl --follow --lines=0` (what this receiver runs under + # the hood) ships only what's written *after* it starts — every + # collector start has a silent gap at the front. `storage:` below + # persists the read cursor, so a restart resumes from where it + # left off instead of re-opening that gap; `start_at` stays `end` + # (its default) because a cursor already covers every run after + # the first, and `beginning` without one would re-ship the whole + # journal on every restart. The cursor lives in the unit's own + # `StateDirectory` (nixpkgs' module already sets one, `%S`, below + # as `WorkingDirectory` too) — container-lifetime, not the + # host-persisted state bind mount: it indexes /var/log/journal, + # itself fresh on every container recreate, so the two must share + # a lifetime or the cursor outlives the journal it points into. + extensions.file_storage.directory = "/var/lib/opentelemetry-collector"; + receivers.journald = { # ⚠️ STATED, and it must stay stated: the receiver's own default # is the RUNTIME journal (`/run/log/journal`), which in an agent @@ -229,6 +244,7 @@ in # leaves a collector that validates, starts, reports healthy and # forwards nothing. directory = "/var/log/journal"; + storage = "file_storage"; # No `units` allowlist, unlike the swarm tier's receiver. That # one needs one because the host's journal also holds an # operator's own session; a container's journal is the harness @@ -270,6 +286,11 @@ in processors = [ "resource" ]; exporters = [ logExporter ]; }; + + # An extension configured but not listed here is INERT — the + # journald receiver's `storage: file_storage` above would name a + # component the collector never starts. + service.extensions = [ "file_storage" ]; }; }; };