restructured logger from budigt
This commit is contained in:
parent
358ee6acc0
commit
b2fd12b1c8
11 changed files with 1227 additions and 1 deletions
219
logger_test.go
Normal file
219
logger_test.go
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
package splinter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type memoryStream struct {
|
||||
mu sync.Mutex
|
||||
records []Record
|
||||
level Level
|
||||
}
|
||||
|
||||
func newMemoryStream(level Level) *memoryStream {
|
||||
return &memoryStream{level: level}
|
||||
}
|
||||
|
||||
func (m *memoryStream) Name() string { return "memory" }
|
||||
func (m *memoryStream) Write(_ context.Context, rec Record) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.records = append(m.records, rec)
|
||||
return nil
|
||||
}
|
||||
func (m *memoryStream) Enabled(level Level) bool { return level >= m.level }
|
||||
func (m *memoryStream) Close() error { return nil }
|
||||
|
||||
func (m *memoryStream) len() int {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return len(m.records)
|
||||
}
|
||||
|
||||
func (m *memoryStream) last() Record {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.records[len(m.records)-1]
|
||||
}
|
||||
|
||||
type failingStream struct{}
|
||||
|
||||
func (f *failingStream) Name() string { return "failing" }
|
||||
func (f *failingStream) Write(_ context.Context, _ Record) error { return errors.New("always fails") }
|
||||
func (f *failingStream) Enabled(_ Level) bool { return true }
|
||||
func (f *failingStream) Close() error { return nil }
|
||||
|
||||
func TestLogger_LevelFiltering(t *testing.T) {
|
||||
mem := newMemoryStream(LevelWarn)
|
||||
logger := New(WithStream(mem))
|
||||
|
||||
logger.Debug("d")
|
||||
logger.Info("i")
|
||||
logger.Warn("w")
|
||||
logger.Error("e")
|
||||
|
||||
if mem.len() != 2 {
|
||||
t.Fatalf("expected 2 records (Warn+Error), got %d", mem.len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogger_BaseAttrs(t *testing.T) {
|
||||
mem := newMemoryStream(LevelDebug)
|
||||
logger := New(WithStream(mem), WithAttrs(map[string]any{"service": "api"}))
|
||||
logger.Info("request")
|
||||
|
||||
if mem.last().Attrs["service"] != "api" {
|
||||
t.Errorf("expected service=api, got %v", mem.last().Attrs["service"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogger_With(t *testing.T) {
|
||||
mem := newMemoryStream(LevelDebug)
|
||||
logger := New(WithStream(mem))
|
||||
child := logger.With(map[string]any{"request_id": "abc"})
|
||||
child.Info("handled")
|
||||
|
||||
if mem.last().Attrs["request_id"] != "abc" {
|
||||
t.Errorf("expected request_id=abc, got %v", mem.last().Attrs["request_id"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogger_InlineArgsOverrideBase(t *testing.T) {
|
||||
mem := newMemoryStream(LevelDebug)
|
||||
logger := New(WithStream(mem), WithAttrs(map[string]any{"env": "prod"}))
|
||||
logger.Info("override", "env", "staging")
|
||||
|
||||
if mem.last().Attrs["env"] != "staging" {
|
||||
t.Errorf("expected staging, got %v", mem.last().Attrs["env"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogger_FanOut(t *testing.T) {
|
||||
a := newMemoryStream(LevelDebug)
|
||||
b := newMemoryStream(LevelDebug)
|
||||
logger := New(WithStream(a), WithStream(b))
|
||||
logger.Info("fanout")
|
||||
|
||||
if a.len() != 1 || b.len() != 1 {
|
||||
t.Errorf("expected both streams to receive 1 record, got %d and %d", a.len(), b.len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogger_ErrorHandler(t *testing.T) {
|
||||
var captured string
|
||||
logger := New(
|
||||
WithStream(&failingStream{}),
|
||||
WithErrorHandler(func(stream string, _ Record, err error) {
|
||||
captured = stream + ": " + err.Error()
|
||||
}),
|
||||
)
|
||||
logger.Info("trigger")
|
||||
|
||||
if captured != "failing: always fails" {
|
||||
t.Errorf("expected handler to fire, got %q", captured)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogger_DefaultsToConsoleStream(t *testing.T) {
|
||||
logger := New()
|
||||
if len(logger.streams) != 1 {
|
||||
t.Fatalf("expected 1 default stream, got %d", len(logger.streams))
|
||||
}
|
||||
if _, ok := logger.streams[0].(*ConsoleStream); !ok {
|
||||
t.Errorf("expected default to be *ConsoleStream, got %T", logger.streams[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageFuncs_RouteThroughDefault(t *testing.T) {
|
||||
mem := newMemoryStream(LevelDebug)
|
||||
prev := SetDefault(New(WithStream(mem)))
|
||||
defer SetDefault(prev)
|
||||
|
||||
Debug("d")
|
||||
Info("i")
|
||||
Warn("w")
|
||||
Error("e")
|
||||
|
||||
if mem.len() != 4 {
|
||||
t.Fatalf("expected 4 records via package funcs, got %d", mem.len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefault_ReturnsPrevious(t *testing.T) {
|
||||
memA := newMemoryStream(LevelDebug)
|
||||
memB := newMemoryStream(LevelDebug)
|
||||
|
||||
loggerA := New(WithStream(memA))
|
||||
loggerB := New(WithStream(memB))
|
||||
|
||||
original := SetDefault(loggerA)
|
||||
t.Cleanup(func() { SetDefault(original) })
|
||||
|
||||
prev := SetDefault(loggerB)
|
||||
if prev != loggerA {
|
||||
t.Errorf("expected SetDefault to return loggerA, got %p", prev)
|
||||
}
|
||||
|
||||
Info("via B")
|
||||
if memA.len() != 0 {
|
||||
t.Errorf("expected memA to be empty after swap, got %d", memA.len())
|
||||
}
|
||||
if memB.len() != 1 {
|
||||
t.Errorf("expected memB to have 1 record, got %d", memB.len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLevelFromString(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
want Level
|
||||
}{
|
||||
{"debug", LevelDebug},
|
||||
{"DEBUG", LevelDebug},
|
||||
{"info", LevelInfo},
|
||||
{"warn", LevelWarn},
|
||||
{"error", LevelError},
|
||||
{"bogus", LevelInfo},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := LevelFromString(tt.in); got != tt.want {
|
||||
t.Errorf("LevelFromString(%q) = %v, want %v", tt.in, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecord_LevelLabel(t *testing.T) {
|
||||
tests := []struct {
|
||||
level Level
|
||||
want string
|
||||
}{
|
||||
{LevelDebug, "DEBUG"},
|
||||
{LevelInfo, "INFO"},
|
||||
{LevelWarn, "WARN"},
|
||||
{LevelError, "ERROR"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
r := Record{Level: tt.level}
|
||||
if got := r.LevelLabel(); got != tt.want {
|
||||
t.Errorf("LevelLabel(%v) = %q, want %q", tt.level, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity: serialising a Record through encoding/json works for callers who
|
||||
// build their own streams from scratch.
|
||||
func TestRecord_JSONShape(t *testing.T) {
|
||||
r := Record{Message: "hi", Attrs: map[string]any{"k": 1}}
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(r); err != nil {
|
||||
t.Fatalf("encode: %v", err)
|
||||
}
|
||||
if !bytes.Contains(buf.Bytes(), []byte(`"Message":"hi"`)) {
|
||||
t.Errorf("missing Message in JSON: %s", buf.String())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue