Skip to content

Commit 16bf49a

Browse files
committed
2
Change-Id: I2effccfd296366065ee9626a9674cd637b702218
1 parent 61a3498 commit 16bf49a

File tree

3 files changed

+71
-104
lines changed

3 files changed

+71
-104
lines changed

src/log/slog/handler.go

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package slog
66

77
import (
88
"context"
9-
"errors"
109
"fmt"
1110
"io"
1211
"log/slog/internal/buffer"
@@ -643,49 +642,3 @@ func (dh discardHandler) Enabled(context.Context, Level) bool { return false }
643642
func (dh discardHandler) Handle(context.Context, Record) error { return nil }
644643
func (dh discardHandler) WithAttrs(attrs []Attr) Handler { return dh }
645644
func (dh discardHandler) WithGroup(name string) Handler { return dh }
646-
647-
// MultiHandler returns a handler that invokes all the given Handlers.
648-
// Its Enable method reports whether any of the handlers' Enabled methods return true.
649-
// Its Handle, WithAttr and WithGroup methods call the corresponding method on each of the enabled handlers.
650-
func MultiHandler(handlers ...Handler) Handler {
651-
return multiHandler(handlers)
652-
}
653-
654-
type multiHandler []Handler
655-
656-
func (h multiHandler) Enabled(ctx context.Context, l Level) bool {
657-
for i := range h {
658-
if h[i].Enabled(ctx, l) {
659-
return true
660-
}
661-
}
662-
return false
663-
}
664-
665-
func (h multiHandler) Handle(ctx context.Context, r Record) error {
666-
var errs []error
667-
for i := range h {
668-
if h[i].Enabled(ctx, r.Level) {
669-
if err := h[i].Handle(ctx, r.Clone()); err != nil {
670-
errs = append(errs, err)
671-
}
672-
}
673-
}
674-
return errors.Join(errs...)
675-
}
676-
677-
func (h multiHandler) WithAttrs(attrs []Attr) Handler {
678-
handlers := make([]Handler, 0, len(h))
679-
for i := range h {
680-
handlers = append(handlers, h[i].WithAttrs(attrs))
681-
}
682-
return multiHandler(handlers)
683-
}
684-
685-
func (h multiHandler) WithGroup(name string) Handler {
686-
handlers := make([]Handler, 0, len(h))
687-
for i := range h {
688-
handlers = append(handlers, h[i].WithGroup(name))
689-
}
690-
return multiHandler(handlers)
691-
}

src/log/slog/multi_handler.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package slog
6+
7+
import (
8+
"context"
9+
"errors"
10+
)
11+
12+
// MultiHandler returns a handler that invokes all the given Handlers.
13+
// Its Enable method reports whether any of the handlers' Enabled methods return true.
14+
// Its Handle, WithAttr and WithGroup methods call the corresponding method on each of the enabled handlers.
15+
func MultiHandler(handlers ...Handler) Handler {
16+
return &multiHandler{multi: handlers}
17+
}
18+
19+
type multiHandler struct {
20+
multi []Handler
21+
}
22+
23+
func (h *multiHandler) Enabled(ctx context.Context, l Level) bool {
24+
for i := range h.multi {
25+
if h.multi[i].Enabled(ctx, l) {
26+
return true
27+
}
28+
}
29+
return false
30+
}
31+
32+
func (h *multiHandler) Handle(ctx context.Context, r Record) error {
33+
var errs []error
34+
for i := range h.multi {
35+
if h.multi[i].Enabled(ctx, r.Level) {
36+
if err := h.multi[i].Handle(ctx, r.Clone()); err != nil {
37+
errs = append(errs, err)
38+
}
39+
}
40+
}
41+
return errors.Join(errs...)
42+
}
43+
44+
func (h *multiHandler) WithAttrs(attrs []Attr) Handler {
45+
handlers := make([]Handler, 0, len(h.multi))
46+
for i := range h.multi {
47+
handlers = append(handlers, h.multi[i].WithAttrs(attrs))
48+
}
49+
return &multiHandler{multi: handlers}
50+
}
51+
52+
func (h *multiHandler) WithGroup(name string) Handler {
53+
handlers := make([]Handler, 0, len(h.multi))
54+
for i := range h.multi {
55+
handlers = append(handlers, h.multi[i].WithGroup(name))
56+
}
57+
return &multiHandler{multi: handlers}
58+
}

src/log/slog/multi_handler_test.go

Lines changed: 13 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"bytes"
99
"context"
1010
"errors"
11-
"strings"
1211
"testing"
1312
"time"
1413
)
@@ -27,8 +26,6 @@ func (h *mockFailingHandler) Handle(ctx context.Context, r Record) error {
2726
}
2827

