Honor stderrthreshold when logtostderr is enabled

Opt into the fixed klog behavior by setting legacy_stderr_threshold_behavior=false
after klog.InitFlags(). Ref: kubernetes/klog#212, kubernetes/klog#432

Signed-off-by: Pierluigi Lenoci <pierluigi.lenoci@gmail.com>
This commit is contained in:
Pierluigi Lenoci
2026-03-27 00:15:23 +01:00
parent 12357b5187
commit c4d4af5bc6
11 changed files with 230 additions and 151 deletions

View File

@@ -31,6 +31,9 @@ import (
func main() {
klogFlags := flag.NewFlagSet("klog", flag.ExitOnError)
klog.InitFlags(klogFlags)
// Opt into fixed stderrthreshold behavior (kubernetes/klog#212).
_ = klogFlags.Set("legacy_stderr_threshold_behavior", "false")
_ = klogFlags.Set("stderrthreshold", "INFO")
klogFlags.VisitAll(func(f *flag.Flag) {
switch f.Name {
case "v", "vmodule", "logtostderr":

2
go.mod
View File

@@ -23,7 +23,7 @@ require (
k8s.io/api v0.35.3
k8s.io/apimachinery v0.35.3
k8s.io/client-go v0.35.3
k8s.io/klog/v2 v2.130.1
k8s.io/klog/v2 v2.140.0
k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2
)

4
go.sum
View File

@@ -1928,8 +1928,8 @@ k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=

2
vendor/k8s.io/klog/v2/README.md generated vendored
View File

@@ -48,8 +48,6 @@ How to use klog
- For more logging conventions (See [Logging Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md))
- See our documentation on [pkg.go.dev/k8s.io](https://pkg.go.dev/k8s.io/klog).
**NOTE**: please use the newer go versions that support semantic import versioning in modules, ideally go 1.11.4 or greater.
### Coexisting with klog/v2
See [this example](examples/coexist_klog_v1_and_v2/) to see how to coexist with both klog/v1 and klog/v2.

View File

@@ -20,7 +20,9 @@ import (
"bytes"
"encoding/json"
"fmt"
"slices"
"strconv"
"strings"
"github.com/go-logr/logr"
)
@@ -51,139 +53,157 @@ func WithValues(oldKV, newKV []interface{}) []interface{} {
return kv
}
// MergeKVs deduplicates elements provided in two key/value slices.
//
// Keys in each slice are expected to be unique, so duplicates can only occur
// when the first and second slice contain the same key. When that happens, the
// key/value pair from the second slice is used. The first slice must be well-formed
// (= even key/value pairs). The second one may have a missing value, in which
// case the special "missing value" is added to the result.
func MergeKVs(first, second []interface{}) []interface{} {
maxLength := len(first) + (len(second)+1)/2*2
if maxLength == 0 {
// Nothing to do at all.
return nil
}
if len(first) == 0 && len(second)%2 == 0 {
// Nothing to be overridden, second slice is well-formed
// and can be used directly.
return second
}
// Determine which keys are in the second slice so that we can skip
// them when iterating over the first one. The code intentionally
// favors performance over completeness: we assume that keys are string
// constants and thus compare equal when the string values are equal. A
// string constant being overridden by, for example, a fmt.Stringer is
// not handled.
overrides := map[interface{}]bool{}
for i := 0; i < len(second); i += 2 {
overrides[second[i]] = true
}
merged := make([]interface{}, 0, maxLength)
for i := 0; i+1 < len(first); i += 2 {
key := first[i]
if overrides[key] {
continue
}
merged = append(merged, key, first[i+1])
}
merged = append(merged, second...)
if len(merged)%2 != 0 {
merged = append(merged, missingValue)
}
return merged
}
type Formatter struct {
AnyToStringHook AnyToStringFunc
}
type AnyToStringFunc func(v interface{}) string
// MergeKVsInto is a variant of MergeKVs which directly formats the key/value
// pairs into a buffer.
func (f Formatter) MergeAndFormatKVs(b *bytes.Buffer, first, second []interface{}) {
if len(first) == 0 && len(second) == 0 {
// Nothing to do at all.
return
}
if len(first) == 0 && len(second)%2 == 0 {
// Nothing to be overridden, second slice is well-formed
// and can be used directly.
for i := 0; i < len(second); i += 2 {
f.KVFormat(b, second[i], second[i+1])
}
return
}
// Determine which keys are in the second slice so that we can skip
// them when iterating over the first one. The code intentionally
// favors performance over completeness: we assume that keys are string
// constants and thus compare equal when the string values are equal. A
// string constant being overridden by, for example, a fmt.Stringer is
// not handled.
overrides := map[interface{}]bool{}
for i := 0; i < len(second); i += 2 {
overrides[second[i]] = true
}
for i := 0; i < len(first); i += 2 {
key := first[i]
if overrides[key] {
continue
}
f.KVFormat(b, key, first[i+1])
}
// Round down.
l := len(second)
l = l / 2 * 2
for i := 1; i < l; i += 2 {
f.KVFormat(b, second[i-1], second[i])
}
if len(second)%2 == 1 {
f.KVFormat(b, second[len(second)-1], missingValue)
}
}
func MergeAndFormatKVs(b *bytes.Buffer, first, second []interface{}) {
Formatter{}.MergeAndFormatKVs(b, first, second)
}
const missingValue = "(MISSING)"
// KVListFormat serializes all key/value pairs into the provided buffer.
// A space gets inserted before the first pair and between each pair.
func (f Formatter) KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) {
for i := 0; i < len(keysAndValues); i += 2 {
var v interface{}
k := keysAndValues[i]
if i+1 < len(keysAndValues) {
v = keysAndValues[i+1]
} else {
v = missingValue
func FormatKVs(b *bytes.Buffer, kvs ...[]interface{}) {
Formatter{}.FormatKVs(b, kvs...)
}
// FormatKVs formats all key/value pairs such that the output contains no
// duplicates ("last one wins").
func (f Formatter) FormatKVs(b *bytes.Buffer, kvs ...[]interface{}) {
// De-duplication is done by optimistically formatting all key value
// pairs and then cutting out the output of those key/value pairs which
// got overwritten later.
//
// In the common case of no duplicates, the only overhead is tracking
// previous keys. This uses a slice with a simple linear search because
// the number of entries is typically so low that allocating a map or
// keeping a sorted slice with binary search aren't justified.
//
// Using a fixed size here makes the Go compiler use the stack as
// initial backing store for the slice, which is crucial for
// performance.
existing := make([]obsoleteKV, 0, 32)
obsolete := make([]interval, 0, 32) // Sorted by start index.
for _, keysAndValues := range kvs {
for i := 0; i < len(keysAndValues); i += 2 {
var v interface{}
k := keysAndValues[i]
if i+1 < len(keysAndValues) {
v = keysAndValues[i+1]
} else {
v = missingValue
}
var e obsoleteKV
e.start = b.Len()
e.key = f.KVFormat(b, k, v)
e.end = b.Len()
i := findObsoleteEntry(existing, e.key)
if i >= 0 {
data := b.Bytes()
if bytes.Compare(data[existing[i].start:existing[i].end], data[e.start:e.end]) == 0 {
// The new entry gets obsoleted because it's identical.
// This has the advantage that key/value pairs from
// a WithValues call always come first, even if the same
// pair gets added again later. This makes different log
// entries more consistent.
//
// The new entry has a higher start index and thus can be appended.
obsolete = append(obsolete, e.interval)
} else {
// The old entry gets obsoleted because it's value is different.
//
// Sort order is not guaranteed, we have to insert at the right place.
index, _ := slices.BinarySearchFunc(obsolete, existing[i].interval, func(a, b interval) int { return a.start - b.start })
obsolete = slices.Insert(obsolete, index, existing[i].interval)
existing[i].interval = e.interval
}
} else {
// Instead of appending at the end and doing a
// linear search in findEntry, we could keep
// the slice sorted by key and do a binary search.
//
// Above:
// i, ok := slices.BinarySearchFunc(existing, e, func(a, b entry) int { return strings.Compare(a.key, b.key) })
// Here:
// existing = slices.Insert(existing, i, e)
//
// But that adds a dependency on the slices package
// and made performance slightly worse, presumably
// because the cost of shifting entries around
// did not pay of with faster lookups.
existing = append(existing, e)
}
}
f.KVFormat(b, k, v)
}
// If we need to remove some obsolete key/value pairs then move the memory.
if len(obsolete) > 0 {
// Potentially the next remaining output (might itself be obsolete).
from := obsolete[0].end
// Next obsolete entry.
nextObsolete := 1
// This is the source buffer, before truncation.
all := b.Bytes()
b.Truncate(obsolete[0].start)
for nextObsolete < len(obsolete) {
if from == obsolete[nextObsolete].start {
// Skip also the next obsolete key/value.
from = obsolete[nextObsolete].end
nextObsolete++
continue
}
// Preserve some output. Write uses copy, which
// explicitly allows source and destination to overlap.
// That could happen here.
valid := all[from:obsolete[nextObsolete].start]
b.Write(valid)
from = obsolete[nextObsolete].end
nextObsolete++
}
// Copy end of buffer.
valid := all[from:]
b.Write(valid)
}
}
func KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) {
Formatter{}.KVListFormat(b, keysAndValues...)
type obsoleteKV struct {
key string
interval
}
func KVFormat(b *bytes.Buffer, k, v interface{}) {
Formatter{}.KVFormat(b, k, v)
// interval includes the start and excludes the end.
type interval struct {
start int
end int
}
func findObsoleteEntry(entries []obsoleteKV, key string) int {
for i, entry := range entries {
if entry.key == key {
return i
}
}
return -1
}
// formatAny is the fallback formatter for a value. It supports a hook (for
// example, for YAML encoding) and itself uses JSON encoding.
func (f Formatter) formatAny(b *bytes.Buffer, v interface{}) {
b.WriteRune('=')
if f.AnyToStringHook != nil {
b.WriteString(f.AnyToStringHook(v))
str := f.AnyToStringHook(v)
if strings.Contains(str, "\n") {
// If it's multi-line, then pass it through writeStringValue to get start/end delimiters,
// which separates it better from any following key/value pair.
writeStringValue(b, str)
return
}
// Otherwise put it directly after the separator, on the same lime,
// The assumption is that the hook returns something where start/end are obvious.
b.WriteRune('=')
b.WriteString(str)
return
}
b.WriteRune('=')
formatAsJSON(b, v)
}

View File

@@ -28,7 +28,7 @@ import (
// KVFormat serializes one key/value pair into the provided buffer.
// A space gets inserted before the pair.
func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) string {
// This is the version without slog support. Must be kept in sync with
// the version in keyvalues_slog.go.
@@ -37,13 +37,15 @@ func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments
// for the sake of performance. Keys with spaces,
// special characters, etc. will break parsing.
var key string
if sK, ok := k.(string); ok {
// Avoid one allocation when the key is a string, which
// normally it should be.
b.WriteString(sK)
key = sK
} else {
b.WriteString(fmt.Sprintf("%s", k))
key = fmt.Sprintf("%s", k)
}
b.WriteString(key)
// The type checks are sorted so that more frequently used ones
// come first because that is then faster in the common
@@ -94,4 +96,6 @@ func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
default:
f.formatAny(b, v)
}
return key
}

View File

@@ -29,8 +29,8 @@ import (
)
// KVFormat serializes one key/value pair into the provided buffer.
// A space gets inserted before the pair.
func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
// A space gets inserted before the pair. It returns the key.
func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) string {
// This is the version without slog support. Must be kept in sync with
// the version in keyvalues_slog.go.
@@ -39,13 +39,15 @@ func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments
// for the sake of performance. Keys with spaces,
// special characters, etc. will break parsing.
var key string
if sK, ok := k.(string); ok {
// Avoid one allocation when the key is a string, which
// normally it should be.
b.WriteString(sK)
key = sK
} else {
b.WriteString(fmt.Sprintf("%s", k))
key = fmt.Sprintf("%s", k)
}
b.WriteString(key)
// The type checks are sorted so that more frequently used ones
// come first because that is then faster in the common
@@ -112,6 +114,8 @@ func (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) {
default:
f.formatAny(b, v)
}
return key
}
// generateJSON has the same preference for plain strings as KVFormat.

87
vendor/k8s.io/klog/v2/klog.go generated vendored
View File

@@ -58,15 +58,30 @@
//
// -logtostderr=true
// Logs are written to standard error instead of to files.
// This shortcuts most of the usual output routing:
// -alsologtostderr, -stderrthreshold and -log_dir have no
// effect and output redirection at runtime with SetOutput is
// ignored.
// By default, all logs are written regardless of severity
// (legacy behavior). To filter logs by severity when
// -logtostderr=true, set -legacy_stderr_threshold_behavior=false
// and use -stderrthreshold.
// With -legacy_stderr_threshold_behavior=true,
// -stderrthreshold has no effect.
//
// The following flags always have no effect:
// -alsologtostderr, -alsologtostderrthreshold, and -log_dir.
// Output redirection at runtime with SetOutput is also ignored.
// -alsologtostderr=false
// Logs are written to standard error as well as to files.
// -alsologtostderrthreshold=INFO
// Log events at or above this severity are logged to standard
// error when -alsologtostderr=true (no effect when -logtostderr=true).
// Default is INFO to maintain backward compatibility.
// -stderrthreshold=ERROR
// Log events at or above this severity are logged to standard
// error as well as to files.
// error as well as to files. When -logtostderr=true, this flag
// has no effect unless -legacy_stderr_threshold_behavior=false.
// -legacy_stderr_threshold_behavior=true
// If true, -stderrthreshold is ignored when -logtostderr=true
// (legacy behavior). If false, -stderrthreshold is honored even
// when -logtostderr=true, allowing severity-based filtering.
// -log_dir=""
// Log files will be written to this directory instead of the
// default temporary directory.
@@ -156,7 +171,7 @@ func (s *severityValue) Set(value string) error {
}
threshold = severity.Severity(v)
}
logging.stderrThreshold.set(threshold)
s.set(threshold)
return nil
}
@@ -409,6 +424,15 @@ var commandLine flag.FlagSet
// init sets up the defaults and creates command line flags.
func init() {
// Initialize severity thresholds
logging.stderrThreshold = severityValue{
Severity: severity.ErrorLog, // Default stderrThreshold is ERROR.
}
logging.alsologtostderrthreshold = severityValue{
Severity: severity.InfoLog, // Default alsologtostderrthreshold is INFO (to maintain backward compatibility).
}
logging.setVState(0, nil, false)
commandLine.StringVar(&logging.logDir, "log_dir", "", "If non-empty, write log files in this directory (no effect when -logtostderr=true)")
commandLine.StringVar(&logging.logFile, "log_file", "", "If non-empty, use this log file (no effect when -logtostderr=true)")
commandLine.Uint64Var(&logging.logFileMaxSizeMB, "log_file_max_size", 1800,
@@ -416,16 +440,14 @@ func init() {
"If the value is 0, the maximum file size is unlimited.")
commandLine.BoolVar(&logging.toStderr, "logtostderr", true, "log to standard error instead of files")
commandLine.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files (no effect when -logtostderr=true)")
logging.setVState(0, nil, false)
commandLine.BoolVar(&logging.legacyStderrThresholdBehavior, "legacy_stderr_threshold_behavior", true, "If true, stderrthreshold is ignored when logtostderr=true (legacy behavior). If false, stderrthreshold is honored even when logtostderr=true")
commandLine.Var(&logging.verbosity, "v", "number for the log level verbosity")
commandLine.BoolVar(&logging.addDirHeader, "add_dir_header", false, "If true, adds the file directory to the header of the log messages")
commandLine.BoolVar(&logging.skipHeaders, "skip_headers", false, "If true, avoid header prefixes in the log messages")
commandLine.BoolVar(&logging.oneOutput, "one_output", false, "If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true)")
commandLine.BoolVar(&logging.skipLogHeaders, "skip_log_headers", false, "If true, avoid headers when opening log files (no effect when -logtostderr=true)")
logging.stderrThreshold = severityValue{
Severity: severity.ErrorLog, // Default stderrThreshold is ERROR.
}
commandLine.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=true)")
commandLine.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=true unless -legacy_stderr_threshold_behavior=false)")
commandLine.Var(&logging.alsologtostderrthreshold, "alsologtostderrthreshold", "logs at or above this threshold go to stderr when -alsologtostderr=true (no effect when -logtostderr=true)")
commandLine.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging")
commandLine.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace")
@@ -470,11 +492,13 @@ type settings struct {
// Boolean flags. Not handled atomically because the flag.Value interface
// does not let us avoid the =true, and that shorthand is necessary for
// compatibility. TODO: does this matter enough to fix? Seems unlikely.
toStderr bool // The -logtostderr flag.
alsoToStderr bool // The -alsologtostderr flag.
toStderr bool // The -logtostderr flag.
alsoToStderr bool // The -alsologtostderr flag.
legacyStderrThresholdBehavior bool // The -legacy_stderr_threshold_behavior flag.
// Level flag. Handled atomically.
stderrThreshold severityValue // The -stderrthreshold flag.
stderrThreshold severityValue // The -stderrthreshold flag.
alsologtostderrthreshold severityValue // The -alsologtostderrthreshold flag.
// Access to all of the following fields must be protected via a mutex.
@@ -809,16 +833,21 @@ func (l *loggingT) infoS(logger *logWriter, filter LogFilter, depth int, msg str
// printS is called from infoS and errorS if logger is not specified.
// set log severity by s
func (l *loggingT) printS(err error, s severity.Severity, depth int, msg string, keysAndValues ...interface{}) {
// Only create a new buffer if we don't have one cached.
b := buffer.GetBuffer()
// The message is always quoted, even if it contains line breaks.
// If developers want multi-line output, they should use a small, fixed
// message and put the multi-line output into a value.
b.WriteString(strconv.Quote(msg))
qMsg := make([]byte, 0, 1024)
qMsg = strconv.AppendQuote(qMsg, msg)
// Only create a new buffer if we don't have one cached.
b := buffer.GetBuffer()
b.Write(qMsg)
var errKV []interface{}
if err != nil {
serialize.KVListFormat(&b.Buffer, "err", err)
errKV = []interface{}{"err", err}
}
serialize.KVListFormat(&b.Buffer, keysAndValues...)
serialize.FormatKVs(&b.Buffer, errKV, keysAndValues)
l.printDepth(s, nil, nil, depth+1, &b.Buffer)
// Make the buffer available for reuse.
buffer.PutBuffer(b)
@@ -885,9 +914,25 @@ func (l *loggingT) output(s severity.Severity, logger *logWriter, buf *buffer.Bu
}
}
} else if l.toStderr {
os.Stderr.Write(data)
// When logging to stderr only, check if we should filter by severity.
// This is controlled by the legacy_stderr_threshold_behavior flag.
if l.legacyStderrThresholdBehavior {
// Legacy behavior: always write to stderr, ignore stderrthreshold
os.Stderr.Write(data)
} else {
// New behavior: honor stderrthreshold even when logtostderr=true
if s >= l.stderrThreshold.get() {
os.Stderr.Write(data)
}
}
} else {
if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() {
// Write to stderr if any of these conditions are met:
// - alsoToStderr is set (legacy behavior)
// - alsologtostderr is set and severity meets alsologtostderrthreshold
// - alsologtostderr is not set and severity meets stderrThreshold
if alsoToStderr ||
(l.alsoToStderr && s >= l.alsologtostderrthreshold.get()) ||
(!l.alsoToStderr && s >= l.stderrThreshold.get()) {
os.Stderr.Write(data)
}

4
vendor/k8s.io/klog/v2/klogr.go generated vendored
View File

@@ -53,7 +53,7 @@ func (l *klogger) Init(info logr.RuntimeInfo) {
}
func (l *klogger) Info(level int, msg string, kvList ...interface{}) {
merged := serialize.MergeKVs(l.values, kvList)
merged := serialize.WithValues(l.values, kvList)
// Skip this function.
VDepth(l.callDepth+1, Level(level)).InfoSDepth(l.callDepth+1, msg, merged...)
}
@@ -63,7 +63,7 @@ func (l *klogger) Enabled(level int) bool {
}
func (l *klogger) Error(err error, msg string, kvList ...interface{}) {
merged := serialize.MergeKVs(l.values, kvList)
merged := serialize.WithValues(l.values, kvList)
ErrorSDepth(l.callDepth+1, err, msg, merged...)
}

11
vendor/k8s.io/klog/v2/klogr_slog.go generated vendored
View File

@@ -63,12 +63,17 @@ func slogOutput(file string, line int, now time.Time, err error, s severity.Seve
}
// See printS.
qMsg := make([]byte, 0, 1024)
qMsg = strconv.AppendQuote(qMsg, msg)
b := buffer.GetBuffer()
b.WriteString(strconv.Quote(msg))
b.Write(qMsg)
var errKV []interface{}
if err != nil {
serialize.KVListFormat(&b.Buffer, "err", err)
errKV = []interface{}{"err", err}
}
serialize.KVListFormat(&b.Buffer, kvList...)
serialize.FormatKVs(&b.Buffer, errKV, kvList)
// See print + header.
buf := logging.formatHeader(s, file, line, now)

4
vendor/modules.txt vendored
View File

@@ -860,8 +860,8 @@ k8s.io/client-go/util/homedir
k8s.io/client-go/util/keyutil
k8s.io/client-go/util/retry
k8s.io/client-go/util/workqueue
# k8s.io/klog/v2 v2.130.1
## explicit; go 1.18
# k8s.io/klog/v2 v2.140.0
## explicit; go 1.21
k8s.io/klog/v2
k8s.io/klog/v2/internal/buffer
k8s.io/klog/v2/internal/clock