1
2
3
4
19
20 package funcr
21
22 import (
23 "bytes"
24 "fmt"
25 "log/slog"
26 "path/filepath"
27 "runtime"
28 "testing"
29
30 "github.com/go-logr/logr"
31 "github.com/go-logr/logr/internal/testhelp"
32 )
33
34 func TestSlogSink(t *testing.T) {
35 testCases := []struct {
36 name string
37 withAttrs []any
38 withGroup string
39 args []any
40 expect string
41 }{{
42 name: "just msg",
43 args: makeKV(),
44 expect: `{"logger":"","level":0,"msg":"msg"}`,
45 }, {
46 name: "primitives",
47 args: makeKV("int", 1, "str", "ABC", "bool", true),
48 expect: `{"logger":"","level":0,"msg":"msg","int":1,"str":"ABC","bool":true}`,
49 }, {
50 name: "with attrs",
51 withAttrs: makeKV("attrInt", 1, "attrStr", "ABC", "attrBool", true),
52 args: makeKV("int", 2),
53 expect: `{"logger":"","level":0,"msg":"msg","attrInt":1,"attrStr":"ABC","attrBool":true,"int":2}`,
54 }, {
55 name: "with group",
56 withGroup: "groupname",
57 args: makeKV("int", 1, "str", "ABC", "bool", true),
58 expect: `{"logger":"","level":0,"msg":"msg","groupname":{"int":1,"str":"ABC","bool":true}}`,
59 }, {
60 name: "with attrs and group",
61 withAttrs: makeKV("attrInt", 1, "attrStr", "ABC"),
62 withGroup: "groupname",
63 args: makeKV("int", 3, "bool", true),
64 expect: `{"logger":"","level":0,"msg":"msg","attrInt":1,"attrStr":"ABC","groupname":{"int":3,"bool":true}}`,
65 }}
66
67 for _, tc := range testCases {
68 t.Run(tc.name, func(t *testing.T) {
69 capt := &capture{}
70 logger := logr.New(newSink(capt.Func, NewFormatterJSON(Options{})))
71 slogger := slog.New(logr.ToSlogHandler(logger))
72 if len(tc.withAttrs) > 0 {
73 slogger = slogger.With(tc.withAttrs...)
74 }
75 if tc.withGroup != "" {
76 slogger = slogger.WithGroup(tc.withGroup)
77 }
78 slogger.Info("msg", tc.args...)
79 if capt.log != tc.expect {
80 t.Errorf("\nexpected %q\n got %q", tc.expect, capt.log)
81 }
82 })
83 }
84 }
85
86 func TestSlogSinkGroups(t *testing.T) {
87 testCases := []struct {
88 name string
89 fn func(slogger *slog.Logger)
90 expect string
91 }{{
92 name: "no group",
93 fn: func(slogger *slog.Logger) {
94 slogger.
95 Info("msg", "k", "v")
96 },
97 expect: `{"logger":"","level":0,"msg":"msg","k":"v"}`,
98 }, {
99 name: "1 group with leaf args",
100 fn: func(slogger *slog.Logger) {
101 slogger.
102 WithGroup("g1").
103 Info("msg", "k", "v")
104 },
105 expect: `{"logger":"","level":0,"msg":"msg","g1":{"k":"v"}}`,
106 }, {
107 name: "1 group without leaf args",
108 fn: func(slogger *slog.Logger) {
109 slogger.
110 WithGroup("g1").
111 Info("msg")
112 },
113 expect: `{"logger":"","level":0,"msg":"msg"}`,
114 }, {
115 name: "1 group with value without leaf args",
116 fn: func(slogger *slog.Logger) {
117 slogger.
118 WithGroup("g1").With("k1", 1).
119 Info("msg")
120 },
121 expect: `{"logger":"","level":0,"msg":"msg","g1":{"k1":1}}`,
122 }, {
123 name: "2 groups with values no leaf args",
124 fn: func(slogger *slog.Logger) {
125 slogger.
126 WithGroup("g1").With("k1", 1).
127 WithGroup("g2").With("k2", 2).
128 Info("msg")
129 },
130 expect: `{"logger":"","level":0,"msg":"msg","g1":{"k1":1,"g2":{"k2":2}}}`,
131 }, {
132 name: "3 empty groups with no values or leaf args",
133 fn: func(slogger *slog.Logger) {
134 slogger.
135 WithGroup("g1").
136 WithGroup("g2").
137 WithGroup("g3").
138 Info("msg")
139 },
140 expect: `{"logger":"","level":0,"msg":"msg"}`,
141 }, {
142 name: "3 empty groups with no values but with leaf args",
143 fn: func(slogger *slog.Logger) {
144 slogger.
145 WithGroup("g1").
146 WithGroup("g2").
147 WithGroup("g3").
148 Info("msg", "k", "v")
149 },
150 expect: `{"logger":"","level":0,"msg":"msg","g1":{"g2":{"g3":{"k":"v"}}}}`,
151 }, {
152 name: "multiple groups with and without values",
153 fn: func(slogger *slog.Logger) {
154 slogger.
155 With("k0", 0).
156 WithGroup("g1").
157 WithGroup("g2").
158 WithGroup("g3").With("k3", 3).
159 WithGroup("g4").
160 WithGroup("g5").
161 WithGroup("g6").With("k6", 6).
162 WithGroup("g7").
163 WithGroup("g8").
164 WithGroup("g9").
165 Info("msg")
166 },
167 expect: `{"logger":"","level":0,"msg":"msg","k0":0,"g1":{"g2":{"g3":{"k3":3,"g4":{"g5":{"g6":{"k6":6}}}}}}}`,
168 }}
169
170 for _, tc := range testCases {
171 t.Run(tc.name, func(t *testing.T) {
172 capt := &capture{}
173 logger := logr.New(newSink(capt.Func, NewFormatterJSON(Options{})))
174 slogger := slog.New(logr.ToSlogHandler(logger))
175 tc.fn(slogger)
176 if capt.log != tc.expect {
177 t.Errorf("\nexpected: `%s`\n got: `%s`", tc.expect, capt.log)
178 }
179 })
180 }
181 }
182
183 func TestSlogSinkWithCaller(t *testing.T) {
184 capt := &capture{}
185 logger := logr.New(newSink(capt.Func, NewFormatterJSON(Options{LogCaller: All})))
186 slogger := slog.New(logr.ToSlogHandler(logger))
187 slogger.Error("msg", "int", 1)
188 _, file, line, _ := runtime.Caller(0)
189 expect := fmt.Sprintf(`{"logger":"","caller":{"file":%q,"line":%d},"msg":"msg","error":null,"int":1}`, filepath.Base(file), line-1)
190 if capt.log != expect {
191 t.Errorf("\nexpected %q\n got %q", expect, capt.log)
192 }
193 }
194
195 func TestRunSlogTests(t *testing.T) {
196 fn := func(buffer *bytes.Buffer) slog.Handler {
197 printfn := func(obj string) {
198 fmt.Fprintln(buffer, obj)
199 }
200 opts := Options{
201 LogTimestamp: true,
202 Verbosity: 10,
203 RenderBuiltinsHook: func(kvList []any) []any {
204 mappedKVList := make([]any, len(kvList))
205 for i := 0; i < len(kvList); i += 2 {
206 key := kvList[i]
207 switch key {
208 case "ts":
209 mappedKVList[i] = "time"
210 default:
211 mappedKVList[i] = key
212 }
213 mappedKVList[i+1] = kvList[i+1]
214 }
215 return mappedKVList
216 },
217 }
218 logger := NewJSON(printfn, opts)
219 return logr.ToSlogHandler(logger)
220 }
221 exceptions := []string{
222 "a Handler should ignore a zero Record.Time",
223 }
224 testhelp.RunSlogTests(t, fn, exceptions...)
225 }
226
227 func TestLogrSlogConversion(t *testing.T) {
228 f := New(func(_, _ string) {}, Options{})
229 f2 := logr.FromSlogHandler(logr.ToSlogHandler(f))
230 if want, got := f, f2; got != want {
231 t.Helper()
232 t.Errorf("Expected %T %+v, got instead: %T %+v", want, want, got, got)
233 }
234 }
235
View as plain text