slogsink.go

  1//go:build go1.21
  2// +build go1.21
  3
  4/*
  5Copyright 2023 The logr Authors.
  6
  7Licensed under the Apache License, Version 2.0 (the "License");
  8you may not use this file except in compliance with the License.
  9You may obtain a copy of the License at
 10
 11    http://www.apache.org/licenses/LICENSE-2.0
 12
 13Unless required by applicable law or agreed to in writing, software
 14distributed under the License is distributed on an "AS IS" BASIS,
 15WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 16See the License for the specific language governing permissions and
 17limitations under the License.
 18*/
 19
 20package logr
 21
 22import (
 23	"context"
 24	"log/slog"
 25	"runtime"
 26	"time"
 27)
 28
 29var (
 30	_ LogSink          = &slogSink{}
 31	_ CallDepthLogSink = &slogSink{}
 32	_ Underlier        = &slogSink{}
 33)
 34
 35// Underlier is implemented by the LogSink returned by NewFromLogHandler.
 36type Underlier interface {
 37	// GetUnderlying returns the Handler used by the LogSink.
 38	GetUnderlying() slog.Handler
 39}
 40
 41const (
 42	// nameKey is used to log the `WithName` values as an additional attribute.
 43	nameKey = "logger"
 44
 45	// errKey is used to log the error parameter of Error as an additional attribute.
 46	errKey = "err"
 47)
 48
 49type slogSink struct {
 50	callDepth int
 51	name      string
 52	handler   slog.Handler
 53}
 54
 55func (l *slogSink) Init(info RuntimeInfo) {
 56	l.callDepth = info.CallDepth
 57}
 58
 59func (l *slogSink) GetUnderlying() slog.Handler {
 60	return l.handler
 61}
 62
 63func (l *slogSink) WithCallDepth(depth int) LogSink {
 64	newLogger := *l
 65	newLogger.callDepth += depth
 66	return &newLogger
 67}
 68
 69func (l *slogSink) Enabled(level int) bool {
 70	return l.handler.Enabled(context.Background(), slog.Level(-level))
 71}
 72
 73func (l *slogSink) Info(level int, msg string, kvList ...interface{}) {
 74	l.log(nil, msg, slog.Level(-level), kvList...)
 75}
 76
 77func (l *slogSink) Error(err error, msg string, kvList ...interface{}) {
 78	l.log(err, msg, slog.LevelError, kvList...)
 79}
 80
 81func (l *slogSink) log(err error, msg string, level slog.Level, kvList ...interface{}) {
 82	var pcs [1]uintptr
 83	// skip runtime.Callers, this function, Info/Error, and all helper functions above that.
 84	runtime.Callers(3+l.callDepth, pcs[:])
 85
 86	record := slog.NewRecord(time.Now(), level, msg, pcs[0])
 87	if l.name != "" {
 88		record.AddAttrs(slog.String(nameKey, l.name))
 89	}
 90	if err != nil {
 91		record.AddAttrs(slog.Any(errKey, err))
 92	}
 93	record.Add(kvList...)
 94	_ = l.handler.Handle(context.Background(), record)
 95}
 96
 97func (l slogSink) WithName(name string) LogSink {
 98	if l.name != "" {
 99		l.name += "/"
100	}
101	l.name += name
102	return &l
103}
104
105func (l slogSink) WithValues(kvList ...interface{}) LogSink {
106	l.handler = l.handler.WithAttrs(kvListToAttrs(kvList...))
107	return &l
108}
109
110func kvListToAttrs(kvList ...interface{}) []slog.Attr {
111	// We don't need the record itself, only its Add method.
112	record := slog.NewRecord(time.Time{}, 0, "", 0)
113	record.Add(kvList...)
114	attrs := make([]slog.Attr, 0, record.NumAttrs())
115	record.Attrs(func(attr slog.Attr) bool {
116		attrs = append(attrs, attr)
117		return true
118	})
119	return attrs
120}