From 1d414ff813127195e7a79e37d99156f9bbe98624 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Mon, 1 Dec 2025 13:52:00 +0100 Subject: [PATCH] textlogger: optionally turn off header This is useful for converting structured log parameters (e.g. error + message + key/value pairs from the Kubernetes ErrorHandler API) into a single string. --- textlogger/options.go | 15 +++++++++++++++ textlogger/textlogger.go | 32 ++++++++++++++++++++------------ textlogger/textlogger_test.go | 27 ++++++++++++++++++++++----- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/textlogger/options.go b/textlogger/options.go index b1c4eefb..6cc48a2c 100644 --- a/textlogger/options.go +++ b/textlogger/options.go @@ -59,6 +59,7 @@ type configOptions struct { verbosityDefault int fixedTime *time.Time unwind func(int) (string, int) + withHeader bool output io.Writer } @@ -106,6 +107,19 @@ func FixedTime(ts time.Time) ConfigOption { } } +// WithHeader controls whether the header (time, source code location, etc.) +// is included in the output. The default is to include it. +// +// # Experimental +// +// Notice: This function is EXPERIMENTAL and may be changed or removed in a +// later release. +func WithHeader(enabled bool) ConfigOption { + return func(co *configOptions) { + co.withHeader = enabled + } +} + // Backtrace overrides the default mechanism for determining the call site. // The callback is invoked with the number of function calls between itself // and the call site. It must return the file name and line number. An empty @@ -131,6 +145,7 @@ func NewConfig(opts ...ConfigOption) *Config { vmoduleFlagName: "vmodule", verbosityDefault: 0, unwind: runtimeBacktrace, + withHeader: true, output: os.Stderr, }, } diff --git a/textlogger/textlogger.go b/textlogger/textlogger.go index 6b9aab86..704c1e27 100644 --- a/textlogger/textlogger.go +++ b/textlogger/textlogger.go @@ -94,15 +94,21 @@ func (l *tlogger) Error(err error, msg string, kvList ...interface{}) { func (l *tlogger) print(err error, s severity.Severity, msg string, kvList []interface{}) { // Determine caller. // +1 for this frame, +1 for Info/Error. - skip := l.callDepth + 2 - file, line := l.config.co.unwind(skip) - if file == "" { - file = "???" - line = 1 - } else if slash := strings.LastIndex(file, "/"); slash >= 0 { - file = file[slash+1:] + var file string + var line int + var now time.Time + if l.config.co.withHeader { + skip := l.callDepth + 2 + file, line = l.config.co.unwind(skip) + if file == "" { + file = "???" + line = 1 + } else if slash := strings.LastIndex(file, "/"); slash >= 0 { + file = file[slash+1:] + } + now = time.Now() } - l.printWithInfos(file, line, time.Now(), err, s, msg, kvList) + l.printWithInfos(file, line, now, err, s, msg, kvList) } func runtimeBacktrace(skip int) (string, int) { @@ -124,11 +130,13 @@ func (l *tlogger) printWithInfos(file string, line int, now time.Time, err error b := buffer.GetBuffer() defer buffer.PutBuffer(b) - // Format header. - if l.config.co.fixedTime != nil { - now = *l.config.co.fixedTime + if l.config.co.withHeader { + // Format header. + if l.config.co.fixedTime != nil { + now = *l.config.co.fixedTime + } + b.FormatHeader(s, file, line, now) } - b.FormatHeader(s, file, line, now) b.Write(qMsg) diff --git a/textlogger/textlogger_test.go b/textlogger/textlogger_test.go index 1c79c751..11d30382 100644 --- a/textlogger/textlogger_test.go +++ b/textlogger/textlogger_test.go @@ -17,7 +17,9 @@ limitations under the License. package textlogger_test import ( + "bytes" "errors" + "fmt" "os" "time" @@ -65,11 +67,11 @@ func ExampleNewLogger() { someHelper(logger, "hello world") // Output: - // I1224 12:30:40.000000 123 textlogger_test.go:54] "A debug message" - // I1224 12:30:40.000000 123 textlogger_test.go:56] "An info message" - // E1224 12:30:40.000000 123 textlogger_test.go:57] "An error" err="fake error" - // I1224 12:30:40.000000 123 textlogger_test.go:58] "With values" int=42 duration="1s" float=3.12 coordinates={"X":100,"Y":200} variables={"A":1,"B":2} - // I1224 12:30:40.000000 123 textlogger_test.go:65] "hello world" + // I1224 12:30:40.000000 123 textlogger_test.go:56] "A debug message" + // I1224 12:30:40.000000 123 textlogger_test.go:58] "An info message" + // E1224 12:30:40.000000 123 textlogger_test.go:59] "An error" err="fake error" + // I1224 12:30:40.000000 123 textlogger_test.go:60] "With values" int=42 duration="1s" float=3.12 coordinates={"X":100,"Y":200} variables={"A":1,"B":2} + // I1224 12:30:40.000000 123 textlogger_test.go:67] "hello world" } func someHelper(logger klog.Logger, msg string) { @@ -108,3 +110,18 @@ func ExampleBacktrace() { // I1224 12:30:40.000000 123 ???:1] "First message" // I1224 12:30:40.000000 123 fake.go:42] "Second message" } + +func ExampleWithHeader() { + var buffer bytes.Buffer + config := textlogger.NewConfig( + textlogger.WithHeader(false), + textlogger.Output(&buffer), + ) + logger := textlogger.NewLogger(config) + + logger.Error(errors.New("fake error"), "Something broke", "id", 42) + fmt.Println(buffer.String()) + + // Output: + // "Something broke" err="fake error" id=42 +}