Skip to content

Commit 9ed1532

Browse files
committed
slog: reimplement defaultHandler using commonHandler
The defaultHandler can be viewed as a variant of a TextHandler. Re-use commonHandler to implement it. Test groups, Marshaler and scopes for it. Change-Id: I7600e5b82d4d8189024ae94f760848eaedf5d4db Reviewed-on: https://go-review.googlesource.com/c/exp/+/439055 Reviewed-by: Alan Donovan <adonovan@google.com> Run-TryBot: Jonathan Amsterdam <jba@google.com>
1 parent 09be386 commit 9ed1532

File tree

4 files changed

+117
-73
lines changed

4 files changed

+117
-73
lines changed

slog/handler.go

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"fmt"
99
"io"
1010
"strconv"
11-
"strings"
1211
"sync"
1312
"time"
1413

@@ -61,8 +60,16 @@ type Handler interface {
6160
}
6261

6362
type defaultHandler struct {
64-
attrs []Attr
65-
output func(int, string) error // log.Output, except for testing
63+
ch *commonHandler
64+
// log.Output, except for testing
65+
output func(calldepth int, message string) error
66+
}
67+
68+
func newDefaultHandler(output func(int, string) error) *defaultHandler {
69+
return &defaultHandler{
70+
ch: &commonHandler{json: false},
71+
output: output,
72+
}
6673
}
6774

