slog.Int("status", testInt),
slog.Duration("duration", testDuration),
slog.Time("time", testTime),
- slog.Any("error", testError),
+ slog.Any("event", testEvent),
slog.String("string", testString),
slog.Int("status", testInt),
slog.Duration("duration", testDuration),
slog.Time("time", testTime),
- slog.Any("error", testError),
+ slog.Any("event", testEvent),
)
},
},
slog.Int("status", testInt),
slog.Duration("duration", testDuration),
slog.Time("time", testTime),
- slog.Any("error", testError),
+ slog.Any("event", testEvent),
slog.String("string", testString),
slog.Int("status", testInt),
slog.Duration("duration", testDuration),
slog.Time("time", testTime),
- slog.Any("error", testError),
+ slog.Any("event", testEvent),
slog.String("string", testString),
slog.Int("status", testInt),
slog.Duration("duration", testDuration),
slog.Time("time", testTime),
- slog.Any("error", testError),
+ slog.Any("event", testEvent),
slog.String("string", testString),
slog.Int("status", testInt),
slog.Duration("duration", testDuration),
slog.Time("time", testTime),
- slog.Any("error", testError),
+ slog.Any("event", testEvent),
slog.String("string", testString),
slog.Int("status", testInt),
slog.Duration("duration", testDuration),
slog.Time("time", testTime),
- slog.Any("error", testError),
+ slog.Any("event", testEvent),
slog.String("string", testString),
slog.Int("status", testInt),
slog.Duration("duration", testDuration),
slog.Time("time", testTime),
- slog.Any("error", testError),
+ slog.Any("event", testEvent),
slog.String("string", testString),
slog.Int("status", testInt),
slog.Duration("duration", testDuration),
slog.Time("time", testTime),
- slog.Any("error", testError),
+ slog.Any("event", testEvent),
)
},
},
return nil
}
-func appendJSONMarshal(buf *buffer.Buffer, v any) error {
+type jsonEncoder struct {
+ buf *bytes.Buffer
// Use a json.Encoder to avoid escaping HTML.
- var bb bytes.Buffer
- enc := json.NewEncoder(&bb)
- enc.SetEscapeHTML(false)
- if err := enc.Encode(v); err != nil {
+ json *json.Encoder
+}
+
+var jsonEncoderPool = &sync.Pool{
+ New: func() any {
+ enc := &jsonEncoder{
+ buf: new(bytes.Buffer),
+ }
+ enc.json = json.NewEncoder(enc.buf)
+ enc.json.SetEscapeHTML(false)
+ return enc
+ },
+}
+
+func appendJSONMarshal(buf *buffer.Buffer, v any) error {
+ j := jsonEncoderPool.Get().(*jsonEncoder)
+ defer func() {
+ // To reduce peak allocation, return only smaller buffers to the pool.
+ const maxBufferSize = 16 << 10
+ if j.buf.Cap() > maxBufferSize {
+ return
+ }
+ j.buf.Reset()
+ jsonEncoderPool.Put(j)
+ }()
+
+ if err := j.json.Encode(v); err != nil {
return err
}
- bs := bb.Bytes()
+
+ bs := j.buf.Bytes()
buf.Write(bs[:len(bs)-1]) // remove final newline
return nil
}
return string(buf)
}
+func TestJSONAllocs(t *testing.T) {
+ ctx := t.Context()
+ l := New(NewJSONHandler(io.Discard, &HandlerOptions{}))
+ testErr := errors.New("an error occurred")
+ testEvent := struct {
+ ID int
+ Scope string
+ Enabled bool
+ }{
+ 123456, "abcdefgh", true,
+ }
+
+ t.Run("message", func(t *testing.T) {
+ wantAllocs(t, 0, func() {
+ l.LogAttrs(ctx, LevelInfo,
+ "hello world",
+ )
+ })
+ })
+ t.Run("attrs", func(t *testing.T) {
+ wantAllocs(t, 1, func() {
+ l.LogAttrs(ctx, LevelInfo,
+ "hello world",
+ String("component", "subtest"),
+ Int("id", 67890),
+ Bool("flag", true),
+ Any("error", testErr),
+ Any("event", testEvent),
+ )
+ })
+ })
+}
+
func BenchmarkJSONHandler(b *testing.B) {
for _, bench := range []struct {
name string