template/pkg/logger/handler_test.go
djmil 7bc91b0890 pkg/logger: replace NewDevelopment with NewCLI(level, debugFile)
Two modes for interactive CLI use — driven by debugFile presence:
- Normal (debugFile=""): human text on screen, INFO and above.
- Debug  (debugFile set): same screen + full JSON trace to file.

Auto-detects TTY; falls back to 12-factor JSON when piped/redirected.
IsInteractive() exposes TTY detection for call sites that need it.

Terminal format: INFO has no prefix (program's normal voice);
WARN prints "warning: …"; ERROR prints "error: …"; DEBUG "debug: …".

Breaking: NewDevelopment removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 16:48:34 +00:00

122 lines
3.1 KiB
Go

package logger
import (
"log/slog"
"strings"
"testing"
)
func TestHumanLevel(t *testing.T) {
tests := []struct {
level slog.Level
want string
}{
{slog.LevelDebug, "debug: "},
{slog.LevelInfo, ""},
{slog.LevelWarn, "warning: "},
{slog.LevelError, "error: "},
{slog.LevelError + 4, "error: "},
}
for _, tc := range tests {
if got := humanLevel(tc.level); got != tc.want {
t.Errorf("humanLevel(%v) = %q, want %q", tc.level, got, tc.want)
}
}
}
func TestHumanHandlerInfoNoPrefix(t *testing.T) {
var buf strings.Builder
h := newHumanHandler(&buf, slog.LevelDebug)
slog.New(h).Info("hello world")
if got := strings.TrimRight(buf.String(), "\n"); got != "hello world" {
t.Errorf("got %q, want %q", got, "hello world")
}
}
func TestHumanHandlerPrefixes(t *testing.T) {
tests := []struct {
fn func(*slog.Logger, string, ...any)
want string
}{
{(*slog.Logger).Warn, "warning: "},
{(*slog.Logger).Error, "error: "},
}
for _, tc := range tests {
var buf strings.Builder
log := slog.New(newHumanHandler(&buf, slog.LevelDebug))
tc.fn(log, "msg")
if !strings.HasPrefix(buf.String(), tc.want) {
t.Errorf("got %q, want prefix %q", buf.String(), tc.want)
}
}
}
func TestHumanHandlerAttrs(t *testing.T) {
var buf strings.Builder
slog.New(newHumanHandler(&buf, slog.LevelDebug)).Info("started", "port", 8080)
want := "started port=8080\n"
if got := buf.String(); got != want {
t.Errorf("got %q, want %q", got, want)
}
}
func TestHumanHandlerStringQuoting(t *testing.T) {
var buf strings.Builder
slog.New(newHumanHandler(&buf, slog.LevelDebug)).Warn("check", "msg", "Hello, Gopher!")
if !strings.Contains(buf.String(), `msg="Hello, Gopher!"`) {
t.Errorf("expected quoted value in %q", buf.String())
}
}
func TestHumanHandlerWithAttrs(t *testing.T) {
var buf strings.Builder
h := newHumanHandler(&buf, slog.LevelDebug).WithAttrs([]slog.Attr{slog.String("rid", "abc")})
slog.New(h).Info("request")
if !strings.Contains(buf.String(), "rid=abc") {
t.Errorf("pre-set attr missing: %q", buf.String())
}
}
func TestHumanHandlerLevelFiltering(t *testing.T) {
var buf strings.Builder
log := slog.New(newHumanHandler(&buf, slog.LevelWarn))
log.Info("dropped")
log.Warn("kept")
if strings.Contains(buf.String(), "dropped") {
t.Error("INFO line should have been filtered at WARN level")
}
if !strings.Contains(buf.String(), "kept") {
t.Error("WARN line should appear")
}
}
func TestMultiHandlerFanOut(t *testing.T) {
var screen, file strings.Builder
// screen: INFO+, file: DEBUG+
m := multiHandler{
newHumanHandler(&screen, slog.LevelInfo),
slog.NewJSONHandler(&file, &slog.HandlerOptions{Level: slog.LevelDebug}),
}
log := slog.New(m)
log.Debug("trace", "k", "v")
log.Info("event", "k", "v")
if strings.Contains(screen.String(), "trace") {
t.Error("DEBUG should not appear on screen")
}
if !strings.Contains(screen.String(), "event") {
t.Error("INFO should appear on screen")
}
if !strings.Contains(file.String(), "trace") {
t.Error("DEBUG should appear in file")
}
if !strings.Contains(file.String(), "event") {
t.Error("INFO should appear in file")
}
}