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}