funcr: Add strict JSON mode
New funcs `funcr.NewJSON()` and `funcr.NewFormatterJSON()` will
configure the output for strict JSON. This is not just an `Options`
field because the function (from which funcr takes its name) has a
different signature in JSON mode (just 1 argument).
Benchmarks:
Before:
```
BenchmarkFuncrInfoOneArg-6 2015502 592.1 ns/op
BenchmarkFuncrInfoSeveralArgs-6 970407 1233 ns/op
BenchmarkFuncrV0Info-6 970164 1232 ns/op
BenchmarkFuncrV9Info-6 15342703 78.60 ns/op
BenchmarkFuncrError-6 816357 1456 ns/op
```
After:
```
BenchmarkFuncrInfoOneArg-6 1998198 598.6 ns/op
BenchmarkFuncrJSONInfoOneArg-6 1727032 694.8 ns/op
BenchmarkFuncrInfoSeveralArgs-6 971455 1230 ns/op
BenchmarkFuncrJSONInfoSeveralArgs-6 889963 1344 ns/op
BenchmarkFuncrV0Info-6 975469 1226 ns/op
BenchmarkFuncrJSONV0Info-6 890828 1342 ns/op
BenchmarkFuncrV9Info-6 15004518 79.69 ns/op
BenchmarkFuncrJSONV9Info-6 15045549 79.86 ns/op
BenchmarkFuncrError-6 816446 1462 ns/op
BenchmarkFuncrJSONError-6 754684 1583 ns/op
```
The majority of the extra time in JSON mode seems to be from adding the
"logger" key-value pair, which I don't see how to improve on.
Tim Hockin
2 years ago
22 | 22 | "github.com/go-logr/logr" |
23 | 23 | "github.com/go-logr/logr/funcr" |
24 | 24 | ) |
25 | ||
26 | func noop(prefix, args string) { | |
27 | } | |
28 | 25 | |
29 | 26 | //go:noinline |
30 | 27 | func doInfoOneArg(b *testing.B, log logr.Logger) { |
94 | 91 | } |
95 | 92 | } |
96 | 93 | |
97 | func BenchmarkDiscardInfoOneArg(b *testing.B) { | |
94 | func BenchmarkDiscardLogInfoOneArg(b *testing.B) { | |
98 | 95 | var log logr.Logger = logr.Discard() |
99 | 96 | doInfoOneArg(b, log) |
100 | 97 | } |
101 | 98 | |
102 | func BenchmarkDiscardInfoSeveralArgs(b *testing.B) { | |
99 | func BenchmarkDiscardLogInfoSeveralArgs(b *testing.B) { | |
103 | 100 | var log logr.Logger = logr.Discard() |
104 | 101 | doInfoSeveralArgs(b, log) |
105 | 102 | } |
106 | 103 | |
107 | func BenchmarkDiscardV0Info(b *testing.B) { | |
104 | func BenchmarkDiscardLogV0Info(b *testing.B) { | |
108 | 105 | var log logr.Logger = logr.Discard() |
109 | 106 | doV0Info(b, log) |
110 | 107 | } |
111 | 108 | |
112 | func BenchmarkDiscardV9Info(b *testing.B) { | |
109 | func BenchmarkDiscardLogV9Info(b *testing.B) { | |
113 | 110 | var log logr.Logger = logr.Discard() |
114 | 111 | doV9Info(b, log) |
115 | 112 | } |
116 | 113 | |
117 | func BenchmarkDiscardError(b *testing.B) { | |
114 | func BenchmarkDiscardLogError(b *testing.B) { | |
118 | 115 | var log logr.Logger = logr.Discard() |
119 | 116 | doError(b, log) |
120 | 117 | } |
129 | 126 | doWithName(b, log) |
130 | 127 | } |
131 | 128 | |
132 | func BenchmarkFuncrInfoOneArg(b *testing.B) { | |
133 | var log logr.Logger = funcr.New(noop, funcr.Options{}) | |
129 | func noopKV(prefix, args string) {} | |
130 | func noopJSON(obj string) {} | |
131 | ||
132 | func BenchmarkFuncrLogInfoOneArg(b *testing.B) { | |
133 | var log logr.Logger = funcr.New(noopKV, funcr.Options{}) | |
134 | 134 | doInfoOneArg(b, log) |
135 | 135 | } |
136 | 136 | |
137 | func BenchmarkFuncrInfoSeveralArgs(b *testing.B) { | |
138 | var log logr.Logger = funcr.New(noop, funcr.Options{}) | |
137 | func BenchmarkFuncrJSONLogInfoOneArg(b *testing.B) { | |
138 | var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) | |
139 | doInfoOneArg(b, log) | |
140 | } | |
141 | ||
142 | func BenchmarkFuncrLogInfoSeveralArgs(b *testing.B) { | |
143 | var log logr.Logger = funcr.New(noopKV, funcr.Options{}) | |
139 | 144 | doInfoSeveralArgs(b, log) |
140 | 145 | } |
141 | 146 | |
142 | func BenchmarkFuncrV0Info(b *testing.B) { | |
143 | var log logr.Logger = funcr.New(noop, funcr.Options{}) | |
147 | func BenchmarkFuncrJSONLogInfoSeveralArgs(b *testing.B) { | |
148 | var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) | |
149 | doInfoSeveralArgs(b, log) | |
150 | } | |
151 | ||
152 | func BenchmarkFuncrLogV0Info(b *testing.B) { | |
153 | var log logr.Logger = funcr.New(noopKV, funcr.Options{}) | |
144 | 154 | doV0Info(b, log) |
145 | 155 | } |
146 | 156 | |
147 | func BenchmarkFuncrV9Info(b *testing.B) { | |
148 | var log logr.Logger = funcr.New(noop, funcr.Options{}) | |
157 | func BenchmarkFuncrJSONLogV0Info(b *testing.B) { | |
158 | var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) | |
159 | doV0Info(b, log) | |
160 | } | |
161 | ||
162 | func BenchmarkFuncrLogV9Info(b *testing.B) { | |
163 | var log logr.Logger = funcr.New(noopKV, funcr.Options{}) | |
149 | 164 | doV9Info(b, log) |
150 | 165 | } |
151 | 166 | |
152 | func BenchmarkFuncrError(b *testing.B) { | |
153 | var log logr.Logger = funcr.New(noop, funcr.Options{}) | |
167 | func BenchmarkFuncrJSONLogV9Info(b *testing.B) { | |
168 | var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) | |
169 | doV9Info(b, log) | |
170 | } | |
171 | ||
172 | func BenchmarkFuncrLogError(b *testing.B) { | |
173 | var log logr.Logger = funcr.New(noopKV, funcr.Options{}) | |
174 | doError(b, log) | |
175 | } | |
176 | ||
177 | func BenchmarkFuncrJSONLogError(b *testing.B) { | |
178 | var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) | |
154 | 179 | doError(b, log) |
155 | 180 | } |
156 | 181 | |
157 | 182 | func BenchmarkFuncrWithValues(b *testing.B) { |
158 | var log logr.Logger = funcr.New(noop, funcr.Options{}) | |
183 | var log logr.Logger = funcr.New(noopKV, funcr.Options{}) | |
159 | 184 | doWithValues(b, log) |
160 | 185 | } |
161 | 186 | |
162 | 187 | func BenchmarkFuncrWithName(b *testing.B) { |
163 | var log logr.Logger = funcr.New(noop, funcr.Options{}) | |
188 | var log logr.Logger = funcr.New(noopKV, funcr.Options{}) | |
164 | 189 | doWithName(b, log) |
165 | 190 | } |
166 | 191 | |
167 | 192 | func BenchmarkFuncrWithCallDepth(b *testing.B) { |
168 | var log logr.Logger = funcr.New(noop, funcr.Options{}) | |
193 | var log logr.Logger = funcr.New(noopKV, funcr.Options{}) | |
169 | 194 | doWithCallDepth(b, log) |
170 | 195 | } |
22 | 22 | "github.com/go-logr/logr" |
23 | 23 | "github.com/go-logr/logr/funcr" |
24 | 24 | ) |
25 | ||
26 | func ExampleNew() { | |
27 | var log logr.Logger = funcr.New(func(prefix, args string) { | |
28 | fmt.Println(prefix, args) | |
29 | }, funcr.Options{}) | |
30 | ||
31 | log = log.WithName("MyLogger") | |
32 | log = log.WithValues("savedKey", "savedValue") | |
33 | log.Info("the message", "key", "value") | |
34 | // Output: MyLogger "level"=0 "msg"="the message" "savedKey"="savedValue" "key"="value" | |
35 | } | |
36 | ||
37 | func ExampleNewJSON() { | |
38 | var log logr.Logger = funcr.NewJSON(func(obj string) { | |
39 | fmt.Println(obj) | |
40 | }, funcr.Options{}) | |
41 | ||
42 | log = log.WithName("MyLogger") | |
43 | log = log.WithValues("savedKey", "savedValue") | |
44 | log.Info("the message", "key", "value") | |
45 | // Output: {"logger":"MyLogger","level":0,"msg":"the message","savedKey":"savedValue","key":"value"} | |
46 | } | |
25 | 47 | |
26 | 48 | func ExampleUnderlier() { |
27 | 49 | var log logr.Logger = funcr.New(func(prefix, args string) { |
41 | 41 | |
42 | 42 | // New returns a logr.Logger which is implemented by an arbitrary function. |
43 | 43 | func New(fn func(prefix, args string), opts Options) logr.Logger { |
44 | return logr.New(newSink(fn, opts)) | |
44 | return logr.New(newSink(fn, NewFormatter(opts))) | |
45 | } | |
46 | ||
47 | // NewJSON returns a logr.Logger which is implemented by an arbitrary function | |
48 | // and produces JSON output. | |
49 | func NewJSON(fn func(obj string), opts Options) logr.Logger { | |
50 | fnWrapper := func(_, obj string) { | |
51 | fn(obj) | |
52 | } | |
53 | return logr.New(newSink(fnWrapper, NewFormatterJSON(opts))) | |
45 | 54 | } |
46 | 55 | |
47 | 56 | // Underlier exposes access to the underlying logging function. Since |
52 | 61 | GetUnderlying() func(prefix, args string) |
53 | 62 | } |
54 | 63 | |
55 | func newSink(fn func(prefix, args string), opts Options) logr.LogSink { | |
64 | func newSink(fn func(prefix, args string), formatter Formatter) logr.LogSink { | |
56 | 65 | l := &fnlogger{ |
57 | Formatter: NewFormatter(opts), | |
66 | Formatter: formatter, | |
58 | 67 | write: fn, |
59 | 68 | } |
60 | 69 | // For skipping fnlogger.Info and fnlogger.Error. |
134 | 143 | var _ logr.CallDepthLogSink = &fnlogger{} |
135 | 144 | var _ Underlier = &fnlogger{} |
136 | 145 | |
137 | // NewFormatter constructs a Formatter. | |
146 | // NewFormatter constructs a Formatter which emits a JSON-like key=value format. | |
138 | 147 | func NewFormatter(opts Options) Formatter { |
148 | return newFormatter(opts, outputKeyValue) | |
149 | } | |
150 | ||
151 | // NewFormatterJSON constructs a Formatter which emits strict JSON. | |
152 | func NewFormatterJSON(opts Options) Formatter { | |
153 | return newFormatter(opts, outputJSON) | |
154 | } | |
155 | ||
156 | func newFormatter(opts Options, outfmt outputFormat) Formatter { | |
139 | 157 | f := Formatter{ |
158 | outputFormat: outfmt, | |
140 | 159 | prefix: "", |
141 | 160 | values: nil, |
142 | 161 | depth: 0, |
151 | 170 | // implementation. It should be constructed with NewFormatter. Some of |
152 | 171 | // its methods directly implement logr.LogSink. |
153 | 172 | type Formatter struct { |
173 | outputFormat outputFormat | |
154 | 174 | prefix string |
155 | 175 | values []interface{} |
156 | 176 | depth int |
159 | 179 | verbosity int |
160 | 180 | } |
161 | 181 | |
182 | // outputFormat indicates which outputFormat to use. | |
183 | type outputFormat int | |
184 | ||
185 | const ( | |
186 | // outputKeyValue emits a JSON-like key=value format, but not strict JSON. | |
187 | outputKeyValue outputFormat = iota | |
188 | // outputJSON emits strict JSON. | |
189 | outputJSON | |
190 | ) | |
191 | ||
162 | 192 | func (f Formatter) flatten(kvList ...interface{}) string { |
163 | 193 | if len(kvList)%2 != 0 { |
164 | 194 | kvList = append(kvList, "<no-value>") |
165 | 195 | } |
166 | 196 | // Empirically bytes.Buffer is faster than strings.Builder for this. |
167 | 197 | buf := bytes.NewBuffer(make([]byte, 0, 1024)) |
198 | if f.outputFormat == outputJSON { | |
199 | buf.WriteRune('{') | |
200 | } | |
168 | 201 | for i := 0; i < len(kvList); i += 2 { |
169 | 202 | k, ok := kvList[i].(string) |
170 | 203 | if !ok { |
173 | 206 | v := kvList[i+1] |
174 | 207 | |
175 | 208 | if i > 0 { |
176 | buf.WriteRune(' ') | |
209 | if f.outputFormat == outputJSON { | |
210 | buf.WriteRune(',') | |
211 | } else { | |
212 | // In theory the format could be something we don't understand. In | |
213 | // practice, we control it, so it won't | |
214 | buf.WriteRune(' ') | |
215 | } | |
177 | 216 | } |
178 | 217 | buf.WriteRune('"') |
179 | 218 | buf.WriteString(k) |
180 | 219 | buf.WriteRune('"') |
181 | buf.WriteRune('=') | |
220 | if f.outputFormat == outputJSON { | |
221 | buf.WriteRune(':') | |
222 | } else { | |
223 | buf.WriteRune('=') | |
224 | } | |
182 | 225 | buf.WriteString(f.pretty(v)) |
226 | } | |
227 | if f.outputFormat == outputJSON { | |
228 | buf.WriteRune('}') | |
183 | 229 | } |
184 | 230 | return buf.String() |
185 | 231 | } |
377 | 423 | } |
378 | 424 | |
379 | 425 | // FormatInfo flattens an Info log message into strings. |
380 | // The prefix will be empty when no names were set. | |
426 | // The prefix will be empty when no names were set, or when the output is | |
427 | // configured for JSON. | |
381 | 428 | func (f Formatter) FormatInfo(level int, msg string, kvList []interface{}) (prefix, argsStr string) { |
382 | 429 | args := make([]interface{}, 0, 64) // using a constant here impacts perf |
430 | prefix = f.prefix | |
431 | if f.outputFormat == outputJSON { | |
432 | args = append(args, "logger", prefix) | |
433 | prefix = "" | |
434 | } | |
383 | 435 | if f.logTimestamp { |
384 | 436 | args = append(args, "ts", time.Now().Format(timestampFmt)) |
385 | 437 | } |
389 | 441 | args = append(args, "level", level, "msg", msg) |
390 | 442 | args = append(args, f.values...) |
391 | 443 | args = append(args, kvList...) |
392 | return f.prefix, f.flatten(args...) | |
444 | return prefix, f.flatten(args...) | |
393 | 445 | } |
394 | 446 | |
395 | 447 | // FormatError flattens an Error log message into strings. |
396 | // The prefix will be empty when no names were set. | |
448 | // The prefix will be empty when no names were set, or when the output is | |
449 | // configured for JSON. | |
397 | 450 | func (f Formatter) FormatError(err error, msg string, kvList []interface{}) (prefix, argsStr string) { |
398 | 451 | args := make([]interface{}, 0, 64) // using a constant here impacts perf |
452 | prefix = f.prefix | |
453 | if f.outputFormat == outputJSON { | |
454 | args = append(args, "logger", prefix) | |
455 | prefix = "" | |
456 | } | |
399 | 457 | if f.logTimestamp { |
400 | 458 | args = append(args, "ts", time.Now().Format(timestampFmt)) |
401 | 459 | } |
192 | 192 | |
193 | 193 | func TestFlatten(t *testing.T) { |
194 | 194 | testCases := []struct { |
195 | name string | |
196 | kv []interface{} | |
197 | expect string | |
198 | }{{ | |
199 | name: "nil", | |
200 | kv: nil, | |
201 | expect: "", | |
202 | }, { | |
203 | name: "empty", | |
204 | kv: []interface{}{}, | |
205 | expect: "", | |
206 | }, { | |
207 | name: "primitives", | |
208 | kv: makeKV("int", 1, "str", "ABC", "bool", true), | |
209 | expect: `"int"=1 "str"="ABC" "bool"=true`, | |
210 | }, { | |
211 | name: "missing value", | |
212 | kv: makeKV("key"), | |
213 | expect: `"key"="<no-value>"`, | |
214 | }, { | |
215 | name: "non-string key", | |
216 | kv: makeKV(123, "val"), | |
217 | expect: `"<non-string-key-0>"="val"`, | |
218 | }} | |
219 | ||
220 | f := NewFormatter(Options{}) | |
221 | for _, tc := range testCases { | |
222 | t.Run(tc.name, func(t *testing.T) { | |
223 | r := f.flatten(tc.kv...) | |
224 | if r != tc.expect { | |
225 | t.Errorf("expected %q, got %q", tc.expect, r) | |
195 | name string | |
196 | kv []interface{} | |
197 | expectKV string | |
198 | expectJSON string | |
199 | }{{ | |
200 | name: "nil", | |
201 | kv: nil, | |
202 | expectKV: "", | |
203 | expectJSON: "{}", | |
204 | }, { | |
205 | name: "empty", | |
206 | kv: []interface{}{}, | |
207 | expectKV: "", | |
208 | expectJSON: "{}", | |
209 | }, { | |
210 | name: "primitives", | |
211 | kv: makeKV("int", 1, "str", "ABC", "bool", true), | |
212 | expectKV: `"int"=1 "str"="ABC" "bool"=true`, | |
213 | expectJSON: `{"int":1,"str":"ABC","bool":true}`, | |
214 | }, { | |
215 | name: "missing value", | |
216 | kv: makeKV("key"), | |
217 | expectKV: `"key"="<no-value>"`, | |
218 | expectJSON: `{"key":"<no-value>"}`, | |
219 | }, { | |
220 | name: "non-string key", | |
221 | kv: makeKV(123, "val"), | |
222 | expectKV: `"<non-string-key-0>"="val"`, | |
223 | expectJSON: `{"<non-string-key-0>":"val"}`, | |
224 | }} | |
225 | ||
226 | fKV := NewFormatter(Options{}) | |
227 | fJSON := NewFormatterJSON(Options{}) | |
228 | for _, tc := range testCases { | |
229 | t.Run(tc.name, func(t *testing.T) { | |
230 | r := fKV.flatten(tc.kv...) | |
231 | if r != tc.expectKV { | |
232 | t.Errorf("expected %q, got %q", tc.expectKV, r) | |
233 | } | |
234 | r = fJSON.flatten(tc.kv...) | |
235 | if r != tc.expectJSON { | |
236 | t.Errorf("expected %q, got %q", tc.expectJSON, r) | |
226 | 237 | } |
227 | 238 | }) |
228 | 239 | } |
230 | 241 | |
231 | 242 | func TestEnabled(t *testing.T) { |
232 | 243 | t.Run("default V", func(t *testing.T) { |
233 | log := newSink(func(prefix, args string) {}, Options{}) | |
244 | log := newSink(func(prefix, args string) {}, NewFormatter(Options{})) | |
234 | 245 | if !log.Enabled(0) { |
235 | 246 | t.Errorf("expected true") |
236 | 247 | } |
239 | 250 | } |
240 | 251 | }) |
241 | 252 | t.Run("V=9", func(t *testing.T) { |
242 | log := newSink(func(prefix, args string) {}, Options{Verbosity: 9}) | |
253 | log := newSink(func(prefix, args string) {}, NewFormatter(Options{Verbosity: 9})) | |
243 | 254 | if !log.Enabled(8) { |
244 | 255 | t.Errorf("expected true") |
245 | 256 | } |
278 | 289 | for _, tc := range testCases { |
279 | 290 | t.Run(tc.name, func(t *testing.T) { |
280 | 291 | cap := &capture{} |
281 | sink := newSink(cap.Func, Options{}) | |
292 | sink := newSink(cap.Func, NewFormatter(Options{})) | |
282 | 293 | sink.Info(0, "msg", tc.args...) |
283 | 294 | if cap.log != tc.expect { |
284 | 295 | t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log) |
290 | 301 | func TestInfoWithCaller(t *testing.T) { |
291 | 302 | t.Run("LogCaller=All", func(t *testing.T) { |
292 | 303 | cap := &capture{} |
293 | sink := newSink(cap.Func, Options{LogCaller: All}) | |
304 | sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All})) | |
294 | 305 | sink.Info(0, "msg") |
295 | 306 | _, file, line, _ := runtime.Caller(0) |
296 | 307 | expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line-1) |
300 | 311 | }) |
301 | 312 | t.Run("LogCaller=Info", func(t *testing.T) { |
302 | 313 | cap := &capture{} |
303 | sink := newSink(cap.Func, Options{LogCaller: Info}) | |
314 | sink := newSink(cap.Func, NewFormatter(Options{LogCaller: Info})) | |
304 | 315 | sink.Info(0, "msg") |
305 | 316 | _, file, line, _ := runtime.Caller(0) |
306 | 317 | expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line-1) |
310 | 321 | }) |
311 | 322 | t.Run("LogCaller=Error", func(t *testing.T) { |
312 | 323 | cap := &capture{} |
313 | sink := newSink(cap.Func, Options{LogCaller: Error}) | |
324 | sink := newSink(cap.Func, NewFormatter(Options{LogCaller: Error})) | |
314 | 325 | sink.Info(0, "msg") |
315 | 326 | expect := ` "level"=0 "msg"="msg"` |
316 | 327 | if cap.log != expect { |
319 | 330 | }) |
320 | 331 | t.Run("LogCaller=None", func(t *testing.T) { |
321 | 332 | cap := &capture{} |
322 | sink := newSink(cap.Func, Options{LogCaller: None}) | |
333 | sink := newSink(cap.Func, NewFormatter(Options{LogCaller: None})) | |
323 | 334 | sink.Info(0, "msg") |
324 | 335 | expect := ` "level"=0 "msg"="msg"` |
325 | 336 | if cap.log != expect { |
346 | 357 | for _, tc := range testCases { |
347 | 358 | t.Run(tc.name, func(t *testing.T) { |
348 | 359 | cap := &capture{} |
349 | sink := newSink(cap.Func, Options{}) | |
360 | sink := newSink(cap.Func, NewFormatter(Options{})) | |
350 | 361 | sink.Error(fmt.Errorf("err"), "msg", tc.args...) |
351 | 362 | if cap.log != tc.expect { |
352 | 363 | t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log) |
358 | 369 | func TestErrorWithCaller(t *testing.T) { |
359 | 370 | t.Run("LogCaller=All", func(t *testing.T) { |
360 | 371 | cap := &capture{} |
361 | sink := newSink(cap.Func, Options{LogCaller: All}) | |
372 | sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All})) | |
362 | 373 | sink.Error(fmt.Errorf("err"), "msg") |
363 | 374 | _, file, line, _ := runtime.Caller(0) |
364 | 375 | expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line-1) |
368 | 379 | }) |
369 | 380 | t.Run("LogCaller=Error", func(t *testing.T) { |
370 | 381 | cap := &capture{} |
371 | sink := newSink(cap.Func, Options{LogCaller: Error}) | |
382 | sink := newSink(cap.Func, NewFormatter(Options{LogCaller: Error})) | |
372 | 383 | sink.Error(fmt.Errorf("err"), "msg") |
373 | 384 | _, file, line, _ := runtime.Caller(0) |
374 | 385 | expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line-1) |
378 | 389 | }) |
379 | 390 | t.Run("LogCaller=Info", func(t *testing.T) { |
380 | 391 | cap := &capture{} |
381 | sink := newSink(cap.Func, Options{LogCaller: Info}) | |
392 | sink := newSink(cap.Func, NewFormatter(Options{LogCaller: Info})) | |
382 | 393 | sink.Error(fmt.Errorf("err"), "msg") |
383 | 394 | expect := ` "msg"="msg" "error"="err"` |
384 | 395 | if cap.log != expect { |
387 | 398 | }) |
388 | 399 | t.Run("LogCaller=None", func(t *testing.T) { |
389 | 400 | cap := &capture{} |
390 | sink := newSink(cap.Func, Options{LogCaller: None}) | |
401 | sink := newSink(cap.Func, NewFormatter(Options{LogCaller: None})) | |
391 | 402 | sink.Error(fmt.Errorf("err"), "msg") |
392 | 403 | expect := ` "msg"="msg" "error"="err"` |
393 | 404 | if cap.log != expect { |
417 | 428 | for _, tc := range testCases { |
418 | 429 | t.Run(tc.name, func(t *testing.T) { |
419 | 430 | cap := &capture{} |
420 | sink := newSink(cap.Func, Options{}) | |
431 | sink := newSink(cap.Func, NewFormatter(Options{})) | |
421 | 432 | for _, n := range tc.names { |
422 | 433 | sink = sink.WithName(n) |
423 | 434 | } |
450 | 461 | for _, tc := range testCases { |
451 | 462 | t.Run(tc.name, func(t *testing.T) { |
452 | 463 | cap := &capture{} |
453 | sink := newSink(cap.Func, Options{}) | |
464 | sink := newSink(cap.Func, NewFormatter(Options{})) | |
454 | 465 | for _, n := range tc.names { |
455 | 466 | sink = sink.WithName(n) |
456 | 467 | } |
488 | 499 | for _, tc := range testCases { |
489 | 500 | t.Run(tc.name, func(t *testing.T) { |
490 | 501 | cap := &capture{} |
491 | sink := newSink(cap.Func, Options{}) | |
502 | sink := newSink(cap.Func, NewFormatter(Options{})) | |
492 | 503 | sink = sink.WithValues(tc.values...) |
493 | 504 | sink.Info(0, "msg", tc.args...) |
494 | 505 | if cap.log != tc.expect { |
524 | 535 | for _, tc := range testCases { |
525 | 536 | t.Run(tc.name, func(t *testing.T) { |
526 | 537 | cap := &capture{} |
527 | sink := newSink(cap.Func, Options{}) | |
538 | sink := newSink(cap.Func, NewFormatter(Options{})) | |
528 | 539 | sink = sink.WithValues(tc.values...) |
529 | 540 | sink.Error(fmt.Errorf("err"), "msg", tc.args...) |
530 | 541 | if cap.log != tc.expect { |
537 | 548 | func TestInfoWithCallDepth(t *testing.T) { |
538 | 549 | t.Run("one", func(t *testing.T) { |
539 | 550 | cap := &capture{} |
540 | sink := newSink(cap.Func, Options{LogCaller: All}) | |
551 | sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All})) | |
541 | 552 | dSink, _ := sink.(logr.CallDepthLogSink) |
542 | 553 | sink = dSink.WithCallDepth(1) |
543 | 554 | sink.Info(0, "msg") |
552 | 563 | func TestErrorWithCallDepth(t *testing.T) { |
553 | 564 | t.Run("one", func(t *testing.T) { |
554 | 565 | cap := &capture{} |
555 | sink := newSink(cap.Func, Options{LogCaller: All}) | |
566 | sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All})) | |
556 | 567 | dSink, _ := sink.(logr.CallDepthLogSink) |
557 | 568 | sink = dSink.WithCallDepth(1) |
558 | 569 | sink.Error(fmt.Errorf("err"), "msg") |