6875
func (*defaultHandler) Enabled(l Level) bool {
@@ -73,34 +80,23 @@ func (*defaultHandler) Enabled(l Level) bool {
7380
// write it with the default log.Logger.
7481
// Let the log.Logger handle time and file/line.
7582
func (h *defaultHandler) Handle(r Record) error {
76-
var b strings.Builder
77-
b.WriteString(r.Level().String())
78-
b.WriteByte(' ')
79-
for _, a := range h.attrs {
80-
h.writeAttr(&b, a)
81-
}
82-
r.Attrs(func(a Attr) {
83-
h.writeAttr(&b, a)
84-
})
85-
b.WriteString(r.Message())
86-
return h.output(4, b.String())
87-
}
88-
89-
func (h *defaultHandler) writeAttr(b *strings.Builder, a Attr) {
90-
b.WriteString(a.Key)
91-
b.WriteByte('=')
92-
b.WriteString(a.Value.Resolve().String())
93-
b.WriteByte(' ')
83+
buf := buffer.New()
84+
defer buf.Free()
85+
buf.WriteString(r.Level().String())
86+
buf.WriteByte(' ')
87+
buf.WriteString(r.Message())
88+
state := handleState{h: h.ch, buf: buf, sep: " "}
89+
state.appendNonBuiltIns(r)
90+
// 4 = log.Output depth + handlerWriter.Write + defaultHandler.Handle
91+
return h.output(4, buf.String())
9492
}
9593

96-
func (d *defaultHandler) With(as []Attr) Handler {
97-
d2 := *d
98-
d2.attrs = concat(d2.attrs, as)
99-
return &d2
94+
func (h *defaultHandler) With(as []Attr) Handler {
95+
return &defaultHandler{h.ch.withAttrs(as), h.output}
10096
}
10197

10298
func (h *defaultHandler) WithScope(name string) Handler {
103-
panic("unimplemented")
99+
return &defaultHandler{h.ch.withScope(name), h.output}
104100
}
105101

106102
// HandlerOptions are options for a TextHandler or JSONHandler.
@@ -254,34 +250,38 @@ func (h *commonHandler) handle(r Record) error {
254250
} else {
255251
state.appendAttr(String(key, msg))
256252
}
253+
state.appendNonBuiltIns(r)
254+
state.buf.WriteByte('\n')
255+
256+
h.mu.Lock()
257+
defer h.mu.Unlock()
258+
_, err := h.w.Write(*state.buf)
259+
return err
260+
}
261+
262+
func (s *handleState) appendNonBuiltIns(r Record) {
257263
// preformatted Attrs
258-
if len(h.preformattedAttrs) > 0 {
259-
state.buf.WriteString(state.sep)
260-
state.buf.Write(h.preformattedAttrs)
261-
state.sep = h.attrSep()
264+
if len(s.h.preformattedAttrs) > 0 {
265+
s.buf.WriteString(s.sep)
266+
s.buf.Write(s.h.preformattedAttrs)
267+
s.sep = s.h.attrSep()
262268
}
263269
// Attrs in Record -- unlike the built-in ones, they are scoped.
264-
state.prefix = buffer.New()
265-
defer state.prefix.Free()
266-
state.prefix.WriteString(h.scopePrefix)
267-
state.openScopes()
270+
s.prefix = buffer.New()
271+
defer s.prefix.Free()
272+
s.prefix.WriteString(s.h.scopePrefix)
273+
s.openScopes()
268274
r.Attrs(func(a Attr) {
269-
state.appendAttr(a)
275+
s.appendAttr(a)
270276
})
271-
if h.json {
277+
if s.h.json {
272278
// Close all open scopes.
273-
for range h.scopes {
274-
state.buf.WriteByte('}')
279+
for range s.h.scopes {
280+
s.buf.WriteByte('}')
275281
}
276282
// Close the top-level object.
277-
state.buf.WriteByte('}')
283+
s.buf.WriteByte('}')
278284
}
279-
state.buf.WriteByte('\n')
280-
281-
h.mu.Lock()
282-
defer h.mu.Unlock()
283-
_, err := h.w.Write(*state.buf)
284-
return err
285285
}
286286

287287
// attrSep returns the separator between attributes.

slog/handler_test.go

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,43 +16,87 @@ import (
1616
)
1717

1818
func TestDefaultHandle(t *testing.T) {
19+
preAttrs := []Attr{Int("pre", 0)}
20+
attrs := []Attr{Int("a", 1), String("b", "two")}
1921
for _, test := range []struct {
20-
name string
21-
withAttrs []Attr
22-
attrs []Attr
23-
want string
22+
name string
23+
with func(Handler) Handler
24+
attrs []Attr
25+
want string
2426
}{
2527
{
2628
name: "no attrs",
2729
want: "INFO message",
2830
},
2931
{
3032
name: "attrs",
31-
attrs: []Attr{Int("a", 1), String("b", "two")},
32-
want: "INFO a=1 b=two message",
33+
attrs: attrs,
34+
want: "INFO message a=1 b=two",
3335
},
3436
{
35-
name: "pre attrs",
36-
withAttrs: []Attr{Int("pre", 0)},
37-
attrs: []Attr{Int("a", 1)},
38-
want: "INFO pre=0 a=1 message",
37+
name: "preformatted",
38+
with: func(h Handler) Handler { return h.With(preAttrs) },
39+
attrs: attrs,
40+
want: "INFO message pre=0 a=1 b=two",
3941
},
40-
} {
41-
var got string
42-
var d Handler = &defaultHandler{output: func(_ int, s string) error {
43-
got = s
44-
return nil
42+
{
43+
name: "groups",
44+
attrs: []Attr{
45+
Int("a", 1),
46+
Group("g",
47+
Int("b", 2),
48+
Group("h", Int("c", 3)),
49+
Int("d", 4)),
50+
Int("e", 5),
51+
},
52+
want: "INFO message a=1 g.b=2 g.h.c=3 g.d=4 e=5",
4553
},
46-
}
47-
d = d.With(test.withAttrs)
48-
r := NewRecord(time.Time{}, InfoLevel, "message", 0)
49-
r.AddAttrs(test.attrs...)
50-
if err := d.Handle(r); err != nil {
51-
t.Fatal(err)
52-
}
53-
if got != test.want {
54-
t.Errorf("\ngot %s\nwant %s", got, test.want)
55-
}
54+
{
55+
name: "scope",
56+
with: func(h Handler) Handler { return h.With(preAttrs).WithScope("s") },
57+
attrs: attrs,
58+
want: "INFO message pre=0 s.a=1 s.b=two",
59+
},
60+
{
61+
name: "preformatted scopes",
62+
with: func(h Handler) Handler {
63+
return h.With([]Attr{Int("p1", 1)}).
64+
WithScope("s1").
65+
With([]Attr{Int("p2", 2)}).
66+
WithScope("s2")
67+
},
68+
attrs: attrs,
69+
want: "INFO message p1=1 s1.p2=2 s1.s2.a=1 s1.s2.b=two",
70+
},
71+
{
72+
name: "two scopes",
73+
with: func(h Handler) Handler {
74+
return h.With([]Attr{Int("p1", 1)}).
75+
WithScope("s1").
76+
WithScope("s2")
77+
},
78+
attrs: attrs,
79+
want: "INFO message p1=1 s1.s2.a=1 s1.s2.b=two",
80+
},
81+
} {
82+
t.Run(test.name, func(t *testing.T) {
83+
var got string
84+
var h Handler = newDefaultHandler(func(_ int, s string) error {
85+
got = s
86+
return nil
87+
})
88+
if test.with != nil {
89+
h = test.with(h)
90+
}
91+
r := NewRecord(time.Time{}, InfoLevel, "message", 0)
92+
r.AddAttrs(test.attrs...)
93+
if err := h.Handle(r); err != nil {
94+
t.Fatal(err)
95+
}
96+
if got != test.want {
97+
t.Errorf("\ngot %s\nwant %s", got, test.want)
98+
}
99+
})
56100
}
57101
}
58102

slog/logger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ var defaultLogger atomic.Value
1414

1515
func init() {
1616
defaultLogger.Store(&Logger{
17-
handler: &defaultHandler{output: log.Output},
17+
handler: newDefaultHandler(log.Output),
1818
})
1919
}
2020

slog/logger_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@ func TestConnections(t *testing.T) {
6060
log.SetFlags(log.Flags() | log.Lshortfile)
6161
Info("msg", "a", 1)
6262
checkLogOutput(t, logbuf.String(),
63-
`\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} logger_test.go:\d\d: INFO a=1 msg`)
63+
`\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} logger_test.go:\d\d: INFO msg a=1`)
6464
logbuf.Reset()
6565
Warn("msg", "b", 2)
6666
checkLogOutput(t, logbuf.String(),
67-
`\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} logger_test.go:\d\d: WARN b=2 msg`)
67+
`\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} logger_test.go:\d\d: WARN msg b=2`)
6868
// Levels below Info are not printed.
6969
logbuf.Reset()
7070
Debug("msg", "c", 3)

0 commit comments

Comments
 (0)