feat(logger): deliver stderr to systemd journal with per-record priority
When stderr is connected to journald, switch from the plain text handler to the native journal protocol via github.com/systemd/slog-journal. Each record now carries PRIORITY, MESSAGE, and slog attributes as structured fields, so journalctl -p and LogLevelMax= in unit files filter by severity instead of dropping everything at once. Detection follows systemd.exec(5): parse JOURNAL_STREAM as <dev>:<ino> and fstat(2) stderr to confirm the match, guarding against processes that inherit the env var but have stderr redirected elsewhere. The fstat path is Linux-only; other platforms fall through to the existing text handler. Also honour DEBUG_INVOCATION by raising stderr to Debug level, matching the behaviour of systemd.service(5) RestartMode=debug.
This commit is contained in:
41
logger/journald_linux.go
Normal file
41
logger/journald_linux.go
Normal file
@@ -0,0 +1,41 @@
|
||||
//go:build linux
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// stderrIsJournal reports whether os.Stderr is currently connected to
|
||||
// the systemd journal. systemd.exec(5) sets JOURNAL_STREAM to
|
||||
// "<dev>:<inode>" of the journal stream at service start. Detection
|
||||
// must fstat(2) the actual stderr and compare — a child process may
|
||||
// redirect stderr while inheriting the env var, so presence of the
|
||||
// variable alone is not sufficient.
|
||||
func stderrIsJournal() bool {
|
||||
dev, ino, ok := parseJournalStream(os.Getenv("JOURNAL_STREAM"))
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
var st syscall.Stat_t
|
||||
if err := syscall.Fstat(int(os.Stderr.Fd()), &st); err != nil {
|
||||
return false
|
||||
}
|
||||
return uint64(st.Dev) == dev && uint64(st.Ino) == ino
|
||||
}
|
||||
|
||||
func parseJournalStream(v string) (dev, ino uint64, ok bool) {
|
||||
sep := strings.IndexByte(v, ':')
|
||||
if sep <= 0 || sep == len(v)-1 {
|
||||
return 0, 0, false
|
||||
}
|
||||
d, err1 := strconv.ParseUint(v[:sep], 10, 64)
|
||||
n, err2 := strconv.ParseUint(v[sep+1:], 10, 64)
|
||||
if err1 != nil || err2 != nil {
|
||||
return 0, 0, false
|
||||
}
|
||||
return d, n, true
|
||||
}
|
||||
Reference in New Issue
Block a user