File size: 4,632 Bytes
e36aeda | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | // Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package slog
import (
"bytes"
"context"
"errors"
"testing"
"time"
)
// mockFailingHandler is a handler that always returns an error
// from its Handle method.
type mockFailingHandler struct {
Handler
err error
}
func (h *mockFailingHandler) Handle(ctx context.Context, r Record) error {
_ = h.Handler.Handle(ctx, r)
return h.err
}
func TestMultiHandler(t *testing.T) {
t.Run("Handle sends log to all handlers", func(t *testing.T) {
var buf1, buf2 bytes.Buffer
h1 := NewTextHandler(&buf1, nil)
h2 := NewJSONHandler(&buf2, nil)
multi := NewMultiHandler(h1, h2)
logger := New(multi)
logger.Info("hello world", "user", "test")
checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="hello world" user=test`)
checkLogOutput(t, buf2.String(), `{"time":"`+jsonTimeRE+`","level":"INFO","msg":"hello world","user":"test"}`)
})
t.Run("Enabled returns true if any handler is enabled", func(t *testing.T) {
h1 := NewTextHandler(&bytes.Buffer{}, &HandlerOptions{Level: LevelError})
h2 := NewTextHandler(&bytes.Buffer{}, &HandlerOptions{Level: LevelInfo})
multi := NewMultiHandler(h1, h2)
if !multi.Enabled(context.Background(), LevelInfo) {
t.Error("Enabled should be true for INFO level, but got false")
}
if !multi.Enabled(context.Background(), LevelError) {
t.Error("Enabled should be true for ERROR level, but got false")
}
})
t.Run("Enabled returns false if no handlers are enabled", func(t *testing.T) {
h1 := NewTextHandler(&bytes.Buffer{}, &HandlerOptions{Level: LevelError})
h2 := NewTextHandler(&bytes.Buffer{}, &HandlerOptions{Level: LevelInfo})
multi := NewMultiHandler(h1, h2)
if multi.Enabled(context.Background(), LevelDebug) {
t.Error("Enabled should be false for DEBUG level, but got true")
}
})
t.Run("WithAttrs propagates attributes to all handlers", func(t *testing.T) {
var buf1, buf2 bytes.Buffer
h1 := NewTextHandler(&buf1, nil)
h2 := NewJSONHandler(&buf2, nil)
multi := NewMultiHandler(h1, h2).WithAttrs([]Attr{String("request_id", "123")})
logger := New(multi)
logger.Info("request processed")
checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="request processed" request_id=123`)
checkLogOutput(t, buf2.String(), `{"time":"`+jsonTimeRE+`","level":"INFO","msg":"request processed","request_id":"123"}`)
})
t.Run("WithGroup propagates group to all handlers", func(t *testing.T) {
var buf1, buf2 bytes.Buffer
h1 := NewTextHandler(&buf1, &HandlerOptions{AddSource: false})
h2 := NewJSONHandler(&buf2, &HandlerOptions{AddSource: false})
multi := NewMultiHandler(h1, h2).WithGroup("req")
logger := New(multi)
logger.Info("user login", "user_id", 42)
checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="user login" req.user_id=42`)
checkLogOutput(t, buf2.String(), `{"time":"`+jsonTimeRE+`","level":"INFO","msg":"user login","req":{"user_id":42}}`)
})
t.Run("Handle propagates errors from handlers", func(t *testing.T) {
errFail := errors.New("mock failing")
var buf1, buf2 bytes.Buffer
h1 := NewTextHandler(&buf1, nil)
h2 := &mockFailingHandler{Handler: NewJSONHandler(&buf2, nil), err: errFail}
multi := NewMultiHandler(h2, h1)
err := multi.Handle(context.Background(), NewRecord(time.Now(), LevelInfo, "test message", 0))
if !errors.Is(err, errFail) {
t.Errorf("Expected error: %v, but got: %v", errFail, err)
}
checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="test message"`)
checkLogOutput(t, buf2.String(), `{"time":"`+jsonTimeRE+`","level":"INFO","msg":"test message"}`)
})
t.Run("Handle with no handlers", func(t *testing.T) {
multi := NewMultiHandler()
logger := New(multi)
logger.Info("nothing")
err := multi.Handle(context.Background(), NewRecord(time.Now(), LevelInfo, "test", 0))
if err != nil {
t.Errorf("Handle with no sub-handlers should return nil, but got: %v", err)
}
})
}
// Test that NewMultiHandler copies the input slice and is insulated from future modification.
func TestNewMultiHandlerCopy(t *testing.T) {
var buf1 bytes.Buffer
h1 := NewTextHandler(&buf1, nil)
slice := []Handler{h1}
multi := NewMultiHandler(slice...)
slice[0] = nil
err := multi.Handle(context.Background(), NewRecord(time.Now(), LevelInfo, "test message", 0))
if err != nil {
t.Errorf("Expected nil error, but got: %v", err)
}
checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="test message"`)
}
|