Source file src/log/slog/internal/benchmarks/handlers.go

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

View as plain text