log: send records natively to journald, keep stdout off-unit
A record written to stdout carries no priority, so journald files the whole stream at one level and the swarm log store shows `info` whatever level `tracing` gave it. Under a systemd unit the process's stdout already *is* the journal, so the fix is to speak the journal protocol directly and let each record carry its own severity. New `hive-log` crate holds the one sink chooser, called by `hive-c0re`, `hive-agent` and `swarm-controller`. It builds the same `EnvFilter` those binaries always built, then installs exactly one layer — never both, since a journald layer stacked on the `fmt` layer under a unit stores every record twice. The choice is an fstat compare, not a presence test: a child inherits `$JOURNAL_STREAM` even when its own stdout was redirected elsewhere, so the variable existing proves nothing. The crate parses `dev:inode` out of it and compares both numbers against an fstat of stdout, the descriptor the `fmt` layer writes to by default. No match, unset, or unparseable takes the `fmt` branch. A journald layer that fails to construct despite a match falls back to `fmt` and warns through it — a process must never fail to start because of its logger.
This commit is contained in:
parent
893747a0e4
commit
8cc7f90c98
12 changed files with 316 additions and 36 deletions
49
hive-log/README.md
Normal file
49
hive-log/README.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# hive-log
|
||||
|
||||
One shared log-sink chooser for the hyperhive daemons: `hive_log::init()`
|
||||
builds the `EnvFilter` every binary built before, then installs **exactly
|
||||
one** sink.
|
||||
|
||||
## Why one sink and not both
|
||||
|
||||
Under a systemd unit the process's stdout already **is** the journal.
|
||||
Adding a journald layer on top of the `fmt` layer therefore stores every
|
||||
record twice — once as the native journal entry, once as the text line
|
||||
`fmt` wrote to a descriptor that leads to the same journal.
|
||||
|
||||
Writing to stdout also throws the severity away. A stdout line carries no
|
||||
priority field, so journald files the whole stream at one level and the
|
||||
swarm log store shows `info` whatever level `tracing` gave the record.
|
||||
Logging natively carries the level across. That is the reason to prefer
|
||||
journald where a journal exists, and the reason never to run both layers.
|
||||
|
||||
## How the choice happens
|
||||
|
||||
systemd sets `$JOURNAL_STREAM` to the device and inode numbers of the
|
||||
journal-connected descriptor, in decimal, separated by a colon.
|
||||
|
||||
Checking that the variable exists is **not** enough. A child process
|
||||
inherits `$JOURNAL_STREAM` from its parent even when the parent redirected
|
||||
that child's stdout somewhere else, so presence alone reports a journal
|
||||
that the process does not actually write to. `hive-log` instead stats
|
||||
stdout — the descriptor the `fmt` layer writes to by default, and the one
|
||||
a journald layer would double up on — and compares both numbers against
|
||||
the parsed pair.
|
||||
|
||||
A match installs the native journald layer alone. No match, an unset
|
||||
variable, or a value that does not parse installs the `fmt` layer alone.
|
||||
|
||||
## When the journald layer fails to build
|
||||
|
||||
`tracing_journald::layer()` validates the journal socket up front and
|
||||
returns an `io::Result`, so a missing journal surfaces as an error rather
|
||||
than a silent no-op. On that error `hive-log` installs the `fmt` layer and
|
||||
emits one warning through it. A process must never fail to start because
|
||||
of its logger, and a logger that drops every record in silence is worse
|
||||
than a degraded one that says so.
|
||||
|
||||
## Scope
|
||||
|
||||
The sink chooser and its tests. Nothing else belongs here: the crate
|
||||
exists so three binaries share one copy of this decision, not as a place
|
||||
for general utilities.
|
||||
Loading…
Reference in a new issue