Logging: structured slog messages throughout, and WithLogger options so callers choose the logger

Log messages are lower-case event names with their details as key/value
attributes ("error" for errors), in place of formatted strings.

Loggable package take a WithLogger option as part of their "New*" call,
defaulting to slog.Default(). Each tags its lines with its `component`.

Some debug loggers print directly rather than using slog when in
terminal logging mode.
This commit is contained in:
bwilliams committed 2026-10-15 16:10:52 +01:00
1 parent 0b5fde9563
commit 043e307663
49 files changed
+598 -248

No files matched your search

+51
View File
@@ -18,9 +18,11 @@ func saveGlobals(t *testing.T) {
t.Helper()
level := logLevel.Level()
def := slog.Default()
out, detect := terminal, isTerminal
t.Cleanup(func() {
logLevel.Set(level)
slog.SetDefault(def)
terminal, isTerminal = out, detect
Shutdown()
})
}
@@ -155,3 +157,52 @@ func TestLogToTerminal(t *testing.T) {
t.Fatal("terminal handler ignores the package level")
}
}
// On an interactive terminal the handler, and loggers derived from it, report
// that they write to the terminal; when stdout is a file or pipe they don't.
func TestWritesToTerminal(t *testing.T) {
for _, interactive := range []bool{true, false} {
saveGlobals(t)
isTerminal = func(*os.File) bool {
return interactive
}
LogToTerminal()
derived := slog.Default().With("component", "test").WithGroup("g")
for name, h := range map[string]slog.Handler{
"default": slog.Default().Handler(),
"derived": derived.Handler(),
} {
if got := WritesToTerminal(h); got != interactive {
t.Errorf(
"interactive %v, %s: WritesToTerminal = %v",
interactive, name, got,
)
}
}
}
if WritesToTerminal(slog.DiscardHandler) {
t.Error("another handler reported as the terminal")
}
}
func TestPrintToTerminal(t *testing.T) {
saveGlobals(t)
out, err := os.Create(filepath.Join(t.TempDir(), "terminal"))
if err != nil {
t.Fatal(err)
}
defer out.Close()
terminal = out
PrintToTerminal("line one\nline two")
got, err := os.ReadFile(out.Name())
if err != nil {
t.Fatal(err)
}
if string(got) != "line one\nline two\n" {
t.Errorf("printed %q; want the text as it is, then a newline", got)
}
}