Short and single-letter names in application code become full words (tests keep theirs). Wrapped calls, signatures and composite literals put one item per line, and one-line function bodies are expanded. Exported names and groups of constants get doc comments, and comments refer to the renamed identifiers.
212 lines
5.1 KiB
Go
212 lines
5.1 KiB
Go
package logging
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/lmittmann/tint"
|
|
)
|
|
|
|
// saveGlobals restores the package level and the default slog logger after a
|
|
// test that changes them.
|
|
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()
|
|
})
|
|
}
|
|
|
|
func TestLevels(t *testing.T) {
|
|
saveGlobals(t)
|
|
slog.SetDefault(slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
|
|
SetLevel(slog.LevelInfo)
|
|
if IsDebugEnabled() {
|
|
t.Fatal("debug enabled at info level")
|
|
}
|
|
EnableDebug()
|
|
if !IsDebugEnabled() {
|
|
t.Fatal("EnableDebug did not enable debug")
|
|
}
|
|
|
|
// EnableDebug must not raise a level that is already more verbose.
|
|
SetLevel(slog.LevelDebug - 4)
|
|
EnableDebug()
|
|
if got := logLevel.Level(); got != slog.LevelDebug-4 {
|
|
t.Fatalf("level = %v, want %v", got, slog.LevelDebug-4)
|
|
}
|
|
|
|
SetLevel(slog.LevelError)
|
|
if IsDebugEnabled() {
|
|
t.Fatal("debug enabled at error level")
|
|
}
|
|
}
|
|
|
|
// wrapper is a handler that wraps another, as middleware handlers do.
|
|
type wrapper struct{ slog.Handler }
|
|
|
|
func TestIsJSONHandler(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
h slog.Handler
|
|
want bool
|
|
}{
|
|
{"json", slog.NewJSONHandler(io.Discard, nil), true},
|
|
{"text", slog.NewTextHandler(io.Discard, nil), false},
|
|
{"tint", tint.NewTextHandler(io.Discard, nil), false},
|
|
{"nil", nil, false},
|
|
{"json with attrs", slog.NewJSONHandler(io.Discard, nil).WithAttrs(
|
|
[]slog.Attr{slog.String("k", "v")},
|
|
), true},
|
|
// Only slog's own JSON handler is recognised, not one wrapped in another.
|
|
{"wrapped json", wrapper{
|
|
Handler: slog.NewJSONHandler(io.Discard, nil),
|
|
}, false},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := IsJSONHandler(tc.h); got != tc.want {
|
|
t.Fatalf("IsJSONHandler = %v, want %v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLogToFileWritesJSON(t *testing.T) {
|
|
saveGlobals(t)
|
|
SetLevel(slog.LevelInfo)
|
|
path := filepath.Join(t.TempDir(), "app.log")
|
|
|
|
if err := LogToFile(path); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if st, err := os.Stat(path); err != nil {
|
|
t.Fatal(err)
|
|
} else if perm := st.Mode().Perm(); perm != 0o600 {
|
|
t.Errorf("log file permissions = %o, want 600", perm)
|
|
}
|
|
if !IsJSONHandler(slog.Default().Handler()) {
|
|
t.Fatal("default handler is not JSON after LogToFile")
|
|
}
|
|
slog.Info("hello file", "k", "v")
|
|
slog.Debug("hidden")
|
|
|
|
//nolint:gosec // G304: reading a file the test created in t.TempDir
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := string(data)
|
|
if !strings.Contains(s, `"msg":"hello file"`) || !strings.Contains(s, `"k":"v"`) {
|
|
t.Fatalf("log file missing entry: %s", s)
|
|
}
|
|
if strings.Contains(s, "hidden") {
|
|
t.Fatalf("debug line written at info level: %s", s)
|
|
}
|
|
}
|
|
|
|
func TestLogToFileBadPath(t *testing.T) {
|
|
saveGlobals(t)
|
|
if err := LogToFile(filepath.Join(t.TempDir(), "missing", "app.log")); err == nil {
|
|
t.Fatal("expected an error for a path in a missing directory")
|
|
}
|
|
}
|
|
|
|
func TestLogToFileTracksFile(t *testing.T) {
|
|
saveGlobals(t)
|
|
if err := LogToFile(filepath.Join(t.TempDir(), "app.log")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !IsFileLogger() {
|
|
t.Fatal("IsFileLogger() = false after LogToFile")
|
|
}
|
|
if err := Shutdown(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if IsFileLogger() {
|
|
t.Fatal("IsFileLogger() = true after Shutdown")
|
|
}
|
|
}
|
|
|
|
func TestShutdownWithoutFile(t *testing.T) {
|
|
if err := Shutdown(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if IsFileLogger() {
|
|
t.Fatal("IsFileLogger() = true with no file")
|
|
}
|
|
}
|
|
|
|
func TestLogToTerminal(t *testing.T) {
|
|
saveGlobals(t)
|
|
LogToTerminal()
|
|
h := slog.Default().Handler()
|
|
if IsJSONHandler(h) {
|
|
t.Fatal("terminal handler reported as JSON")
|
|
}
|
|
SetLevel(slog.LevelWarn)
|
|
if h.Enabled(context.Background(), slog.LevelInfo) {
|
|
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)
|
|
}
|
|
}
|