1 /*
2 Copyright 2021 The logr Authors.
3 4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7 8 http://www.apache.org/licenses/LICENSE-2.0
9 10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 17 // Package funcr implements formatting of structured log messages and
18 // optionally captures the call site and timestamp.
19 //
20 // The simplest way to use it is via its implementation of a
21 // github.com/go-logr/logr.LogSink with output through an arbitrary
22 // "write" function. See New and NewJSON for details.
23 //
24 // # Custom LogSinks
25 //
26 // For users who need more control, a funcr.Formatter can be embedded inside
27 // your own custom LogSink implementation. This is useful when the LogSink
28 // needs to implement additional methods, for example.
29 //
30 // # Formatting
31 //
32 // This will respect logr.Marshaler, fmt.Stringer, and error interfaces for
33 // values which are being logged. When rendering a struct, funcr will use Go's
34 // standard JSON tags (all except "string").
35 package funcr
36 37 import (
38 "bytes"
39 "encoding"
40 "encoding/json"
41 "fmt"
42 "path/filepath"
43 "reflect"
44 "runtime"
45 "strconv"
46 "strings"
47 "time"
48 49 "github.com/go-logr/logr"
50 )
51 52 // New returns a logr.Logger which is implemented by an arbitrary function.
53 func New(fn func(prefix, args string), opts Options) logr.Logger {
54 return logr.New(newSink(fn, NewFormatter(opts)))
55 }
56 57 // NewJSON returns a logr.Logger which is implemented by an arbitrary function
58 // and produces JSON output.
59 func NewJSON(fn func(obj string), opts Options) logr.Logger {
60 fnWrapper := func(_, obj string) {
61 fn(obj)
62 }
63 return logr.New(newSink(fnWrapper, NewFormatterJSON(opts)))
64 }
65 66 // Underlier exposes access to the underlying logging function. Since
67 // callers only have a logr.Logger, they have to know which
68 // implementation is in use, so this interface is less of an
69 // abstraction and more of a way to test type conversion.
70 type Underlier interface {
71 GetUnderlying() func(prefix, args string)
72 }
73 74 func newSink(fn func(prefix, args string), formatter Formatter) logr.LogSink {
75 l := &fnlogger{
76 Formatter: formatter,
77 write: fn,
78 }
79 // For skipping fnlogger.Info and fnlogger.Error.
80 l.AddCallDepth(1) // via Formatter
81 return l
82 }
83 84 // Options carries parameters which influence the way logs are generated.
85 type Options struct {
86 // LogCaller tells funcr to add a "caller" key to some or all log lines.
87 // This has some overhead, so some users might not want it.
88 LogCaller MessageClass
89 90 // LogCallerFunc tells funcr to also log the calling function name. This
91 // has no effect if caller logging is not enabled (see Options.LogCaller).
92 LogCallerFunc bool
93 94 // LogTimestamp tells funcr to add a "ts" key to log lines. This has some
95 // overhead, so some users might not want it.
96 LogTimestamp bool
97 98 // TimestampFormat tells funcr how to render timestamps when LogTimestamp
99 // is enabled. If not specified, a default format will be used. For more
100 // details, see docs for Go's time.Layout.
101 TimestampFormat string
102 103 // LogInfoLevel tells funcr what key to use to log the info level.
104 // If not specified, the info level will be logged as "level".
105 // If this is set to "", the info level will not be logged at all.
106 LogInfoLevel *string
107 108 // Verbosity tells funcr which V logs to produce. Higher values enable
109 // more logs. Info logs at or below this level will be written, while logs
110 // above this level will be discarded.
111 Verbosity int
112 113 // RenderBuiltinsHook allows users to mutate the list of key-value pairs
114 // while a log line is being rendered. The kvList argument follows logr
115 // conventions - each pair of slice elements is comprised of a string key
116 // and an arbitrary value (verified and sanitized before calling this
117 // hook). The value returned must follow the same conventions. This hook
118 // can be used to audit or modify logged data. For example, you might want
119 // to prefix all of funcr's built-in keys with some string. This hook is
120 // only called for built-in (provided by funcr itself) key-value pairs.
121 // Equivalent hooks are offered for key-value pairs saved via
122 // logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and
123 // for user-provided pairs (see RenderArgsHook).
124 RenderBuiltinsHook func(kvList []any) []any
125 126 // RenderValuesHook is the same as RenderBuiltinsHook, except that it is
127 // only called for key-value pairs saved via logr.Logger.WithValues. See
128 // RenderBuiltinsHook for more details.
129 RenderValuesHook func(kvList []any) []any
130 131 // RenderArgsHook is the same as RenderBuiltinsHook, except that it is only
132 // called for key-value pairs passed directly to Info and Error. See
133 // RenderBuiltinsHook for more details.
134 RenderArgsHook func(kvList []any) []any
135 136 // MaxLogDepth tells funcr how many levels of nested fields (e.g. a struct
137 // that contains a struct, etc.) it may log. Every time it finds a struct,
138 // slice, array, or map the depth is increased by one. When the maximum is
139 // reached, the value will be converted to a string indicating that the max
140 // depth has been exceeded. If this field is not specified, a default
141 // value will be used.
142 MaxLogDepth int
143 }
144 145 // MessageClass indicates which category or categories of messages to consider.
146 type MessageClass int
147 148 const (
149 // None ignores all message classes.
150 None MessageClass = iota
151 // All considers all message classes.
152 All
153 // Info only considers info messages.
154 Info
155 // Error only considers error messages.
156 Error
157 )
158 159 // fnlogger inherits some of its LogSink implementation from Formatter
160 // and just needs to add some glue code.
161 type fnlogger struct {
162 Formatter
163 write func(prefix, args string)
164 }
165 166 func (l fnlogger) WithName(name string) logr.LogSink {
167 l.AddName(name) // via Formatter
168 return &l
169 }
170 171 func (l fnlogger) WithValues(kvList ...any) logr.LogSink {
172 l.AddValues(kvList) // via Formatter
173 return &l
174 }
175 176 func (l fnlogger) WithCallDepth(depth int) logr.LogSink {
177 l.AddCallDepth(depth) // via Formatter
178 return &l
179 }
180 181 func (l fnlogger) Info(level int, msg string, kvList ...any) {
182 prefix, args := l.FormatInfo(level, msg, kvList)
183 l.write(prefix, args)
184 }
185 186 func (l fnlogger) Error(err error, msg string, kvList ...any) {
187 prefix, args := l.FormatError(err, msg, kvList)
188 l.write(prefix, args)
189 }
190 191 func (l fnlogger) GetUnderlying() func(prefix, args string) {
192 return l.write
193 }
194 195 // Assert conformance to the interfaces.
196 var _ logr.LogSink = &fnlogger{}
197 var _ logr.CallDepthLogSink = &fnlogger{}
198 var _ Underlier = &fnlogger{}
199 200 // NewFormatter constructs a Formatter which emits a JSON-like key=value format.
201 func NewFormatter(opts Options) Formatter {
202 return newFormatter(opts, outputKeyValue)
203 }
204 205 // NewFormatterJSON constructs a Formatter which emits strict JSON.
206 func NewFormatterJSON(opts Options) Formatter {
207 return newFormatter(opts, outputJSON)
208 }
209 210 // Defaults for Options.
211 const defaultTimestampFormat = "2006-01-02 15:04:05.000000"
212 const defaultMaxLogDepth = 16
213 214 func newFormatter(opts Options, outfmt outputFormat) Formatter {
215 if opts.TimestampFormat == "" {
216 opts.TimestampFormat = defaultTimestampFormat
217 }
218 if opts.MaxLogDepth == 0 {
219 opts.MaxLogDepth = defaultMaxLogDepth
220 }
221 if opts.LogInfoLevel == nil {
222 opts.LogInfoLevel = new(string)
223 *opts.LogInfoLevel = "level"
224 }
225 f := Formatter{
226 outputFormat: outfmt,
227 prefix: "",
228 values: nil,
229 depth: 0,
230 opts: &opts,
231 }
232 return f
233 }
234 235 // Formatter is an opaque struct which can be embedded in a LogSink
236 // implementation. It should be constructed with NewFormatter. Some of
237 // its methods directly implement logr.LogSink.
238 type Formatter struct {
239 outputFormat outputFormat
240 prefix string
241 values []any
242 valuesStr string
243 depth int
244 opts *Options
245 groupName string // for slog groups
246 groups []groupDef
247 }
248 249 // outputFormat indicates which outputFormat to use.
250 type outputFormat int
251 252 const (
253 // outputKeyValue emits a JSON-like key=value format, but not strict JSON.
254 outputKeyValue outputFormat = iota
255 // outputJSON emits strict JSON.
256 outputJSON
257 )
258 259 // groupDef represents a saved group. The values may be empty, but we don't
260 // know if we need to render the group until the final record is rendered.
261 type groupDef struct {
262 name string
263 values string
264 }
265 266 // PseudoStruct is a list of key-value pairs that gets logged as a struct.
267 type PseudoStruct []any
268 269 // render produces a log line, ready to use.
270 func (f Formatter) render(builtins, args []any) string {
271 // Empirically bytes.Buffer is faster than strings.Builder for this.
272 buf := bytes.NewBuffer(make([]byte, 0, 1024))
273 274 if f.outputFormat == outputJSON {
275 buf.WriteByte('{') // for the whole record
276 }
277 278 // Render builtins
279 vals := builtins
280 if hook := f.opts.RenderBuiltinsHook; hook != nil {
281 vals = hook(f.sanitize(vals))
282 }
283 f.flatten(buf, vals, false) // keys are ours, no need to escape
284 continuing := len(builtins) > 0
285 286 // Turn the inner-most group into a string
287 argsStr := func() string {
288 buf := bytes.NewBuffer(make([]byte, 0, 1024))
289 290 vals = args
291 if hook := f.opts.RenderArgsHook; hook != nil {
292 vals = hook(f.sanitize(vals))
293 }
294 f.flatten(buf, vals, true) // escape user-provided keys
295 296 return buf.String()
297 }()
298 299 // Render the stack of groups from the inside out.
300 bodyStr := f.renderGroup(f.groupName, f.valuesStr, argsStr)
301 for i := len(f.groups) - 1; i >= 0; i-- {
302 grp := &f.groups[i]
303 if grp.values == "" && bodyStr == "" {
304 // no contents, so we must elide the whole group
305 continue
306 }
307 bodyStr = f.renderGroup(grp.name, grp.values, bodyStr)
308 }
309 310 if bodyStr != "" {
311 if continuing {
312 buf.WriteByte(f.comma())
313 }
314 buf.WriteString(bodyStr)
315 }
316 317 if f.outputFormat == outputJSON {
318 buf.WriteByte('}') // for the whole record
319 }
320 321 return buf.String()
322 }
323 324 // renderGroup returns a string representation of the named group with rendered
325 // values and args. If the name is empty, this will return the values and args,
326 // joined. If the name is not empty, this will return a single key-value pair,
327 // where the value is a grouping of the values and args. If the values and
328 // args are both empty, this will return an empty string, even if the name was
329 // specified.
330 func (f Formatter) renderGroup(name string, values string, args string) string {
331 buf := bytes.NewBuffer(make([]byte, 0, 1024))
332 333 needClosingBrace := false
334 if name != "" && (values != "" || args != "") {
335 buf.WriteString(f.quoted(name, true)) // escape user-provided keys
336 buf.WriteByte(f.colon())
337 buf.WriteByte('{')
338 needClosingBrace = true
339 }
340 341 continuing := false
342 if values != "" {
343 buf.WriteString(values)
344 continuing = true
345 }
346 347 if args != "" {
348 if continuing {
349 buf.WriteByte(f.comma())
350 }
351 buf.WriteString(args)
352 }
353 354 if needClosingBrace {
355 buf.WriteByte('}')
356 }
357 358 return buf.String()
359 }
360 361 // flatten renders a list of key-value pairs into a buffer. If escapeKeys is
362 // true, the keys are assumed to have non-JSON-compatible characters in them
363 // and must be evaluated for escapes.
364 //
365 // This function returns a potentially modified version of kvList, which
366 // ensures that there is a value for every key (adding a value if needed) and
367 // that each key is a string (substituting a key if needed).
368 func (f Formatter) flatten(buf *bytes.Buffer, kvList []any, escapeKeys bool) []any {
369 // This logic overlaps with sanitize() but saves one type-cast per key,
370 // which can be measurable.
371 if len(kvList)%2 != 0 {
372 kvList = append(kvList, noValue)
373 }
374 copied := false
375 for i := 0; i < len(kvList); i += 2 {
376 k, ok := kvList[i].(string)
377 if !ok {
378 if !copied {
379 newList := make([]any, len(kvList))
380 copy(newList, kvList)
381 kvList = newList
382 copied = true
383 }
384 k = f.nonStringKey(kvList[i])
385 kvList[i] = k
386 }
387 v := kvList[i+1]
388 389 if i > 0 {
390 if f.outputFormat == outputJSON {
391 buf.WriteByte(f.comma())
392 } else {
393 // In theory the format could be something we don't understand. In
394 // practice, we control it, so it won't be.
395 buf.WriteByte(' ')
396 }
397 }
398 399 buf.WriteString(f.quoted(k, escapeKeys))
400 buf.WriteByte(f.colon())
401 buf.WriteString(f.pretty(v))
402 }
403 return kvList
404 }
405 406 func (f Formatter) quoted(str string, escape bool) string {
407 if escape {
408 return prettyString(str)
409 }
410 // this is faster
411 return `"` + str + `"`
412 }
413 414 func (f Formatter) comma() byte {
415 if f.outputFormat == outputJSON {
416 return ','
417 }
418 return ' '
419 }
420 421 func (f Formatter) colon() byte {
422 if f.outputFormat == outputJSON {
423 return ':'
424 }
425 return '='
426 }
427 428 func (f Formatter) pretty(value any) string {
429 return f.prettyWithFlags(value, 0, 0)
430 }
431 432 const (
433 flagRawStruct = 0x1 // do not print braces on structs
434 )
435 436 // TODO: This is not fast. Most of the overhead goes here.
437 func (f Formatter) prettyWithFlags(value any, flags uint32, depth int) string {
438 if depth > f.opts.MaxLogDepth {
439 return `"<max-log-depth-exceeded>"`
440 }
441 442 // Handle types that take full control of logging.
443 if v, ok := value.(logr.Marshaler); ok {
444 // Replace the value with what the type wants to get logged.
445 // That then gets handled below via reflection.
446 value = invokeMarshaler(v)
447 }
448 449 // Handle types that want to format themselves.
450 switch v := value.(type) {
451 case fmt.Stringer:
452 value = invokeStringer(v)
453 case error:
454 value = invokeError(v)
455 }
456 457 // Handling the most common types without reflect is a small perf win.
458 switch v := value.(type) {
459 case bool:
460 return strconv.FormatBool(v)
461 case string:
462 return prettyString(v)
463 case int:
464 return strconv.FormatInt(int64(v), 10)
465 case int8:
466 return strconv.FormatInt(int64(v), 10)
467 case int16:
468 return strconv.FormatInt(int64(v), 10)
469 case int32:
470 return strconv.FormatInt(int64(v), 10)
471 case int64:
472 return strconv.FormatInt(int64(v), 10)
473 case uint:
474 return strconv.FormatUint(uint64(v), 10)
475 case uint8:
476 return strconv.FormatUint(uint64(v), 10)
477 case uint16:
478 return strconv.FormatUint(uint64(v), 10)
479 case uint32:
480 return strconv.FormatUint(uint64(v), 10)
481 case uint64:
482 return strconv.FormatUint(v, 10)
483 case uintptr:
484 return strconv.FormatUint(uint64(v), 10)
485 case float32:
486 return strconv.FormatFloat(float64(v), 'f', -1, 32)
487 case float64:
488 return strconv.FormatFloat(v, 'f', -1, 64)
489 case complex64:
490 return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"`
491 case complex128:
492 return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"`
493 case PseudoStruct:
494 buf := bytes.NewBuffer(make([]byte, 0, 1024))
495 v = f.sanitize(v)
496 if flags&flagRawStruct == 0 {
497 buf.WriteByte('{')
498 }
499 for i := 0; i < len(v); i += 2 {
500 if i > 0 {
501 buf.WriteByte(f.comma())
502 }
503 k, _ := v[i].(string) // sanitize() above means no need to check success
504 // arbitrary keys might need escaping
505 buf.WriteString(prettyString(k))
506 buf.WriteByte(f.colon())
507 buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1))
508 }
509 if flags&flagRawStruct == 0 {
510 buf.WriteByte('}')
511 }
512 return buf.String()
513 }
514 515 buf := bytes.NewBuffer(make([]byte, 0, 256))
516 t := reflect.TypeOf(value)
517 if t == nil {
518 return "null"
519 }
520 v := reflect.ValueOf(value)
521 switch t.Kind() {
522 case reflect.Bool:
523 return strconv.FormatBool(v.Bool())
524 case reflect.String:
525 return prettyString(v.String())
526 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
527 return strconv.FormatInt(int64(v.Int()), 10)
528 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
529 return strconv.FormatUint(uint64(v.Uint()), 10)
530 case reflect.Float32:
531 return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32)
532 case reflect.Float64:
533 return strconv.FormatFloat(v.Float(), 'f', -1, 64)
534 case reflect.Complex64:
535 return `"` + strconv.FormatComplex(complex128(v.Complex()), 'f', -1, 64) + `"`
536 case reflect.Complex128:
537 return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"`
538 case reflect.Struct:
539 if flags&flagRawStruct == 0 {
540 buf.WriteByte('{')
541 }
542 printComma := false // testing i>0 is not enough because of JSON omitted fields
543 for i := 0; i < t.NumField(); i++ {
544 fld := t.Field(i)
545 if fld.PkgPath != "" {
546 // reflect says this field is only defined for non-exported fields.
547 continue
548 }
549 if !v.Field(i).CanInterface() {
550 // reflect isn't clear exactly what this means, but we can't use it.
551 continue
552 }
553 name := ""
554 omitempty := false
555 if tag, found := fld.Tag.Lookup("json"); found {
556 if tag == "-" {
557 continue
558 }
559 if comma := strings.Index(tag, ","); comma != -1 {
560 if n := tag[:comma]; n != "" {
561 name = n
562 }
563 rest := tag[comma:]
564 if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") {
565 omitempty = true
566 }
567 } else {
568 name = tag
569 }
570 }
571 if omitempty && isEmpty(v.Field(i)) {
572 continue
573 }
574 if printComma {
575 buf.WriteByte(f.comma())
576 }
577 printComma = true // if we got here, we are rendering a field
578 if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" {
579 buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1))
580 continue
581 }
582 if name == "" {
583 name = fld.Name
584 }
585 // field names can't contain characters which need escaping
586 buf.WriteString(f.quoted(name, false))
587 buf.WriteByte(f.colon())
588 buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1))
589 }
590 if flags&flagRawStruct == 0 {
591 buf.WriteByte('}')
592 }
593 return buf.String()
594 case reflect.Slice, reflect.Array:
595 // If this is outputing as JSON make sure this isn't really a json.RawMessage.
596 // If so just emit "as-is" and don't pretty it as that will just print
597 // it as [X,Y,Z,...] which isn't terribly useful vs the string form you really want.
598 if f.outputFormat == outputJSON {
599 if rm, ok := value.(json.RawMessage); ok {
600 // If it's empty make sure we emit an empty value as the array style would below.
601 if len(rm) > 0 {
602 buf.Write(rm)
603 } else {
604 buf.WriteString("null")
605 }
606 return buf.String()
607 }
608 }
609 buf.WriteByte('[')
610 for i := 0; i < v.Len(); i++ {
611 if i > 0 {
612 buf.WriteByte(f.comma())
613 }
614 e := v.Index(i)
615 buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1))
616 }
617 buf.WriteByte(']')
618 return buf.String()
619 case reflect.Map:
620 buf.WriteByte('{')
621 // This does not sort the map keys, for best perf.
622 it := v.MapRange()
623 i := 0
624 for it.Next() {
625 if i > 0 {
626 buf.WriteByte(f.comma())
627 }
628 // If a map key supports TextMarshaler, use it.
629 keystr := ""
630 if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok {
631 txt, err := m.MarshalText()
632 if err != nil {
633 keystr = fmt.Sprintf("<error-MarshalText: %s>", err.Error())
634 } else {
635 keystr = string(txt)
636 }
637 keystr = prettyString(keystr)
638 } else {
639 // prettyWithFlags will produce already-escaped values
640 keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1)
641 if t.Key().Kind() != reflect.String {
642 // JSON only does string keys. Unlike Go's standard JSON, we'll
643 // convert just about anything to a string.
644 keystr = prettyString(keystr)
645 }
646 }
647 buf.WriteString(keystr)
648 buf.WriteByte(f.colon())
649 buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1))
650 i++
651 }
652 buf.WriteByte('}')
653 return buf.String()
654 case reflect.Ptr, reflect.Interface:
655 if v.IsNil() {
656 return "null"
657 }
658 return f.prettyWithFlags(v.Elem().Interface(), 0, depth)
659 }
660 return fmt.Sprintf(`"<unhandled-%s>"`, t.Kind().String())
661 }
662 663 func prettyString(s string) string {
664 // Avoid escaping (which does allocations) if we can.
665 if needsEscape(s) {
666 return strconv.Quote(s)
667 }
668 b := bytes.NewBuffer(make([]byte, 0, 1024))
669 b.WriteByte('"')
670 b.WriteString(s)
671 b.WriteByte('"')
672 return b.String()
673 }
674 675 // needsEscape determines whether the input string needs to be escaped or not,
676 // without doing any allocations.
677 func needsEscape(s string) bool {
678 for _, r := range s {
679 if !strconv.IsPrint(r) || r == '\\' || r == '"' {
680 return true
681 }
682 }
683 return false
684 }
685 686 func isEmpty(v reflect.Value) bool {
687 switch v.Kind() {
688 case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
689 return v.Len() == 0
690 case reflect.Bool:
691 return !v.Bool()
692 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
693 return v.Int() == 0
694 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
695 return v.Uint() == 0
696 case reflect.Float32, reflect.Float64:
697 return v.Float() == 0
698 case reflect.Complex64, reflect.Complex128:
699 return v.Complex() == 0
700 case reflect.Interface, reflect.Ptr:
701 return v.IsNil()
702 }
703 return false
704 }
705 706 func invokeMarshaler(m logr.Marshaler) (ret any) {
707 defer func() {
708 if r := recover(); r != nil {
709 ret = fmt.Sprintf("<panic: %s>", r)
710 }
711 }()
712 return m.MarshalLog()
713 }
714 715 func invokeStringer(s fmt.Stringer) (ret string) {
716 defer func() {
717 if r := recover(); r != nil {
718 ret = fmt.Sprintf("<panic: %s>", r)
719 }
720 }()
721 return s.String()
722 }
723 724 func invokeError(e error) (ret string) {
725 defer func() {
726 if r := recover(); r != nil {
727 ret = fmt.Sprintf("<panic: %s>", r)
728 }
729 }()
730 return e.Error()
731 }
732 733 // Caller represents the original call site for a log line, after considering
734 // logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and
735 // Line fields will always be provided, while the Func field is optional.
736 // Users can set the render hook fields in Options to examine logged key-value
737 // pairs, one of which will be {"caller", Caller} if the Options.LogCaller
738 // field is enabled for the given MessageClass.
739 type Caller struct {
740 // File is the basename of the file for this call site.
741 File string `json:"file"`
742 // Line is the line number in the file for this call site.
743 Line int `json:"line"`
744 // Func is the function name for this call site, or empty if
745 // Options.LogCallerFunc is not enabled.
746 Func string `json:"function,omitempty"`
747 }
748 749 func (f Formatter) caller() Caller {
750 // +1 for this frame, +1 for Info/Error.
751 pc, file, line, ok := runtime.Caller(f.depth + 2)
752 if !ok {
753 return Caller{"<unknown>", 0, ""}
754 }
755 fn := ""
756 if f.opts.LogCallerFunc {
757 if fp := runtime.FuncForPC(pc); fp != nil {
758 fn = fp.Name()
759 }
760 }
761 762 return Caller{filepath.Base(file), line, fn}
763 }
764 765 const noValue = "<no-value>"
766 767 func (f Formatter) nonStringKey(v any) string {
768 return fmt.Sprintf("<non-string-key: %s>", f.snippet(v))
769 }
770 771 // snippet produces a short snippet string of an arbitrary value.
772 func (f Formatter) snippet(v any) string {
773 const snipLen = 16
774 775 snip := f.pretty(v)
776 if len(snip) > snipLen {
777 snip = snip[:snipLen]
778 }
779 return snip
780 }
781 782 // sanitize ensures that a list of key-value pairs has a value for every key
783 // (adding a value if needed) and that each key is a string (substituting a key
784 // if needed).
785 func (f Formatter) sanitize(kvList []any) []any {
786 if len(kvList)%2 != 0 {
787 kvList = append(kvList, noValue)
788 }
789 for i := 0; i < len(kvList); i += 2 {
790 _, ok := kvList[i].(string)
791 if !ok {
792 kvList[i] = f.nonStringKey(kvList[i])
793 }
794 }
795 return kvList
796 }
797 798 // startGroup opens a new group scope (basically a sub-struct), which locks all
799 // the current saved values and starts them anew. This is needed to satisfy
800 // slog.
801 func (f *Formatter) startGroup(name string) {
802 // Unnamed groups are just inlined.
803 if name == "" {
804 return
805 }
806 807 n := len(f.groups)
808 f.groups = append(f.groups[:n:n], groupDef{f.groupName, f.valuesStr})
809 810 // Start collecting new values.
811 f.groupName = name
812 f.valuesStr = ""
813 f.values = nil
814 }
815 816 // Init configures this Formatter from runtime info, such as the call depth
817 // imposed by logr itself.
818 // Note that this receiver is a pointer, so depth can be saved.
819 func (f *Formatter) Init(info logr.RuntimeInfo) {
820 f.depth += info.CallDepth
821 }
822 823 // Enabled checks whether an info message at the given level should be logged.
824 func (f Formatter) Enabled(level int) bool {
825 return level <= f.opts.Verbosity
826 }
827 828 // GetDepth returns the current depth of this Formatter. This is useful for
829 // implementations which do their own caller attribution.
830 func (f Formatter) GetDepth() int {
831 return f.depth
832 }
833 834 // FormatInfo renders an Info log message into strings. The prefix will be
835 // empty when no names were set (via AddNames), or when the output is
836 // configured for JSON.
837 func (f Formatter) FormatInfo(level int, msg string, kvList []any) (prefix, argsStr string) {
838 args := make([]any, 0, 64) // using a constant here impacts perf
839 prefix = f.prefix
840 if f.outputFormat == outputJSON {
841 args = append(args, "logger", prefix)
842 prefix = ""
843 }
844 if f.opts.LogTimestamp {
845 args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat))
846 }
847 if policy := f.opts.LogCaller; policy == All || policy == Info {
848 args = append(args, "caller", f.caller())
849 }
850 if key := *f.opts.LogInfoLevel; key != "" {
851 args = append(args, key, level)
852 }
853 args = append(args, "msg", msg)
854 return prefix, f.render(args, kvList)
855 }
856 857 // FormatError renders an Error log message into strings. The prefix will be
858 // empty when no names were set (via AddNames), or when the output is
859 // configured for JSON.
860 func (f Formatter) FormatError(err error, msg string, kvList []any) (prefix, argsStr string) {
861 args := make([]any, 0, 64) // using a constant here impacts perf
862 prefix = f.prefix
863 if f.outputFormat == outputJSON {
864 args = append(args, "logger", prefix)
865 prefix = ""
866 }
867 if f.opts.LogTimestamp {
868 args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat))
869 }
870 if policy := f.opts.LogCaller; policy == All || policy == Error {
871 args = append(args, "caller", f.caller())
872 }
873 args = append(args, "msg", msg)
874 var loggableErr any
875 if err != nil {
876 loggableErr = err.Error()
877 }
878 args = append(args, "error", loggableErr)
879 return prefix, f.render(args, kvList)
880 }
881 882 // AddName appends the specified name. funcr uses '/' characters to separate
883 // name elements. Callers should not pass '/' in the provided name string, but
884 // this library does not actually enforce that.
885 func (f *Formatter) AddName(name string) {
886 if len(f.prefix) > 0 {
887 f.prefix += "/"
888 }
889 f.prefix += name
890 }
891 892 // AddValues adds key-value pairs to the set of saved values to be logged with
893 // each log line.
894 func (f *Formatter) AddValues(kvList []any) {
895 // Three slice args forces a copy.
896 n := len(f.values)
897 f.values = append(f.values[:n:n], kvList...)
898 899 vals := f.values
900 if hook := f.opts.RenderValuesHook; hook != nil {
901 vals = hook(f.sanitize(vals))
902 }
903 904 // Pre-render values, so we don't have to do it on each Info/Error call.
905 buf := bytes.NewBuffer(make([]byte, 0, 1024))
906 f.flatten(buf, vals, true) // escape user-provided keys
907 f.valuesStr = buf.String()
908 }
909 910 // AddCallDepth increases the number of stack-frames to skip when attributing
911 // the log line to a file and line.
912 func (f *Formatter) AddCallDepth(depth int) {
913 f.depth += depth
914 }
915