blob: 2661ac144638fa53d1064460e610f384debbbbce [file] [log] [blame]
// Copyright 2022 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 (
"fmt"
"io"
"log"
"strings"
"sync"
"time"
"golang.org/x/exp/slog/internal/buffer"
)
// A Handler handles log records produced by a Logger..
//
// A typical handler may print log records to standard error,
// or write them to a file or database, or perhaps augment them
// with additional attributes and pass them on to another handler.
//
// Any of the Handler's methods may be called concurrently with itself
// or with other methods. It is the responsibility of the Handler to
// manage this concurrency.
type Handler interface {
// Enabled reports whether the handler handles records at the given level.
// The handler ignores records whose level is lower.
Enabled(Level) bool
// Handle handles the Record.
// Handle methods that produce output should observe the following rules:
// - If r.Time() is the zero time, ignore the time.
// - If r.Level() is Level(0), ignore the level.
// - If an Attr's key is the empty string, ignore the Attr.
Handle(r Record) error
// With returns a new Handler whose attributes consist of
// the receiver's attributes concatenated with the arguments.
// The Handler owns the slice: it may retain, modify or discard it.
With(attrs []Attr) Handler
}
type defaultHandler struct {
attrs []Attr
}
func (*defaultHandler) Enabled(Level) bool { return true }
// Collect the level, attributes and message in a string and
// write it with the default log.Logger.
// Let the log.Logger handle time and file/line.
func (h *defaultHandler) Handle(r Record) error {
var b strings.Builder
if r.Level() > 0 {
b.WriteString(r.Level().String())
b.WriteByte(' ')
}
r.Attrs(func(a Attr) {
fmt.Fprint(&b, a) // Attr.Format will print key=value
b.WriteByte(' ')
})
b.WriteString(r.Message())
return log.Output(4, b.String())
}
func (d *defaultHandler) With(as []Attr) Handler {
d2 := *d
d2.attrs = concat(d2.attrs, as)
return &d2
}
// HandlerOptions are options for a TextHandler or JSONHandler.
// A zero HandlerOptions consists entirely of default values.
type HandlerOptions struct {
// Add a "source" attribute to the output whose value is of the form
// "file:line".
AddSource bool
// Ignore records with levels above Level.Level.
// If nil, accept all levels.
Level *AtomicLevel
// If set, ReplaceAttr is called on each attribute of the message,
// and the returned value is used instead of the original. If the returned
// key is empty, the attribute is omitted from the output.
//
// The built-in attributes with keys "time", "level", "source", and "msg"
// are passed to this function first, except that time and level are omitted
// if zero, and source is omitted if AddSourceLine is false.
ReplaceAttr func(a Attr) Attr
}
type commonHandler struct {
opts HandlerOptions
app appender
attrSep byte // char separating attrs from each other
preformattedAttrs []byte
mu sync.Mutex
w io.Writer
}
// Enabled reports whether l is less than or equal to the
// maximum level.
func (h *commonHandler) Enabled(l Level) bool {
return l <= h.opts.Level.Level()
}
func (h *commonHandler) with(as []Attr) *commonHandler {
h2 := &commonHandler{
app: h.app,
attrSep: h.attrSep,
opts: h.opts,
preformattedAttrs: h.preformattedAttrs,
w: h.w,
}
// Pre-format the attributes as an optimization.
state := handleState{
h2,
(*buffer.Buffer)(&h2.preformattedAttrs),
false,
}
for _, a := range as {
state.appendAttr(a)
}
return h2
}
func (h *commonHandler) handle(r Record) error {
rep := h.opts.ReplaceAttr
state := handleState{h, buffer.New(), false}
defer state.buf.Free()
h.app.appendStart(state.buf)
// time
if !r.Time().IsZero() {
key := "time"
val := r.Time().Round(0) // strip monotonic to match Attr behavior
if rep == nil {
state.appendKey(key)
state.appendTime(val)
} else {
state.appendAttr(Time(key, val))
}
}
// level
if r.Level() != 0 {
key := "level"
val := r.Level()
if rep == nil {
state.appendKey(key)
state.appendString(val.String())
} else {
state.appendAttr(Any(key, val))
}
}
// source
if h.opts.AddSource {
file, line := r.SourceLine()
if file != "" {
key := "source"
if rep == nil {
state.appendKey(key)
h.app.appendSource(state.buf, file, line)
} else {
buf := buffer.New()
buf.WriteString(file) // TODO: escape?
buf.WriteByte(':')
itoa((*[]byte)(buf), line, -1)
s := buf.String()
buf.Free()
state.appendAttr(String(key, s))
}
}
}
// message
key := "msg"
val := r.Message()
if rep == nil {
state.appendKey(key)
state.appendString(val)
} else {
state.appendAttr(String(key, val))
}
// preformatted Attrs
if len(h.preformattedAttrs) > 0 {
state.appendSep()
state.buf.Write(h.preformattedAttrs)
}
// Attrs in Record
r.Attrs(func(a Attr) {
state.appendAttr(a)
})
h.app.appendEnd(state.buf)
state.buf.WriteByte('\n')
h.mu.Lock()
defer h.mu.Unlock()
_, err := h.w.Write(*state.buf)
return err
}
// handleState holds state for a single call to commonHandler.handle.
// The initial value of sep determines whether to emit a separator
// before the next key, after which it stays true.
type handleState struct {
h *commonHandler
buf *buffer.Buffer
sep bool // Append separator before next Attr?
}
// appendAttr appends the Attr's key and value using app.
// If sep is true, it also prepends a separator.
// It handles replacement and checking for an empty key.
// It sets sep to true if it actually did the append (if the key was non-empty
// after replacement).
func (s *handleState) appendAttr(a Attr) {
if rep := s.h.opts.ReplaceAttr; rep != nil {
a = rep(a)
}
if a.Key() == "" {
return
}
s.appendKey(a.Key())
s.appendAttrValue(a)
}
func (s *handleState) appendError(err error) {
s.appendString(fmt.Sprintf("!ERROR:%v", err))
}
type appender interface {
appendStart(*buffer.Buffer) // start of output
appendEnd(*buffer.Buffer) // end of output
appendKey(*buffer.Buffer, string) // append key and key-value separator
appendString(*buffer.Buffer, string) // append a string
appendSource(*buffer.Buffer, string, int) // append a filename and line
appendTime(*buffer.Buffer, time.Time) error // append a time
appendAttrValue(*buffer.Buffer, Attr) error // append Attr's value (but not key)
}
func (s *handleState) appendSep() {
if s.sep {
s.buf.WriteByte(s.h.attrSep)
}
}
func (s *handleState) appendKey(key string) {
s.appendSep()
s.h.app.appendKey(s.buf, key)
s.sep = true
}
func (s *handleState) appendString(str string) {
s.h.app.appendString(s.buf, str)
}
func (s *handleState) appendAttrValue(a Attr) {
if err := s.h.app.appendAttrValue(s.buf, a); err != nil {
s.appendError(err)
}
}
func (s *handleState) appendTime(t time.Time) {
if err := s.h.app.appendTime(s.buf, t); err != nil {
s.appendError(err)
}
}
// This takes half the time of Time.AppendFormat.
func appendTimeRFC3339Millis(buf []byte, t time.Time) []byte {
// TODO: try to speed up by indexing the buffer.
char := func(b byte) {
buf = append(buf, b)
}
year, month, day := t.Date()
itoa(&buf, year, 4)
char('-')
itoa(&buf, int(month), 2)
char('-')
itoa(&buf, day, 2)
char('T')
hour, min, sec := t.Clock()
itoa(&buf, hour, 2)
char(':')
itoa(&buf, min, 2)
char(':')
itoa(&buf, sec, 2)
ns := t.Nanosecond()
char('.')
itoa(&buf, ns/1e6, 3)
_, offsetSeconds := t.Zone()
if offsetSeconds == 0 {
char('Z')
} else {
offsetMinutes := offsetSeconds / 60
if offsetMinutes < 0 {
char('-')
offsetMinutes = -offsetMinutes
} else {
char('+')
}
itoa(&buf, offsetMinutes/60, 2)
char(':')
itoa(&buf, offsetMinutes%60, 2)
}
return buf
}