| // 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" |
| "strconv" |
| "time" |
| |
| "golang.org/x/exp/slog" |
| "golang.org/x/exp/slog/internal/buffer" |
| ) |
| |
| // 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: |
| buf.WriteString(fmt.Sprint(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. |
| 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") |
| } |
| |
| 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") |
| } |