golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/slog/benchmarks/handlers.go (about)

     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 benchmarks
     6  
     7  // Handlers for benchmarking.
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"io"
    13  	"strconv"
    14  	"time"
    15  
    16  	"golang.org/x/exp/slog"
    17  	"golang.org/x/exp/slog/internal/buffer"
    18  )
    19  
    20  // A fastTextHandler writes a Record to an io.Writer in a format similar to
    21  // slog.TextHandler, but without quoting or locking. It has a few other
    22  // performance-motivated shortcuts, like writing times as seconds since the
    23  // epoch instead of strings.
    24  //
    25  // It is intended to represent a high-performance Handler that synchronously
    26  // writes text (as opposed to binary).
    27  type fastTextHandler struct {
    28  	w io.Writer
    29  }
    30  
    31  func newFastTextHandler(w io.Writer) slog.Handler {
    32  	return &fastTextHandler{w: w}
    33  }
    34  
    35  func (h *fastTextHandler) Enabled(context.Context, slog.Level) bool { return true }
    36  
    37  func (h *fastTextHandler) Handle(_ context.Context, r slog.Record) error {
    38  	buf := buffer.New()
    39  	defer buf.Free()
    40  
    41  	if !r.Time.IsZero() {
    42  		buf.WriteString("time=")
    43  		h.appendTime(buf, r.Time)
    44  		buf.WriteByte(' ')
    45  	}
    46  	buf.WriteString("level=")
    47  	*buf = strconv.AppendInt(*buf, int64(r.Level), 10)
    48  	buf.WriteByte(' ')
    49  	buf.WriteString("msg=")
    50  	buf.WriteString(r.Message)
    51  	r.Attrs(func(a slog.Attr) bool {
    52  		buf.WriteByte(' ')
    53  		buf.WriteString(a.Key)
    54  		buf.WriteByte('=')
    55  		h.appendValue(buf, a.Value)
    56  		return true
    57  	})
    58  	buf.WriteByte('\n')
    59  	_, err := h.w.Write(*buf)
    60  	return err
    61  }
    62  
    63  func (h *fastTextHandler) appendValue(buf *buffer.Buffer, v slog.Value) {
    64  	switch v.Kind() {
    65  	case slog.KindString:
    66  		buf.WriteString(v.String())
    67  	case slog.KindInt64:
    68  		*buf = strconv.AppendInt(*buf, v.Int64(), 10)
    69  	case slog.KindUint64:
    70  		*buf = strconv.AppendUint(*buf, v.Uint64(), 10)
    71  	case slog.KindFloat64:
    72  		*buf = strconv.AppendFloat(*buf, v.Float64(), 'g', -1, 64)
    73  	case slog.KindBool:
    74  		*buf = strconv.AppendBool(*buf, v.Bool())
    75  	case slog.KindDuration:
    76  		*buf = strconv.AppendInt(*buf, v.Duration().Nanoseconds(), 10)
    77  	case slog.KindTime:
    78  		h.appendTime(buf, v.Time())
    79  	case slog.KindAny:
    80  		a := v.Any()
    81  		switch a := a.(type) {
    82  		case error:
    83  			buf.WriteString(a.Error())
    84  		default:
    85  			buf.WriteString(fmt.Sprint(a))
    86  		}
    87  	default:
    88  		panic(fmt.Sprintf("bad kind: %s", v.Kind()))
    89  	}
    90  }
    91  
    92  func (h *fastTextHandler) appendTime(buf *buffer.Buffer, t time.Time) {
    93  	*buf = strconv.AppendInt(*buf, t.Unix(), 10)
    94  }
    95  
    96  func (h *fastTextHandler) WithAttrs([]slog.Attr) slog.Handler {
    97  	panic("fastTextHandler: With unimplemented")
    98  }
    99  
   100  func (*fastTextHandler) WithGroup(string) slog.Handler {
   101  	panic("fastTextHandler: WithGroup unimplemented")
   102  }
   103  
   104  // An asyncHandler simulates a Handler that passes Records to a
   105  // background goroutine for processing.
   106  // Because sending to a channel can be expensive due to locking,
   107  // we simulate a lock-free queue by adding the Record to a ring buffer.
   108  // Omitting the locking makes this little more than a copy of the Record,
   109  // but that is a worthwhile thing to measure because Records are on the large
   110  // side.
   111  type asyncHandler struct {
   112  	ringBuffer [100]slog.Record
   113  	next       int
   114  }
   115  
   116  func newAsyncHandler() *asyncHandler {
   117  	return &asyncHandler{}
   118  }
   119  
   120  func (*asyncHandler) Enabled(context.Context, slog.Level) bool { return true }
   121  
   122  func (h *asyncHandler) Handle(_ context.Context, r slog.Record) error {
   123  	h.ringBuffer[h.next] = r.Clone()
   124  	h.next = (h.next + 1) % len(h.ringBuffer)
   125  	return nil
   126  }
   127  
   128  func (*asyncHandler) WithAttrs([]slog.Attr) slog.Handler {
   129  	panic("asyncHandler: With unimplemented")
   130  }
   131  
   132  func (*asyncHandler) WithGroup(string) slog.Handler {
   133  	panic("asyncHandler: WithGroup unimplemented")
   134  }
   135  
   136  type disabledHandler struct{}
   137  
   138  func (disabledHandler) Enabled(context.Context, slog.Level) bool  { return false }
   139  func (disabledHandler) Handle(context.Context, slog.Record) error { panic("should not be called") }
   140  
   141  func (disabledHandler) WithAttrs([]slog.Attr) slog.Handler {
   142  	panic("disabledHandler: With unimplemented")
   143  }
   144  
   145  func (disabledHandler) WithGroup(string) slog.Handler {
   146  	panic("disabledHandler: WithGroup unimplemented")
   147  }