funcr: Add Options.LogCallerFunc
This optional (default off) flag tells funcr to look up the calling
function name and include that in the caller information.
Tim Hockin
2 years ago
84 | 84 | // This has some overhead, so some users might not want it. |
85 | 85 | LogCaller MessageClass |
86 | 86 | |
87 | // LogCallerFunc tells funcr to also log the calling function name. This | |
88 | // has no effect if caller logging is not enabled (see Options.LogCaller). | |
89 | LogCallerFunc bool | |
90 | ||
87 | 91 | // LogTimestamp tells funcr to add a "ts" key to log lines. This has some |
88 | 92 | // overhead, so some users might not want it. |
89 | 93 | LogTimestamp bool |
163 | 167 | |
164 | 168 | func newFormatter(opts Options, outfmt outputFormat) Formatter { |
165 | 169 | f := Formatter{ |
166 | outputFormat: outfmt, | |
167 | prefix: "", | |
168 | values: nil, | |
169 | depth: 0, | |
170 | logCaller: opts.LogCaller, | |
171 | logTimestamp: opts.LogTimestamp, | |
172 | verbosity: opts.Verbosity, | |
170 | outputFormat: outfmt, | |
171 | prefix: "", | |
172 | values: nil, | |
173 | depth: 0, | |
174 | logCaller: opts.LogCaller, | |
175 | logCallerFunc: opts.LogCallerFunc, | |
176 | logTimestamp: opts.LogTimestamp, | |
177 | verbosity: opts.Verbosity, | |
173 | 178 | } |
174 | 179 | return f |
175 | 180 | } |
178 | 183 | // implementation. It should be constructed with NewFormatter. Some of |
179 | 184 | // its methods directly implement logr.LogSink. |
180 | 185 | type Formatter struct { |
181 | outputFormat outputFormat | |
182 | prefix string | |
183 | values []interface{} | |
184 | valuesStr string | |
185 | depth int | |
186 | logCaller MessageClass | |
187 | logTimestamp bool | |
188 | verbosity int | |
186 | outputFormat outputFormat | |
187 | prefix string | |
188 | values []interface{} | |
189 | valuesStr string | |
190 | depth int | |
191 | logCaller MessageClass | |
192 | logCallerFunc bool | |
193 | logTimestamp bool | |
194 | verbosity int | |
189 | 195 | } |
190 | 196 | |
191 | 197 | // outputFormat indicates which outputFormat to use. |
473 | 479 | type callerID struct { |
474 | 480 | File string `json:"file"` |
475 | 481 | Line int `json:"line"` |
482 | Func string `json:"function,omitempty"` | |
476 | 483 | } |
477 | 484 | |
478 | 485 | func (f Formatter) caller() callerID { |
479 | 486 | // +1 for this frame, +1 for Info/Error. |
480 | _, file, line, ok := runtime.Caller(f.depth + 2) | |
487 | pc, file, line, ok := runtime.Caller(f.depth + 2) | |
481 | 488 | if !ok { |
482 | return callerID{"<unknown>", 0} | |
483 | } | |
484 | return callerID{filepath.Base(file), line} | |
489 | return callerID{"<unknown>", 0, ""} | |
490 | } | |
491 | fn := "" | |
492 | if f.logCallerFunc { | |
493 | if fp := runtime.FuncForPC(pc); fp != nil { | |
494 | fn = fp.Name() | |
495 | } | |
496 | } | |
497 | ||
498 | return callerID{filepath.Base(file), line, fn} | |
485 | 499 | } |
486 | 500 | |
487 | 501 | // Init configures this Formatter from runtime info, such as the call depth |
579 | 579 | if cap.log != expect { |
580 | 580 | t.Errorf("\nexpected %q\n got %q", expect, cap.log) |
581 | 581 | } |
582 | sink.Error(fmt.Errorf("error"), "msg") | |
583 | _, file, line, _ = runtime.Caller(0) | |
584 | expect = fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="error"`, filepath.Base(file), line-1) | |
585 | if cap.log != expect { | |
586 | t.Errorf("\nexpected %q\n got %q", expect, cap.log) | |
587 | } | |
588 | }) | |
589 | t.Run("LogCaller=All, LogCallerFunc=true", func(t *testing.T) { | |
590 | thisFunc := "github.com/go-logr/logr/funcr.TestInfoWithCaller.func2" | |
591 | cap := &capture{} | |
592 | sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All, LogCallerFunc: true})) | |
593 | sink.Info(0, "msg") | |
594 | _, file, line, _ := runtime.Caller(0) | |
595 | expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d,"function":%q} "level"=0 "msg"="msg"`, filepath.Base(file), line-1, thisFunc) | |
596 | if cap.log != expect { | |
597 | t.Errorf("\nexpected %q\n got %q", expect, cap.log) | |
598 | } | |
599 | sink.Error(fmt.Errorf("error"), "msg") | |
600 | _, file, line, _ = runtime.Caller(0) | |
601 | expect = fmt.Sprintf(` "caller"={"file":%q,"line":%d,"function":%q} "msg"="msg" "error"="error"`, filepath.Base(file), line-1, thisFunc) | |
602 | if cap.log != expect { | |
603 | t.Errorf("\nexpected %q\n got %q", expect, cap.log) | |
604 | } | |
582 | 605 | }) |
583 | 606 | t.Run("LogCaller=Info", func(t *testing.T) { |
584 | 607 | cap := &capture{} |
589 | 612 | if cap.log != expect { |
590 | 613 | t.Errorf("\nexpected %q\n got %q", expect, cap.log) |
591 | 614 | } |
615 | sink.Error(fmt.Errorf("error"), "msg") | |
616 | expect = ` "msg"="msg" "error"="error"` | |
617 | if cap.log != expect { | |
618 | t.Errorf("\nexpected %q\n got %q", expect, cap.log) | |
619 | } | |
592 | 620 | }) |
593 | 621 | t.Run("LogCaller=Error", func(t *testing.T) { |
594 | 622 | cap := &capture{} |
598 | 626 | if cap.log != expect { |
599 | 627 | t.Errorf("\nexpected %q\n got %q", expect, cap.log) |
600 | 628 | } |
629 | sink.Error(fmt.Errorf("error"), "msg") | |
630 | _, file, line, _ := runtime.Caller(0) | |
631 | expect = fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="error"`, filepath.Base(file), line-1) | |
632 | if cap.log != expect { | |
633 | t.Errorf("\nexpected %q\n got %q", expect, cap.log) | |
634 | } | |
601 | 635 | }) |
602 | 636 | t.Run("LogCaller=None", func(t *testing.T) { |
603 | 637 | cap := &capture{} |
604 | 638 | sink := newSink(cap.Func, NewFormatter(Options{LogCaller: None})) |
605 | 639 | sink.Info(0, "msg") |
606 | 640 | expect := ` "level"=0 "msg"="msg"` |
641 | if cap.log != expect { | |
642 | t.Errorf("\nexpected %q\n got %q", expect, cap.log) | |
643 | } | |
644 | sink.Error(fmt.Errorf("error"), "msg") | |
645 | expect = ` "msg"="msg" "error"="error"` | |
607 | 646 | if cap.log != expect { |
608 | 647 | t.Errorf("\nexpected %q\n got %q", expect, cap.log) |
609 | 648 | } |