Skip to content

Commit 007e661

Browse files
pohlydims
authored andcommitted
textlogger: allow caller to override stack unwinding
One use case is to use Ginkgo for stack unwinding. Then `ginkgo.GingkgoHelper` also works for log output, not just for E2E failures.
1 parent 2d08296 commit 007e661

File tree

3 files changed

+67
-7
lines changed

3 files changed

+67
-7
lines changed

textlogger/options.go

+21
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ type configOptions struct {
5858
vmoduleFlagName string
5959
verbosityDefault int
6060
fixedTime *time.Time
61+
unwind func(int) (string, int)
6162
output io.Writer
6263
}
6364

@@ -105,6 +106,21 @@ func FixedTime(ts time.Time) ConfigOption {
105106
}
106107
}
107108

109+
// Backtrace overrides the default mechanism for determining the call site.
110+
// The callback is invoked with the number of function calls between itself
111+
// and the call site. It must return the file name and line number. An empty
112+
// file name indicates that the information is unknown.
113+
//
114+
// # Experimental
115+
//
116+
// Notice: This function is EXPERIMENTAL and may be changed or removed in a
117+
// later release.
118+
func Backtrace(unwind func(skip int) (filename string, line int)) ConfigOption {
119+
return func(co *configOptions) {
120+
co.unwind = unwind
121+
}
122+
}
123+
108124
// NewConfig returns a configuration with recommended defaults and optional
109125
// modifications. Command line flags are not bound to any FlagSet yet.
110126
func NewConfig(opts ...ConfigOption) *Config {
@@ -114,6 +130,7 @@ func NewConfig(opts ...ConfigOption) *Config {
114130
verbosityFlagName: "v",
115131
vmoduleFlagName: "vmodule",
116132
verbosityDefault: 0,
133+
unwind: runtimeBacktrace,
117134
output: os.Stderr,
118135
},
119136
}
@@ -127,6 +144,10 @@ func NewConfig(opts ...ConfigOption) *Config {
127144
}
128145

129146
// AddFlags registers the command line flags that control the configuration.
147+
//
148+
// The default flag names are the same as in klog, so unless those defaults
149+
// are changed, either klog.InitFlags or Config.AddFlags can be used for the
150+
// same flag set, but not both.
130151
func (c *Config) AddFlags(fs *flag.FlagSet) {
131152
fs.Var(c.Verbosity(), c.co.verbosityFlagName, "number for the log level verbosity of the testing logger")
132153
fs.Var(c.VModule(), c.co.vmoduleFlagName, "comma-separated list of pattern=N log level settings for files matching the patterns")

textlogger/textlogger.go

+13-7
Original file line numberDiff line numberDiff line change
@@ -94,19 +94,25 @@ func (l *tlogger) Error(err error, msg string, kvList ...interface{}) {
9494
func (l *tlogger) print(err error, s severity.Severity, msg string, kvList []interface{}) {
9595
// Determine caller.
9696
// +1 for this frame, +1 for Info/Error.
97-
_, file, line, ok := runtime.Caller(l.callDepth + 2)
98-
if !ok {
97+
skip := l.callDepth + 2
98+
file, line := l.config.co.unwind(skip)
99+
if file == "" {
99100
file = "???"
100101
line = 1
101-
} else {
102-
if slash := strings.LastIndex(file, "/"); slash >= 0 {
103-
file = file[slash+1:]
104-
}
102+
} else if slash := strings.LastIndex(file, "/"); slash >= 0 {
103+
file = file[slash+1:]
105104
}
106-
107105
l.printWithInfos(file, line, time.Now(), err, s, msg, kvList)
108106
}
109107

108+
func runtimeBacktrace(skip int) (string, int) {
109+
_, file, line, ok := runtime.Caller(skip + 1)
110+
if !ok {
111+
return "", 0
112+
}
113+
return file, line
114+
}
115+
110116
func (l *tlogger) printWithInfos(file string, line int, now time.Time, err error, s severity.Severity, msg string, kvList []interface{}) {
111117
// Only create a new buffer if we don't have one cached.
112118
b := buffer.GetBuffer()

textlogger/textlogger_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,36 @@ func ExampleNewLogger() {
7575
func someHelper(logger klog.Logger, msg string) {
7676
logger.WithCallDepth(1).Info(msg)
7777
}
78+
79+
func ExampleBacktrace() {
80+
ts, _ := time.Parse(time.RFC3339, "2000-12-24T12:30:40Z")
81+
internal.Pid = 123 // To get consistent output for each run.
82+
backtraceCounter := 0
83+
config := textlogger.NewConfig(
84+
textlogger.FixedTime(ts), // To get consistent output for each run.
85+
textlogger.Backtrace(func(skip int) (filename string, line int) {
86+
backtraceCounter++
87+
if backtraceCounter == 1 {
88+
// Simulate "missing information".
89+
return "", 0
90+
}
91+
return "fake.go", 42
92+
93+
// A real implementation could use Ginkgo:
94+
//
95+
// import ginkgotypes "github.com/onsi/ginkgo/v2/types"
96+
//
97+
// location := ginkgotypes.NewCodeLocation(skip + 1)
98+
// return location.FileName, location.LineNumber
99+
}),
100+
textlogger.Output(os.Stdout),
101+
)
102+
logger := textlogger.NewLogger(config)
103+
104+
logger.Info("First message")
105+
logger.Info("Second message")
106+
107+
// Output:
108+
// I1224 12:30:40.000000 123 ???:1] "First message"
109+
// I1224 12:30:40.000000 123 fake.go:42] "Second message"
110+
}

0 commit comments

Comments
 (0)