2928
func TestMultiHandler(t *testing.T) {
30-
ctx := context.Background()
31-
3229
t.Run("Handle sends log to all handlers", func(t *testing.T) {
3330
var buf1, buf2 bytes.Buffer
3431
h1 := NewTextHandler(&buf1, nil)
@@ -39,21 +36,8 @@ func TestMultiHandler(t *testing.T) {
3936

4037
logger.Info("hello world", "user", "test")
4138

42-
// Check the output of the Text handler.
43-
output1 := buf1.String()
44-
if !strings.Contains(output1, `level=INFO`) ||
45-
!strings.Contains(output1, `msg="hello world"`) ||
46-
!strings.Contains(output1, `user=test`) {
47-
t.Errorf("Text handler did not receive the correct log message. Got: %s", output1)
48-
}
49-
50-
// Check the output of the JSON handle.
51-
output2 := buf2.String()
52-
if !strings.Contains(output2, `"level":"INFO"`) ||
53-
!strings.Contains(output2, `"msg":"hello world"`) ||
54-
!strings.Contains(output2, `"user":"test"`) {
55-
t.Errorf("JSON handler did not receive the correct log message. Got: %s", output2)
56-
}
39+
checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="hello world" user=test`)
40+
checkLogOutput(t, buf2.String(), `{"time":"`+jsonTimeRE+`","level":"INFO","msg":"hello world","user":"test"}`)
5741
})
5842

5943
t.Run("Enabled returns true if any handler is enabled", func(t *testing.T) {
@@ -62,10 +46,10 @@ func TestMultiHandler(t *testing.T) {
6246

6347
multi := MultiHandler(h1, h2)
6448

65-
if !multi.Enabled(ctx, LevelInfo) {
49+
if !multi.Enabled(context.Background(), LevelInfo) {
6650
t.Error("Enabled should be true for INFO level, but got false")
6751
}
68-
if !multi.Enabled(ctx, LevelError) {
52+
if !multi.Enabled(context.Background(), LevelError) {
6953
t.Error("Enabled should be true for ERROR level, but got false")
7054
}
7155
})
@@ -76,7 +60,7 @@ func TestMultiHandler(t *testing.T) {
7660

7761
multi := MultiHandler(h1, h2)
7862

79-
if multi.Enabled(ctx, LevelDebug) {
63+
if multi.Enabled(context.Background(), LevelDebug) {
8064
t.Error("Enabled should be false for DEBUG level, but got true")
8165
}
8266
})
@@ -91,15 +75,8 @@ func TestMultiHandler(t *testing.T) {
9175

9276
logger.Info("request processed")
9377

94-
// Check if the Text handler contains the attribute.
95-
if !strings.Contains(buf1.String(), "request_id=123") {
96-
t.Errorf("Text handler output missing attribute. Got: %s", buf1.String())
97-
}
98-
99-
// Check if the JSON handler contains the attribute.
100-
if !strings.Contains(buf2.String(), `"request_id":"123"`) {
101-
t.Errorf("JSON handler output missing attribute. Got: %s", buf2.String())
102-
}
78+
checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="request processed" request_id=123`)
79+
checkLogOutput(t, buf2.String(), `{"time":"`+jsonTimeRE+`","level":"INFO","msg":"request processed","request_id":"123"}`)
10380
})
10481

10582
t.Run("WithGroup propagates group to all handlers", func(t *testing.T) {
@@ -112,24 +89,14 @@ func TestMultiHandler(t *testing.T) {
11289

11390
logger.Info("user login", "user_id", 42)
11491

115-
// Check if the Text handler contains the group.
116-
expectedText := "req.user_id=42"
117-
if !strings.Contains(buf1.String(), expectedText) {
118-
t.Errorf("Text handler output missing group. Expected to contain %q, Got: %s", expectedText, buf1.String())
119-
}
120-
121-
// Check if the JSON handler contains the group.
122-
expectedJSON := `"req":{"user_id":42}`
123-
if !strings.Contains(buf2.String(), expectedJSON) {
124-
t.Errorf("JSON handler output missing group. Expected to contain %q, Got: %s", expectedJSON, buf2.String())
125-
}
92+
checkLogOutput(t, buf1.String(), "time="+textTimeRE+` level=INFO msg="user login" req.user_id=42`)
93+
checkLogOutput(t, buf2.String(), `{"time":"`+jsonTimeRE+`","level":"INFO","msg":"user login","req":{"user_id":42}}`)
12694
})
12795

12896
t.Run("Handle propagates errors from handlers", func(t *testing.T) {
12997
var buf bytes.Buffer
13098
h1 := NewTextHandler(&buf, nil)
13199

132-
// Simulate a handler that will fail.
133100
errFail := errors.New("fake fail")
134101
h2 := &mockFailingHandler{
135102
Handler: NewTextHandler(&bytes.Buffer{}, nil),
@@ -138,32 +105,21 @@ func TestMultiHandler(t *testing.T) {
138105

139106
multi := MultiHandler(h1, h2)
140107

141-
err := multi.Handle(ctx, NewRecord(time.Now(), LevelInfo, "test message", 0))
142-
143-
// Check if the error was returned correctly.
144-
if err == nil {
145-
t.Fatal("Expected an error from Handle, but got nil")
146-
}
108+
err := multi.Handle(context.Background(), NewRecord(time.Now(), LevelInfo, "test message", 0))
147109
if !errors.Is(err, errFail) {
148110
t.Errorf("Expected error: %v, but got: %v", errFail, err)
149111
}
150112

151-
// Also, check that the successful handler still output the log.
152-
if !strings.Contains(buf.String(), "test message") {
153-
t.Error("The successful handler should still have processed the log")
154-
}
113+
checkLogOutput(t, buf.String(), "time="+textTimeRE+` level=INFO msg="test message"`)
155114
})
156115

157116
t.Run("Handle with no handlers", func(t *testing.T) {
158-
// Create an empty multi-handler.
159117
multi := MultiHandler()
160118
logger := New(multi)
161119

162-
// This should be safe to call and do nothing.
163-
logger.Info("this is nothing")
120+
logger.Info("nothing")
164121

165-
// Calling Handle directly should also be safe.
166-
err := multi.Handle(ctx, NewRecord(time.Now(), LevelInfo, "test", 0))
122+
err := multi.Handle(context.Background(), NewRecord(time.Now(), LevelInfo, "test", 0))
167123
if err != nil {
168124
t.Errorf("Handle with no sub-handlers should return nil, but got: %v", err)
169125
}

0 commit comments

Comments
 (0)