slogsink.go raw
1 //go:build go1.21
2 // +build go1.21
3
4 /*
5 Copyright 2023 The logr Authors.
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 Unless required by applicable law or agreed to in writing, software
14 distributed under the License is distributed on an "AS IS" BASIS,
15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 See the License for the specific language governing permissions and
17 limitations under the License.
18 */
19
20 package logr
21
22 import (
23 "context"
24 "log/slog"
25 "runtime"
26 "time"
27 )
28
29 var (
30 _ LogSink = &slogSink{}
31 _ CallDepthLogSink = &slogSink{}
32 _ Underlier = &slogSink{}
33 )
34
35 // Underlier is implemented by the LogSink returned by NewFromLogHandler.
36 type Underlier interface {
37 // GetUnderlying returns the Handler used by the LogSink.
38 GetUnderlying() slog.Handler
39 }
40
41 const (
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
49 type slogSink struct {
50 callDepth int
51 name string
52 handler slog.Handler
53 }
54
55 func (l *slogSink) Init(info RuntimeInfo) {
56 l.callDepth = info.CallDepth
57 }
58
59 func (l *slogSink) GetUnderlying() slog.Handler {
60 return l.handler
61 }
62
63 func (l *slogSink) WithCallDepth(depth int) LogSink {
64 newLogger := *l
65 newLogger.callDepth += depth
66 return &newLogger
67 }
68
69 func (l *slogSink) Enabled(level int) bool {
70 return l.handler.Enabled(context.Background(), slog.Level(-level))
71 }
72
73 func (l *slogSink) Info(level int, msg string, kvList ...interface{}) {
74 l.log(nil, msg, slog.Level(-level), kvList...)
75 }
76
77 func (l *slogSink) Error(err error, msg string, kvList ...interface{}) {
78 l.log(err, msg, slog.LevelError, kvList...)
79 }
80
81 func (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
97 func (l slogSink) WithName(name string) LogSink {
98 if l.name != "" {
99 l.name += "/"
100 }
101 l.name += name
102 return &l
103 }
104
105 func (l slogSink) WithValues(kvList ...interface{}) LogSink {
106 l.handler = l.handler.WithAttrs(kvListToAttrs(kvList...))
107 return &l
108 }
109
110 func 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 }
121