Files
common/logger/journald_linux_test.go
Ask Bjørn Hansen d8b9cddfb8 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.
2026-04-12 14:01:07 -07:00

77 lines
2.1 KiB
Go

//go:build linux
package logger
import (
"fmt"
"os"
"syscall"
"testing"
)
func TestParseJournalStream(t *testing.T) {
tests := []struct {
name string
input string
wantDev uint64
wantIno uint64
wantOK bool
}{
{"empty", "", 0, 0, false},
{"no separator", "12345", 0, 0, false},
{"leading colon", ":12345", 0, 0, false},
{"trailing colon", "8:", 0, 0, false},
{"non-numeric dev", "x:12345", 0, 0, false},
{"non-numeric ino", "8:x", 0, 0, false},
{"valid small", "8:12345", 8, 12345, true},
{"valid large", "18446744073709551615:1", 18446744073709551615, 1, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dev, ino, ok := parseJournalStream(tt.input)
if ok != tt.wantOK {
t.Fatalf("ok=%v want %v (input=%q)", ok, tt.wantOK, tt.input)
}
if ok && (dev != tt.wantDev || ino != tt.wantIno) {
t.Fatalf("dev=%d ino=%d want dev=%d ino=%d", dev, ino, tt.wantDev, tt.wantIno)
}
})
}
}
func TestStderrIsJournal_Unset(t *testing.T) {
t.Setenv("JOURNAL_STREAM", "")
if stderrIsJournal() {
t.Fatal("stderrIsJournal returned true with JOURNAL_STREAM unset")
}
}
func TestStderrIsJournal_Bogus(t *testing.T) {
t.Setenv("JOURNAL_STREAM", "not-a-valid-value")
if stderrIsJournal() {
t.Fatal("stderrIsJournal returned true with bogus JOURNAL_STREAM")
}
}
func TestStderrIsJournal_Mismatch(t *testing.T) {
// Pick impossibly high dev:inode that won't match real stderr.
t.Setenv("JOURNAL_STREAM", "999999999:999999999")
if stderrIsJournal() {
t.Fatal("stderrIsJournal returned true for mismatching dev:ino")
}
}
func TestStderrIsJournal_Match(t *testing.T) {
// Point JOURNAL_STREAM at the real stderr's dev:inode and confirm
// detection works. This exercises the fstat+compare path without
// needing an actual journal socket.
var st syscall.Stat_t
if err := syscall.Fstat(int(os.Stderr.Fd()), &st); err != nil {
t.Fatalf("fstat stderr: %v", err)
}
t.Setenv("JOURNAL_STREAM", fmt.Sprintf("%d:%d", uint64(st.Dev), uint64(st.Ino)))
if !stderrIsJournal() {
t.Fatal("stderrIsJournal returned false when JOURNAL_STREAM matches stderr dev:ino")
}
}