219 lines
5.4 KiB
Go
219 lines
5.4 KiB
Go
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())
|
|
}
|
|
}
|