Skip to content

Commit 4365e8f

Browse files
committed
slog: rewrite commonHandler
Reorganize and simplify commonHandler.Handle. - Simplify the appender interface by removing all state from it. - Introduce a handleState struct to track state. In particular, we have to track whether or not we have written the first key, to know when to write a separator. This fixes a bug in the previous version. Also, improve commonHandler tests to check preformatted Attrs. Change-Id: Ia87eee200cea1617ff5bae486e8a52eab7aadc5d Reviewed-on: https://go-review.googlesource.com/c/exp/+/432184 Reviewed-by: Alan Donovan <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]>
1 parent 67e04e6 commit 4365e8f

File tree

6 files changed

+209
-219
lines changed

6 files changed

+209
-219
lines changed

slog/handler.go

Lines changed: 102 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ type HandlerOptions struct {
9393
}
9494

9595
type commonHandler struct {
96-
newAppender func(*buffer.Buffer) appender
9796
opts HandlerOptions
98-
attrs []Attr
97+
app appender
98+
attrSep byte // char separating attrs from each other
9999
preformattedAttrs []byte
100100
mu sync.Mutex
101101
w io.Writer
@@ -109,132 +109,162 @@ func (h *commonHandler) Enabled(l Level) bool {
109109

110110
func (h *commonHandler) with(as []Attr) *commonHandler {
111111
h2 := &commonHandler{
112-
newAppender: h.newAppender,
112+
app: h.app,
113+
attrSep: h.attrSep,
113114
opts: h.opts,
114-
attrs: concat(h.attrs, as),
115115
preformattedAttrs: h.preformattedAttrs,
116116
w: h.w,
117117
}
118-
if h.opts.ReplaceAttr != nil {
119-
for i, p := range h2.attrs[len(h.attrs):] {
120-
h2.attrs[i] = h.opts.ReplaceAttr(p)
121-
}
122-
}
123-
124118
// Pre-format the attributes as an optimization.
125-
app := h2.newAppender((*buffer.Buffer)(&h2.preformattedAttrs))
126-
for _, p := range h2.attrs[len(h.attrs):] {
127-
appendAttr(app, p)
119+
state := handleState{
120+
h2,
121+
(*buffer.Buffer)(&h2.preformattedAttrs),
122+
false,
123+
}
124+
for _, a := range as {
125+
state.appendAttr(a)
128126
}
129127
return h2
130128
}
131129

132130
func (h *commonHandler) handle(r Record) error {
133-
buf := buffer.New()
134-
defer buf.Free()
135-
app := h.newAppender(buf)
136131
rep := h.opts.ReplaceAttr
137-
replace := func(a Attr) {
138-
a = rep(a)
139-
if a.Key() != "" {
140-
app.appendKey(a.Key())
141-
if err := app.appendAttrValue(a); err != nil {
142-
app.appendString(fmt.Sprintf("!ERROR:%v", err))
143-
}
144-
}
145-
}
146-
147-
app.appendStart()
132+
state := handleState{h, buffer.New(), false}
133+
defer state.buf.Free()
134+
h.app.appendStart(state.buf)
135+
// time
148136
if !r.Time().IsZero() {
149137
key := "time"
150138
val := r.Time().Round(0) // strip monotonic to match Attr behavior
151139
if rep == nil {
152-
app.appendKey(key)
153-
if err := app.appendTime(val); err != nil {
154-
return err
155-
}
140+
state.appendKey(key)
141+
state.appendTime(val)
156142
} else {
157-
replace(Time(key, val))
143+
state.appendAttr(Time(key, val))
158144
}
159-
app.appendSep()
160145
}
146+
// level
161147
if r.Level() != 0 {
162148
key := "level"
163149
val := r.Level()
164150
if rep == nil {
165-
app.appendKey(key)
166-
app.appendString(val.String())
151+
state.appendKey(key)
152+
state.appendString(val.String())
167153
} else {
168-
replace(Any(key, val))
154+
state.appendAttr(Any(key, val))
169155
}
170-
app.appendSep()
171156
}
157+
// source
172158
if h.opts.AddSource {
173159
file, line := r.SourceLine()
174160
if file != "" {
175161
key := "source"
176162
if rep == nil {
177-
app.appendKey(key)
178-
app.appendSource(file, line)
163+
state.appendKey(key)
164+
h.app.appendSource(state.buf, file, line)
179165
} else {
180166
buf := buffer.New()
181-
buf.WriteString(file)
167+
buf.WriteString(file) // TODO: escape?
182168
buf.WriteByte(':')
183169
itoa((*[]byte)(buf), line, -1)
184170
s := buf.String()
185171
buf.Free()
186-
replace(String(key, s))
172+
state.appendAttr(String(key, s))
187173
}
188-
app.appendSep()
189174
}
190175
}
176+
// message
191177
key := "msg"
192178
val := r.Message()
193179
if rep == nil {
194-
app.appendKey(key)
195-
app.appendString(val)
180+
state.appendKey(key)
181+
state.appendString(val)
196182
} else {
197-
replace(String(key, val))
183+
state.appendAttr(String(key, val))
198184
}
199-
*buf = append(*buf, h.preformattedAttrs...)
185+
// preformatted Attrs
186+
if len(h.preformattedAttrs) > 0 {
187+
state.appendSep()
188+
state.buf.Write(h.preformattedAttrs)
189+
}
190+
// Attrs in Record
200191
r.Attrs(func(a Attr) {
201-
if rep != nil {
202-
a = rep(a)
203-
}
204-
appendAttr(app, a)
192+
state.appendAttr(a)
205193
})
206-
app.appendEnd()
207-
buf.WriteByte('\n')
194+
h.app.appendEnd(state.buf)
195+
state.buf.WriteByte('\n')
208196

209197
h.mu.Lock()
210198
defer h.mu.Unlock()
211-
_, err := h.w.Write(*buf)
199+
_, err := h.w.Write(*state.buf)
212200
return err
213201
}
214202

215-
func appendAttr(app appender, a Attr) {
216-
if a.Key() != "" {
217-
app.appendSep()
218-
app.appendKey(a.Key())
219-
if err := app.appendAttrValue(a); err != nil {
220-
app.appendString(fmt.Sprintf("!ERROR:%v", err))
221-
}
203+
// handleState holds state for a single call to commonHandler.handle.
204+
// The initial value of sep determines whether to emit a separator
205+
// before the next key, after which it stays true.
206+
type handleState struct {
207+
h *commonHandler
208+
buf *buffer.Buffer
209+
sep bool // Append separator before next Attr?
210+
}
211+
212+
// appendAttr appends the Attr's key and value using app.
213+
// If sep is true, it also prepends a separator.
214+
// It handles replacement and checking for an empty key.
215+
// It sets sep to true if it actually did the append (if the key was non-empty
216+
// after replacement).
217+
func (s *handleState) appendAttr(a Attr) {
218+
if rep := s.h.opts.ReplaceAttr; rep != nil {
219+
a = rep(a)
222220
}
221+
if a.Key() == "" {
222+
return
223+
}
224+
s.appendKey(a.Key())
225+
s.appendAttrValue(a)
226+
}
227+
228+
func (s *handleState) appendError(err error) {
229+
s.appendString(fmt.Sprintf("!ERROR:%v", err))
223230
}
224231

225-
// An appender appends keys and values to a buffer.
226-
// TextHandler and JSONHandler both implement it.
227-
// It factors out the format-specific parts of the job.
228-
// Other than growing the buffer, none of the methods should allocate.
229232
type appender interface {
230-
appendStart() // start a sequence of Attrs
231-
appendEnd() // end a sequence of Attrs
232-
appendSep() // separate one Attr from the next
233-
appendKey(key string) // append a key
234-
appendString(string) // append a string that may need to be escaped
235-
appendTime(time.Time) error // append a time
236-
appendSource(file string, line int) // append file:line
237-
appendAttrValue(a Attr) error // append the Attr's value (but not its key)
233+
appendStart(*buffer.Buffer) // start of output
234+
appendEnd(*buffer.Buffer) // end of output
235+
appendKey(*buffer.Buffer, string) // append key and key-value separator
236+
appendString(*buffer.Buffer, string) // append a string
237+
appendSource(*buffer.Buffer, string, int) // append a filename and line
238+
appendTime(*buffer.Buffer, time.Time) error // append a time
239+
appendAttrValue(*buffer.Buffer, Attr) error // append Attr's value (but not key)
240+
}
241+
242+
func (s *handleState) appendSep() {
243+
if s.sep {
244+
s.buf.WriteByte(s.h.attrSep)
245+
}
246+
}
247+
248+
func (s *handleState) appendKey(key string) {
249+
s.appendSep()
250+
s.h.app.appendKey(s.buf, key)
251+
s.sep = true
252+
}
253+
254+
func (s *handleState) appendString(str string) {
255+
s.h.app.appendString(s.buf, str)
256+
}
257+
258+
func (s *handleState) appendAttrValue(a Attr) {
259+
if err := s.h.app.appendAttrValue(s.buf, a); err != nil {
260+
s.appendError(err)
261+
}
262+
}
263+
264+
func (s *handleState) appendTime(t time.Time) {
265+
if err := s.h.app.appendTime(s.buf, t); err != nil {
266+
s.appendError(err)
267+
}
238268
}
239269

240270
// This takes half the time of Time.AppendFormat.

slog/handler_test.go

Lines changed: 32 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@ package slog
88

99
import (
1010
"bytes"
11-
"fmt"
1211
"strings"
1312
"testing"
1413
"time"
15-
16-
"golang.org/x/exp/slog/internal/buffer"
1714
)
1815

1916
func TestDefaultWith(t *testing.T) {
@@ -33,45 +30,61 @@ func TestDefaultWith(t *testing.T) {
3330
}
3431
}
3532

33+
// NOTE TO REVIEWER: Ignore this test. The next CL revamps it.
3634
func TestCommonHandle(t *testing.T) {
3735
tm := time.Date(2022, 9, 18, 8, 26, 33, 0, time.UTC)
3836
r := NewRecord(tm, InfoLevel, "message", 1)
3937
r.AddAttrs(String("a", "one"), Int("b", 2), Any("", "ignore me"))
4038

39+
newHandler := func(replace func(Attr) Attr) *commonHandler {
40+
return &commonHandler{
41+
app: textAppender{},
42+
attrSep: ' ',
43+
opts: HandlerOptions{ReplaceAttr: replace},
44+
}
45+
}
46+
47+
removeAttr := func(a Attr) Attr { return Attr{} }
48+
4149
for _, test := range []struct {
4250
name string
4351
h *commonHandler
4452
want string
4553
}{
4654
{
4755
name: "basic",
48-
h: &commonHandler{},
49-
want: "(time=2022-09-18T08:26:33.000Z;level=INFO;msg=message;a=one;b=2)",
56+
h: newHandler(nil),
57+
want: "time=2022-09-18T08:26:33.000Z level=INFO msg=message a=one b=2",
5058
},
5159
{
5260
name: "cap keys",
53-
h: &commonHandler{
54-
opts: HandlerOptions{ReplaceAttr: upperCaseKey},
55-
},
56-
want: "(TIME=2022-09-18T08:26:33.000Z;LEVEL=INFO;MSG=message;A=one;B=2)",
61+
h: newHandler(upperCaseKey),
62+
want: "TIME=2022-09-18T08:26:33.000Z LEVEL=INFO MSG=message A=one B=2",
5763
},
5864
{
5965
name: "remove all",
60-
h: &commonHandler{
61-
opts: HandlerOptions{
62-
ReplaceAttr: func(a Attr) Attr { return Attr{} },
63-
},
64-
},
65-
// TODO: fix this. The correct result is "()".
66-
want: "(;;)",
66+
h: newHandler(removeAttr),
67+
want: "",
68+
},
69+
{
70+
name: "preformatted",
71+
h: newHandler(nil).with([]Attr{Int("pre", 3), String("x", "y")}),
72+
want: "time=2022-09-18T08:26:33.000Z level=INFO msg=message pre=3 x=y a=one b=2",
73+
},
74+
{
75+
name: "preformatted cap keys",
76+
h: newHandler(upperCaseKey).with([]Attr{Int("pre", 3), String("x", "y")}),
77+
want: "TIME=2022-09-18T08:26:33.000Z LEVEL=INFO MSG=message PRE=3 X=y A=one B=2",
78+
},
79+
{
80+
name: "preformatted remove all",
81+
h: newHandler(removeAttr).with([]Attr{Int("pre", 3), String("x", "y")}),
82+
want: "",
6783
},
6884
} {
6985
t.Run(test.name, func(t *testing.T) {
7086
var buf bytes.Buffer
7187
test.h.w = &buf
72-
test.h.newAppender = func(buf *buffer.Buffer) appender {
73-
return &testAppender{buf}
74-
}
7588
if err := test.h.handle(r); err != nil {
7689
t.Fatal(err)
7790
}
@@ -87,41 +100,6 @@ func upperCaseKey(a Attr) Attr {
87100
return a.WithKey(strings.ToUpper(a.Key()))
88101
}
89102

90-
type testAppender struct {
91-
buf *buffer.Buffer
92-
}
93-
94-
func (a *testAppender) appendStart() { a.buf.WriteByte('(') }
95-
func (a *testAppender) appendSep() { a.buf.WriteByte(';') }
96-
func (a *testAppender) appendEnd() { a.buf.WriteByte(')') }
97-
98-
func (a *testAppender) appendKey(key string) {
99-
a.appendString(key)
100-
a.buf.WriteByte('=')
101-
}
102-
func (a *testAppender) appendString(s string) { a.buf.WriteString(s) }
103-
104-
func (a *testAppender) appendTime(t time.Time) error {
105-
*a.buf = appendTimeRFC3339Millis(*a.buf, t)
106-
return nil
107-
}
108-
109-
func (a *testAppender) appendSource(file string, line int) {
110-
a.appendString(fmt.Sprintf("%s:%d", file, line))
111-
}
112-
113-
func (a *testAppender) appendAttrValue(at Attr) error {
114-
switch at.Kind() {
115-
case StringKind:
116-
a.appendString(at.str())
117-
case TimeKind:
118-
a.appendTime(at.Time())
119-
default:
120-
*a.buf = at.appendValue(*a.buf)
121-
}
122-
return nil
123-
}
124-
125103
const rfc3339Millis = "2006-01-02T15:04:05.000Z07:00"
126104

127105
func TestAppendTimeRFC3339(t *testing.T) {

0 commit comments

Comments
 (0)