// 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 benchmarks // Handlers for benchmarking. import ( "context" "fmt" "io" "log/slog" "log/slog/internal/buffer" "strconv" "time" ) // A fastTextHandler writes a Record to an io.Writer in a format similar to // slog.TextHandler, but without quoting or locking. It has a few other // performance-motivated shortcuts, like writing times as seconds since the // epoch instead of strings. // // It is intended to represent a high-performance Handler that synchronously // writes text (as opposed to binary). type fastTextHandler struct { w io.Writer } func newFastTextHandler(w io.Writer) slog.Handler { return &fastTextHandler{w: w} } func (h *fastTextHandler) Enabled(context.Context, slog.Level) bool { return true } func (h *fastTextHandler) Handle(_ context.Context, r slog.Record) error { buf := buffer.New() defer buf.Free() if !r.Time.IsZero() { buf.WriteString("time=") h.appendTime(buf, r.Time) buf.WriteByte(' ') } buf.WriteString("level=") *buf = strconv.AppendInt(*buf, int64(r.Level), 10) buf.WriteByte(' ') buf.WriteString("msg=") buf.WriteString(r.Message) r.Attrs(func(a slog.Attr) bool { buf.WriteByte(' ') buf.WriteString(a.Key) buf.WriteByte('=') h.appendValue(buf, a.Value) return true }) buf.WriteByte('\n') _, err := h.w.Write(*buf) return err } func (h *fastTextHandler) appendValue(buf *buffer.Buffer, v slog.Value) { switch v.Kind() { case slog.KindString: buf.WriteString(v.String()) case slog.KindInt64: *buf = strconv.AppendInt(*buf, v.Int64(), 10) case slog.KindUint64: *buf = strconv.AppendUint(*buf, v.Uint64(), 10) case slog.KindFloat64: *buf = strconv.AppendFloat(*buf, v.Float64(), 'g', -1, 64) case slog.KindBool: *buf = strconv.AppendBool(*buf, v.Bool()) case slog.KindDuration: *buf = strconv.AppendInt(*buf, v.Duration().Nanoseconds(), 10) case slog.KindTime: h.appendTime(buf, v.Time()) case slog.KindAny: a := v.Any() switch a := a.(type) { case error: buf.WriteString(a.Error()) default: fmt.Fprint(buf, a) } default: panic(fmt.Sprintf("bad kind: %s", v.Kind())) } } func (h *fastTextHandler) appendTime(buf *buffer.Buffer, t time.Time) { *buf = strconv.AppendInt(*buf, t.Unix(), 10) } func (h *fastTextHandler) WithAttrs([]slog.Attr) slog.Handler { panic("fastTextHandler: With unimplemented") } func (*fastTextHandler) WithGroup(string) slog.Handler { panic("fastTextHandler: WithGroup unimplemented") } // An asyncHandler simulates a Handler that passes Records to a // background goroutine for processing. // Because sending to a channel can be expensive due to locking, // we simulate a lock-free queue by adding the Record to a ring buffer. // Omitting the locking makes this little more than a copy of the Record, // but that is a worthwhile thing to measure because Records are on the large // side. Since nothing actually reads from the ring buffer, it can handle an // arbitrary number of Records without either blocking or allocation. type asyncHandler struct { ringBuffer [100]slog.Record next int } func newAsyncHandler() *asyncHandler { return &asyncHandler{} } func (*asyncHandler) Enabled(context.Context, slog.Level) bool { return true } func (h *asyncHandler) Handle(_ context.Context, r slog.Record) error { h.ringBuffer[h.next] = r.Clone() h.next = (h.next + 1) % len(h.ringBuffer) return nil } func (*asyncHandler) WithAttrs([]slog.Attr) slog.Handler { panic("asyncHandler: With unimplemented") } func (*asyncHandler) WithGroup(string) slog.Handler { panic("asyncHandler: WithGroup unimplemented") } // A disabledHandler's Enabled method always returns false. type disabledHandler struct{} func (disabledHandler) Enabled(context.Context, slog.Level) bool { return false } func (disabledHandler) Handle(context.Context, slog.Record) error { panic("should not be called") } func (disabledHandler) WithAttrs([]slog.Attr) slog.Handler { panic("disabledHandler: With unimplemented") } func (disabledHandler) WithGroup(string) slog.Handler { panic("disabledHandler: WithGroup unimplemented") }