Skip to content

Commit 1cac5f9

Browse files
committed
slog: Handler interface and default implementation
The Handler is the "backend" of the package. Two user-visible Handler implementations, and tests that use them, will arrive in later CLs. Change-Id: I5e6296f803f3dcaf44a96f50d00c80ad4cab6c6b Reviewed-on: https://go-review.googlesource.com/c/exp/+/426024 Reviewed-by: Alan Donovan <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]>
1 parent bee5968 commit 1cac5f9

File tree

2 files changed

+416
-0
lines changed

2 files changed

+416
-0
lines changed

slog/handler.go

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
// Copyright 2022 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+
"fmt"
9+
"io"
10+
"log"
11+
"strings"
12+
"sync"
13+
"time"
14+
15+
"golang.org/x/exp/slog/internal/buffer"
16+
)
17+
18+
// A Handler processes log records produced by Logger output.
19+
// Any of the Handler's methods may be called concurrently with itself
20+
// or with other methods. It is the responsibility of the Handler to
21+
// manage this concurrency.
22+
type Handler interface {
23+
// Enabled reports whether this handler is accepting records
24+
// at the given level.
25+
Enabled(Level) bool
26+
27+
// Handle processes the Record.
28+
// Handle methods that produce output should observe the following rules:
29+
// - If r.Time() is the zero time, do not output the time.
30+
// - If r.Level() is Level(0), do not output the level.
31+
// - If an Attr's key is the empty string, do not output the Attr.
32+
Handle(r Record) error
33+
34+
// With returns a new Handler whose attributes consist of
35+
// the receiver's attributes concatenated with the arguments.
36+
// The Handler owns the slice: it may retain, modify or discard it.
37+
With(attrs []Attr) Handler
38+
}
39+
40+
type defaultHandler struct {
41+
attrs []Attr
42+
}
43+
44+
func (*defaultHandler) Enabled(Level) bool { return true }
45+
46+
// Collect the level, attributes and message in a string and
47+
// write it with the default log.Logger.
48+
// Let the log.Logger handle time and file/line.
49+
func (h *defaultHandler) Handle(r Record) error {
50+
var b strings.Builder
51+
if r.Level() > 0 {
52+
b.WriteString(r.Level().String())
53+
b.WriteByte(' ')
54+
}
55+
for i := 0; i < r.NumAttrs(); i++ {
56+
fmt.Fprint(&b, r.Attr(i)) // Attr.Format will print key=value
57+
b.WriteByte(' ')
58+
}
59+
b.WriteString(r.Message())
60+
return log.Output(4, b.String())
61+
}
62+
63+
func (d *defaultHandler) With(as []Attr) Handler {
64+
d2 := *d
65+
d2.attrs = concat(d2.attrs, as)
66+
return &d2
67+
}
68+
69+
// HandlerOptions are options for a TextHandler or JSONHandler.
70+
// A zero HandlerOptions consists entirely of default values.
71+
type HandlerOptions struct {
72+
// Add a "source" attribute to the output whose value is of the form
73+
// "file:line".
74+
AddSource bool
75+
76+
// Ignore records with levels above AtomicLevel.Level.
77+
// If nil, accept all levels.
78+
AtomicLevel *AtomicLevel
79+
80+
// If set, ReplaceAttr is called on each attribute of the message,
81+
// and the returned value is used instead of the original. If the returned
82+
// key is empty, the attribute is omitted from the output.
83+
//
84+
// The built-in attributes with keys "time", "level", "source", and "msg"
85+
// are passed to this function first, except that time and level are omitted
86+
// if zero, and source is omitted if AddSourceLine is false.
87+
ReplaceAttr func(a Attr) Attr
88+
}
89+
90+
type commonHandler struct {
91+
appender
92+
opts HandlerOptions
93+
attrs []Attr
94+
preformattedAttrs []byte
95+
mu sync.Mutex
96+
w io.Writer
97+
}
98+
99+
// Enabled reports whether l is less than or equal to the
100+
// maximum level.
101+
func (h *commonHandler) Enabled(l Level) bool {
102+
return l <= h.opts.AtomicLevel.Level()
103+
}
104+
105+
func (h *commonHandler) with(as []Attr) *commonHandler {
106+
h2 := &commonHandler{
107+
appender: h.appender,
108+
opts: h.opts,
109+
attrs: concat(h.attrs, as),
110+
preformattedAttrs: h.preformattedAttrs,
111+
w: h.w,
112+
}
113+
if h.opts.ReplaceAttr != nil {
114+
for i, p := range h2.attrs[len(h.attrs):] {
115+
h2.attrs[i] = h.opts.ReplaceAttr(p)
116+
}
117+
}
118+
119+
// Pre-format the attributes as an optimization.
120+
h2.setBuffer((*buffer.Buffer)(&h2.preformattedAttrs))
121+
for _, p := range h2.attrs[len(h.attrs):] {
122+
h2.appendAttr(p)
123+
}
124+
return h2
125+
}
126+
127+
func (h *commonHandler) handle(r Record) error {
128+
buf := buffer.New()
129+
defer buf.Free()
130+
h.setBuffer(buf)
131+
132+
rep := h.opts.ReplaceAttr
133+
134+
replace := func(a Attr) {
135+
a = rep(a)
136+
if a.Key() != "" {
137+
h.appendKey(a.Key())
138+
h.appendAttrValue(a)
139+
}
140+
}
141+
142+
h.appendStart()
143+
if !r.Time().IsZero() {
144+
key := "time"
145+
val := r.Time().Round(0) // strip monotonic to match Attr behavior
146+
if rep == nil {
147+
h.appendKey(key)
148+
h.appendTime(val)
149+
} else {
150+
replace(Time(key, val))
151+
}
152+
h.appendSep()
153+
}
154+
if r.Level() != 0 {
155+
key := "level"
156+
val := r.Level()
157+
if rep == nil {
158+
h.appendKey(key)
159+
h.appendString(val.String())
160+
} else {
161+
// Don't use replace: we want to output Levels as strings
162+
// to match the above.
163+
a := rep(Any(key, val))
164+
if a.Key() != "" {
165+
h.appendKey(a.Key())
166+
if l, ok := a.any.(Level); ok {
167+
h.appendString(l.String())
168+
} else {
169+
h.appendAttrValue(a)
170+
}
171+
}
172+
}
173+
h.appendSep()
174+
}
175+
if h.opts.AddSource {
176+
file, line := r.SourceLine()
177+
if file != "" {
178+
key := "source"
179+
if rep == nil {
180+
h.appendKey(key)
181+
h.appendSource(file, line)
182+
} else {
183+
buf := buffer.New()
184+
buf.WriteString(file)
185+
buf.WriteByte(':')
186+
itoa((*[]byte)(buf), line, -1)
187+
s := string(*buf)
188+
buf.Free()
189+
replace(String(key, s))
190+
}
191+
h.appendSep()
192+
}
193+
}
194+
key := "msg"
195+
val := r.Message()
196+
if rep == nil {
197+
h.appendKey(key)
198+
h.appendString(val)
199+
} else {
200+
replace(String(key, val))
201+
}
202+
*buf = append(*buf, h.preformattedAttrs...)
203+
for i := 0; i < r.NumAttrs(); i++ {
204+
a := r.Attr(i)
205+
if rep != nil {
206+
a = rep(a)
207+
}
208+
h.appendAttr(a)
209+
}
210+
h.appendEnd()
211+
buf.WriteByte('\n')
212+
213+
h.mu.Lock()
214+
defer h.mu.Unlock()
215+
_, err := h.w.Write(*buf)
216+
return err
217+
}
218+
219+
func (h *commonHandler) appendAttr(a Attr) {
220+
if a.Key() != "" {
221+
h.appendSep()
222+
h.appendKey(a.Key())
223+
if err := h.appendAttrValue(a); err != nil {
224+
h.appendString(fmt.Sprintf("!ERROR:%v", err))
225+
}
226+
}
227+
}
228+
229+
// An appender appends keys and values to a buffer.
230+
// TextHandler and JSONHandler both implement it.
231+
type appender interface {
232+
setBuffer(*buffer.Buffer) // set the buffer to use for output
233+
appendStart() // start a sequence of Attrs
234+
appendEnd() // end a sequence of Attrs
235+
appendSep() // separate one Attr from the next
236+
appendKey(key string) // append a key
237+
appendString(string) // append a string that may need to be escaped
238+
appendTime(time.Time) // append a time
239+
appendSource(file string, line int) // append file:line
240+
appendAttrValue(a Attr) error // append the Attr's value (but not its key)
241+
}
242+
243+
// This takes half the time of Time.AppendFormat.
244+
func appendTimeRFC3339Millis(buf []byte, t time.Time) []byte {
245+
// TODO: try to speed up by indexing the buffer.
246+
char := func(b byte) {
247+
buf = append(buf, b)
248+
}
249+
250+
year, month, day := t.Date()
251+
itoa(&buf, year, 4)
252+
char('-')
253+
itoa(&buf, int(month), 2)
254+
char('-')
255+
itoa(&buf, day, 2)
256+
char('T')
257+
hour, min, sec := t.Clock()
258+
itoa(&buf, hour, 2)
259+
char(':')
260+
itoa(&buf, min, 2)
261+
char(':')
262+
itoa(&buf, sec, 2)
263+
ns := t.Nanosecond()
264+
char('.')
265+
itoa(&buf, ns/1e6, 3)
266+
_, offsetSeconds := t.Zone()
267+
if offsetSeconds == 0 {
268+
char('Z')
269+
} else {
270+
offsetMinutes := offsetSeconds / 60
271+
if offsetMinutes < 0 {
272+
char('-')
273+
offsetMinutes = -offsetMinutes
274+
} else {
275+
char('+')
276+
}
277+
itoa(&buf, offsetMinutes/60, 2)
278+
char(':')
279+
itoa(&buf, offsetMinutes%60, 2)
280+
}
281+
return buf
282+
}

0 commit comments

Comments
 (0)