Repository: kubernetes/klog
Pull request #380 (2023-06-01 16:39:02)

golangci-lint action
**What this PR does / why we need it**:

This fixes several linting issues as found by golangci-lint v1.52.2 with default linters and revive enabled, then changes the golangci-lint invocation so that it runs with more linters, covers the examples, and posts issues as annotations.

**Special notes for your reviewer**:

See individual commit messages for the issues as reported by golangci-lint.

**Release note**:
```release-note
NONE
```


Patch:
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..b2582a7
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,24 @@
+name: Run lint
+
+on: [ push, pull_request ]
+
+permissions:
+  contents: read
+
+jobs:
+  lint:
+    strategy:
+      matrix:
+        path:
+          - .
+          - examples
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+      - name: Lint
+        uses: golangci/golangci-lint-action@v2
+        with:
+          # version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
+          version: latest
+          working-directory: ${{ matrix.path }}
diff --git a/.golangci.yaml b/.golangci.yaml
new file mode 100644
index 0000000..0d77d65
--- /dev/null
+++ b/.golangci.yaml
@@ -0,0 +1,6 @@
+linters:
+  disable-all: true
+  enable: # sorted alphabetical
+    - gofmt
+    - misspell
+    - revive
diff --git a/examples/coexist_glog/coexist_glog.go b/examples/coexist_glog/coexist_glog.go
index 960a9b9..9f3fd05 100644
--- a/examples/coexist_glog/coexist_glog.go
+++ b/examples/coexist_glog/coexist_glog.go
@@ -4,11 +4,12 @@ import (
 	"flag"
 
 	"github.com/golang/glog"
+	"k8s.io/klog/examples/util/require"
 	"k8s.io/klog/v2"
 )
 
 func main() {
-	flag.Set("alsologtostderr", "true")
+	require.NoError(flag.Set("alsologtostderr", "true"))
 	flag.Parse()
 
 	klogFlags := flag.NewFlagSet("klog", flag.ExitOnError)
@@ -19,7 +20,7 @@ func main() {
 		f2 := klogFlags.Lookup(f1.Name)
 		if f2 != nil {
 			value := f1.Value.String()
-			f2.Value.Set(value)
+			require.NoError(f2.Value.Set(value))
 		}
 	})
 
diff --git a/examples/go.mod b/examples/go.mod
index 323dd64..4fee663 100644
--- a/examples/go.mod
+++ b/examples/go.mod
@@ -1,4 +1,4 @@
-module example
+module k8s.io/klog/examples
 
 go 1.13
 
@@ -8,7 +8,6 @@ require (
 	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
 	go.uber.org/goleak v1.1.12
 	go.uber.org/zap v1.19.0
-	golang.org/x/tools v0.1.5
 	k8s.io/klog/v2 v2.30.0
 )
 
diff --git a/examples/klogr/main.go b/examples/klogr/main.go
index 5de2b5f..db5539a 100644
--- a/examples/klogr/main.go
+++ b/examples/klogr/main.go
@@ -3,6 +3,7 @@ package main
 import (
 	"flag"
 
+	"k8s.io/klog/examples/util/require"
 	"k8s.io/klog/v2"
 	"k8s.io/klog/v2/klogr"
 )
@@ -17,7 +18,7 @@ func (e myError) Error() string {
 
 func main() {
 	klog.InitFlags(nil)
-	flag.Set("v", "3")
+	require.NoError(flag.Set("v", "3"))
 	flag.Parse()
 	log := klogr.New().WithName("MyName").WithValues("user", "you")
 	log.Info("hello", "val1", 1, "val2", map[string]int{"k": 1})
diff --git a/examples/log_file/usage_log_file.go b/examples/log_file/usage_log_file.go
index bff48e0..4badabc 100644
--- a/examples/log_file/usage_log_file.go
+++ b/examples/log_file/usage_log_file.go
@@ -3,6 +3,7 @@ package main
 import (
 	"flag"
 
+	"k8s.io/klog/examples/util/require"
 	"k8s.io/klog/v2"
 )
 
@@ -10,8 +11,8 @@ func main() {
 	klog.InitFlags(nil)
 	// By default klog writes to stderr. Setting logtostderr to false makes klog
 	// write to a log file.
-	flag.Set("logtostderr", "false")
-	flag.Set("log_file", "myfile.log")
+	require.NoError(flag.Set("logtostderr", "false"))
+	require.NoError(flag.Set("log_file", "myfile.log"))
 	flag.Parse()
 	klog.Info("nice to meet you")
 	klog.Flush()
diff --git a/examples/set_output/usage_set_output.go b/examples/set_output/usage_set_output.go
index 3cdd388..99ad8d0 100644
--- a/examples/set_output/usage_set_output.go
+++ b/examples/set_output/usage_set_output.go
@@ -4,13 +4,15 @@ import (
 	"bytes"
 	"flag"
 	"fmt"
+
+	"k8s.io/klog/examples/util/require"
 	"k8s.io/klog/v2"
 )
 
 func main() {
 	klog.InitFlags(nil)
-	flag.Set("logtostderr", "false")
-	flag.Set("alsologtostderr", "false")
+	require.NoError(flag.Set("logtostderr", "false"))
+	require.NoError(flag.Set("alsologtostderr", "false"))
 	flag.Parse()
 
 	buf := new(bytes.Buffer)
diff --git a/examples/structured_logging/structured_logging.go b/examples/structured_logging/structured_logging.go
index 6e80bd9..b2ae834 100644
--- a/examples/structured_logging/structured_logging.go
+++ b/examples/structured_logging/structured_logging.go
@@ -25,8 +25,9 @@ func main() {
 	flag.Parse()
 
 	someData := MyStruct{
-		Name: "hello",
-		Data: "world",
+		Name:     "hello",
+		Data:     "world",
+		internal: 42,
 	}
 
 	longData := MyStruct{
diff --git a/examples/util/require/require.go b/examples/util/require/require.go
new file mode 100644
index 0000000..0c45855
--- /dev/null
+++ b/examples/util/require/require.go
@@ -0,0 +1,7 @@
+package require
+
+func NoError(err error) {
+	if err != nil {
+		panic(err)
+	}
+}
diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go
index f325ded..475e1bf 100644
--- a/internal/buffer/buffer.go
+++ b/internal/buffer/buffer.go
@@ -36,8 +36,7 @@ var (
 // use. It also provides some helper methods for output formatting.
 type Buffer struct {
 	bytes.Buffer
-	Tmp  [64]byte // temporary byte array for creating headers.
-	next *Buffer
+	Tmp [64]byte // temporary byte array for creating headers.
 }
 
 var buffers = sync.Pool{
diff --git a/internal/clock/clock.go b/internal/clock/clock.go
index b8b6af5..cc11bb4 100644
--- a/internal/clock/clock.go
+++ b/internal/clock/clock.go
@@ -39,16 +39,6 @@ type Clock interface {
 	// Sleep sleeps for the provided duration d.
 	// Consider making the sleep interruptible by using 'select' on a context channel and a timer channel.
 	Sleep(d time.Duration)
-	// Tick returns the channel of a new Ticker.
-	// This method does not allow to free/GC the backing ticker. Use
-	// NewTicker from WithTicker instead.
-	Tick(d time.Duration) <-chan time.Time
-}
-
-// WithTicker allows for injecting fake or real clocks into code that
-// needs to do arbitrary things based on time.
-type WithTicker interface {
-	Clock
 	// NewTicker returns a new Ticker.
 	NewTicker(time.Duration) Ticker
 }
@@ -66,7 +56,7 @@ type WithDelayedExecution interface {
 // WithTickerAndDelayedExecution allows for injecting fake or real clocks
 // into code that needs Ticker and AfterFunc functionality
 type WithTickerAndDelayedExecution interface {
-	WithTicker
+	Clock
 	// AfterFunc executes f in its own goroutine after waiting
 	// for d duration and returns a Timer whose channel can be
 	// closed by calling Stop() on the Timer.
@@ -79,7 +69,7 @@ type Ticker interface {
 	Stop()
 }
 
-var _ = WithTicker(RealClock{})
+var _ Clock = RealClock{}
 
 // RealClock really calls time.Now()
 type RealClock struct{}
@@ -115,13 +105,6 @@ func (RealClock) AfterFunc(d time.Duration, f func()) Timer {
 	}
 }
 
-// Tick is the same as time.Tick(d)
-// This method does not allow to free/GC the backing ticker. Use
-// NewTicker instead.
-func (RealClock) Tick(d time.Duration) <-chan time.Time {
-	return time.Tick(d)
-}
-
 // NewTicker returns a new Ticker.
 func (RealClock) NewTicker(d time.Duration) Ticker {
 	return &realTicker{
diff --git a/internal/verbosity/verbosity.go b/internal/verbosity/verbosity.go
index 309cdfa..40ec27d 100644
--- a/internal/verbosity/verbosity.go
+++ b/internal/verbosity/verbosity.go
@@ -288,9 +288,7 @@ func (vs *VState) setV(pc uintptr) Level {
 	fn := runtime.FuncForPC(pc)
 	file, _ := fn.FileLine(pc)
 	// The file is something like /a/b/c/d.go. We want just the d.
-	if strings.HasSuffix(file, ".go") {
-		file = file[:len(file)-3]
-	}
+	file = strings.TrimSuffix(file, ".go")
 	if slash := strings.LastIndex(file, "/"); slash >= 0 {
 		file = file[slash+1:]
 	}
diff --git a/klog.go b/klog.go
index f42d8c9..e50b1f2 100644
--- a/klog.go
+++ b/klog.go
@@ -415,7 +415,7 @@ func init() {
 	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=false)")
 	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")
 
@@ -518,9 +518,7 @@ type settings struct {
 func (s settings) deepCopy() settings {
 	// vmodule is a slice and would be shared, so we have copy it.
 	filter := make([]modulePat, len(s.vmodule.filter))
-	for i := range s.vmodule.filter {
-		filter[i] = s.vmodule.filter[i]
-	}
+	copy(filter, s.vmodule.filter)
 	s.vmodule.filter = filter
 
 	if s.logger != nil {
@@ -676,10 +674,6 @@ func (l *loggingT) println(s severity.Severity, logger *logWriter, filter LogFil
 }
 
 func (l *loggingT) printlnDepth(s severity.Severity, logger *logWriter, filter LogFilter, depth int, args ...interface{}) {
-	if false {
-		_ = fmt.Sprintln(args...) // cause vet to treat this function like fmt.Println
-	}
-
 	buf, file, line := l.header(s, depth)
 	// If a logger is set and doesn't support writing a formatted buffer,
 	// we clear the generated header as we rely on the backing
@@ -700,10 +694,6 @@ func (l *loggingT) print(s severity.Severity, logger *logWriter, filter LogFilte
 }
 
 func (l *loggingT) printDepth(s severity.Severity, logger *logWriter, filter LogFilter, depth int, args ...interface{}) {
-	if false {
-		_ = fmt.Sprint(args...) //  // cause vet to treat this function like fmt.Print
-	}
-
 	buf, file, line := l.header(s, depth)
 	// If a logger is set and doesn't support writing a formatted buffer,
 	// we clear the generated header as we rely on the backing
@@ -727,10 +717,6 @@ func (l *loggingT) printf(s severity.Severity, logger *logWriter, filter LogFilt
 }
 
 func (l *loggingT) printfDepth(s severity.Severity, logger *logWriter, filter LogFilter, depth int, format string, args ...interface{}) {
-	if false {
-		_ = fmt.Sprintf(format, args...) // cause vet to treat this function like fmt.Printf
-	}
-
 	buf, file, line := l.header(s, depth)
 	// If a logger is set and doesn't support writing a formatted buffer,
 	// we clear the generated header as we rely on the backing
@@ -912,7 +898,7 @@ func (l *loggingT) output(s severity.Severity, logger *logWriter, buf *buffer.Bu
 					l.exit(err)
 				}
 			}
-			l.file[severity.InfoLog].Write(data)
+			_, _ = l.file[severity.InfoLog].Write(data)
 		} else {
 			if l.file[s] == nil {
 				if err := l.createFiles(s); err != nil {
@@ -922,20 +908,20 @@ func (l *loggingT) output(s severity.Severity, logger *logWriter, buf *buffer.Bu
 			}
 
 			if l.oneOutput {
-				l.file[s].Write(data)
+				_, _ = l.file[s].Write(data)
 			} else {
 				switch s {
 				case severity.FatalLog:
-					l.file[severity.FatalLog].Write(data)
+					_, _ = l.file[severity.FatalLog].Write(data)
 					fallthrough
 				case severity.ErrorLog:
-					l.file[severity.ErrorLog].Write(data)
+					_, _ = l.file[severity.ErrorLog].Write(data)
 					fallthrough
 				case severity.WarningLog:
-					l.file[severity.WarningLog].Write(data)
+					_, _ = l.file[severity.WarningLog].Write(data)
 					fallthrough
 				case severity.InfoLog:
-					l.file[severity.InfoLog].Write(data)
+					_, _ = l.file[severity.InfoLog].Write(data)
 				}
 			}
 		}
@@ -961,7 +947,7 @@ func (l *loggingT) output(s severity.Severity, logger *logWriter, buf *buffer.Bu
 		logExitFunc = func(error) {} // If we get a write error, we'll still exit below.
 		for log := severity.FatalLog; log >= severity.InfoLog; log-- {
 			if f := l.file[log]; f != nil { // Can be nil if -logtostderr is set.
-				f.Write(trace)
+				_, _ = f.Write(trace)
 			}
 		}
 		l.mu.Unlock()
@@ -1117,7 +1103,7 @@ const flushInterval = 5 * time.Second
 // flushDaemon periodically flushes the log file buffers.
 type flushDaemon struct {
 	mu       sync.Mutex
-	clock    clock.WithTicker
+	clock    clock.Clock
 	flush    func()
 	stopC    chan struct{}
 	stopDone chan struct{}
@@ -1125,7 +1111,7 @@ type flushDaemon struct {
 
 // newFlushDaemon returns a new flushDaemon. If the passed clock is nil, a
 // clock.RealClock is used.
-func newFlushDaemon(flush func(), tickClock clock.WithTicker) *flushDaemon {
+func newFlushDaemon(flush func(), tickClock clock.Clock) *flushDaemon {
 	if tickClock == nil {
 		tickClock = clock.RealClock{}
 	}
@@ -1216,8 +1202,8 @@ func (l *loggingT) flushAll() {
 	for s := severity.FatalLog; s >= severity.InfoLog; s-- {
 		file := l.file[s]
 		if file != nil {
-			file.Flush() // ignore error
-			file.Sync()  // ignore error
+			_ = file.Flush() // ignore error
+			_ = file.Sync()  // ignore error
 		}
 	}
 	if logging.loggerOptions.flush != nil {
@@ -1296,9 +1282,7 @@ func (l *loggingT) setV(pc uintptr) Level {
 	fn := runtime.FuncForPC(pc)
 	file, _ := fn.FileLine(pc)
 	// The file is something like /a/b/c/d.go. We want just the d.
-	if strings.HasSuffix(file, ".go") {
-		file = file[:len(file)-3]
-	}
+	file = strings.TrimSuffix(file, ".go")
 	if slash := strings.LastIndex(file, "/"); slash >= 0 {
 		file = file[slash+1:]
 	}
diff --git a/klog_file.go b/klog_file.go
index 1025d64..8bee162 100644
--- a/klog_file.go
+++ b/klog_file.go
@@ -109,8 +109,8 @@ func create(tag string, t time.Time, startup bool) (f *os.File, filename string,
 		f, err := openOrCreate(fname, startup)
 		if err == nil {
 			symlink := filepath.Join(dir, link)
-			os.Remove(symlink)        // ignore err
-			os.Symlink(name, symlink) // ignore err
+			_ = os.Remove(symlink)        // ignore err
+			_ = os.Symlink(name, symlink) // ignore err
 			return f, fname, nil
 		}
 		lastErr = err
diff --git a/klogr.go b/klogr.go
index 15de00e..784f7de 100644
--- a/klogr.go
+++ b/klogr.go
@@ -32,7 +32,6 @@ func NewKlogr() Logger {
 // klogger is a subset of klogr/klogr.go. It had to be copied to break an
 // import cycle (klogr wants to use klog, and klog wants to use klogr).
 type klogger struct {
-	level     int
 	callDepth int
 	prefix    string
 	values    []interface{}
diff --git a/klogr/klogr.go b/klogr/klogr.go
index db5b5c7..d80e44f 100644
--- a/klogr/klogr.go
+++ b/klogr/klogr.go
@@ -115,8 +115,10 @@ func pretty(value interface{}) string {
 	buffer := &bytes.Buffer{}
 	encoder := json.NewEncoder(buffer)
 	encoder.SetEscapeHTML(false)
-	encoder.Encode(value)
-	return strings.TrimSpace(string(buffer.Bytes()))
+	if err := encoder.Encode(value); err != nil {
+		return fmt.Sprintf("<<error: %v>>", err)
+	}
+	return strings.TrimSpace(buffer.String())
 }
 
 func (l *klogger) Info(level int, msg string, kvList ...interface{}) {
diff --git a/textlogger/options.go b/textlogger/options.go
index 14cfa80..83eeef4 100644
--- a/textlogger/options.go
+++ b/textlogger/options.go
@@ -30,6 +30,11 @@ import (
 // bind command line flags to the instance before passing it to NewTestContext.
 //
 // Must be constructed with NewConfig.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type Config struct {
 	vstate *verbosity.VState
 	co     configOptions
@@ -50,6 +55,11 @@ func (c *Config) VModule() flag.Value {
 }
 
 // ConfigOption implements functional parameters for NewConfig.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type ConfigOption func(co *configOptions)
 
 type configOptions struct {
@@ -69,6 +79,11 @@ func VerbosityFlagName(name string) ConfigOption {
 
 // VModulFlagName overrides the default -vmodule for the per-module
 // verbosity levels.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func VModuleFlagName(name string) ConfigOption {
 	return func(co *configOptions) {
 		co.vmoduleFlagName = name
@@ -78,6 +93,11 @@ func VModuleFlagName(name string) ConfigOption {
 // Verbosity overrides the default verbosity level of 0.
 // See https://github.com/kubernetes/community/blob/9406b4352fe2d5810cb21cc3cb059ce5886de157/contributors/devel/sig-instrumentation/logging.md#logging-conventions
 // for log level conventions in Kubernetes.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func Verbosity(level int) ConfigOption {
 	return func(co *configOptions) {
 		co.verbosityDefault = level
@@ -85,6 +105,11 @@ func Verbosity(level int) ConfigOption {
 }
 
 // Output overrides stderr as the output stream.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func Output(output io.Writer) ConfigOption {
 	return func(co *configOptions) {
 		co.output = output
@@ -93,6 +118,11 @@ func Output(output io.Writer) ConfigOption {
 
 // NewConfig returns a configuration with recommended defaults and optional
 // modifications. Command line flags are not bound to any FlagSet yet.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func NewConfig(opts ...ConfigOption) *Config {
 	c := &Config{
 		vstate: verbosity.New(),
@@ -107,11 +137,17 @@ func NewConfig(opts ...ConfigOption) *Config {
 		opt(&c.co)
 	}
 
-	c.Verbosity().Set(strconv.FormatInt(int64(c.co.verbosityDefault), 10))
+	// Cannot fail for this input.
+	_ = c.Verbosity().Set(strconv.FormatInt(int64(c.co.verbosityDefault), 10))
 	return c
 }
 
 // AddFlags registers the command line flags that control the configuration.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func (c *Config) AddFlags(fs *flag.FlagSet) {
 	fs.Var(c.Verbosity(), c.co.verbosityFlagName, "number for the log level verbosity of the testing logger")
 	fs.Var(c.VModule(), c.co.vmoduleFlagName, "comma-separated list of pattern=N log level settings for files matching the patterns")
diff --git a/textlogger/textlogger.go b/textlogger/textlogger.go
index 90b2650..c0f916f 100644
--- a/textlogger/textlogger.go
+++ b/textlogger/textlogger.go
@@ -17,6 +17,11 @@ limitations under the License.
 
 // Package textlogger contains an implementation of the logr interface
 // which is producing the exact same output as klog.
+//
+// # Experimental
+//
+// Notice: This package is EXPERIMENTAL and may be changed or removed in a
+// later release.
 package textlogger
 
 import (
@@ -35,6 +40,11 @@ import (
 
 var (
 	// TimeNow is used to retrieve the current time. May be changed for testing.
+	//
+	// Experimental
+	//
+	// Notice: This variable is EXPERIMENTAL and may be changed or removed in a
+	// later release.
 	TimeNow = time.Now
 )
 
@@ -42,6 +52,11 @@ var (
 //
 // Verbosity can be modified at any time through the Config.V and
 // Config.VModule API.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release. The behavior of the returned Logger may change.
 func NewLogger(c *Config) logr.Logger {
 	return logr.New(&tlogger{
 		prefix: "",
@@ -73,7 +88,7 @@ func (l *tlogger) Enabled(level int) bool {
 	return l.config.vstate.Enabled(verbosity.Level(level), 2+l.callDepth)
 }
 
-func (l *tlogger) Info(level int, msg string, kvList ...interface{}) {
+func (l *tlogger) Info(_ int, msg string, kvList ...interface{}) {
 	l.print(nil, severity.InfoLog, msg, kvList)
 }
 
@@ -119,29 +134,29 @@ func (l *tlogger) print(err error, s severity.Severity, msg string, kvList []int
 	if b.Len() == 0 || b.Bytes()[b.Len()-1] != '\n' {
 		b.WriteByte('\n')
 	}
-	l.config.co.output.Write(b.Bytes())
+	_, _ = l.config.co.output.Write(b.Bytes())
 }
 
 func (l *tlogger) WriteKlogBuffer(data []byte) {
-	l.config.co.output.Write(data)
+	_, _ = l.config.co.output.Write(data)
 }
 
 // WithName returns a new logr.Logger with the specified name appended.  klogr
 // uses '/' characters to separate name elements.  Callers should not pass '/'
 // in the provided name string, but this library does not actually enforce that.
 func (l *tlogger) WithName(name string) logr.LogSink {
-	new := *l
+	clone := *l
 	if len(l.prefix) > 0 {
-		new.prefix = l.prefix + "/"
+		clone.prefix = l.prefix + "/"
 	}
-	new.prefix += name
-	return &new
+	clone.prefix += name
+	return &clone
 }
 
 func (l *tlogger) WithValues(kvList ...interface{}) logr.LogSink {
-	new := *l
-	new.values = serialize.WithValues(l.values, kvList)
-	return &new
+	clone := *l
+	clone.values = serialize.WithValues(l.values, kvList)
+	return &clone
 }
 
 // KlogBufferWriter is implemented by the textlogger LogSink.


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 48f0aca..fe8a775 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -4,7 +4,7 @@ jobs:
   test:
     strategy:
       matrix:
-        go-version: [1.18, 1.19, 1.20, 1.21]
+        go-version: [1.17, 1.18, 1.19]
         platform: [ubuntu-latest, macos-latest, windows-latest]
     runs-on: ${{ matrix.platform }}
     steps:
@@ -20,18 +20,6 @@ jobs:
         go test -v -race ./...
     - name: Test examples
       run: cd examples && go test -v -race ./...
-  lint:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Install Go
-        uses: actions/setup-go@v1
-      - name: Checkout code
-        uses: actions/checkout@v2
-      - name: Lint
-        run: |
-          docker run --rm -v `pwd`:/go/src/k8s.io/klog -w /go/src/k8s.io/klog \
-            golangci/golangci-lint:v1.50.1 golangci-lint run --disable-all -v \
-            -E govet -E misspell -E gofmt -E ineffassign -E golint
   apidiff:
     runs-on: ubuntu-latest
     if: github.base_ref
diff --git a/examples/benchmarks/benchmarks_test.go b/examples/benchmarks/benchmarks_test.go
index a343efe..ecb57ca 100644
--- a/examples/benchmarks/benchmarks_test.go
+++ b/examples/benchmarks/benchmarks_test.go
@@ -27,6 +27,7 @@ import (
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
 
+	"k8s.io/klog/examples/util/require"
 	"k8s.io/klog/v2"
 )
 
@@ -38,11 +39,11 @@ func init() {
 	// klog gets configured so that it writes to a single output file that
 	// will be set during tests with SetOutput.
 	klog.InitFlags(nil)
-	flag.Set("v", fmt.Sprintf("%d", verbosityThreshold))
-	flag.Set("log_file", "/dev/null")
-	flag.Set("logtostderr", "false")
-	flag.Set("alsologtostderr", "false")
-	flag.Set("stderrthreshold", "10")
+	require.NoError(flag.Set("v", fmt.Sprintf("%d", verbosityThreshold)))
+	require.NoError(flag.Set("log_file", "/dev/null"))
+	require.NoError(flag.Set("logtostderr", "false"))
+	require.NoError(flag.Set("alsologtostderr", "false"))
+	require.NoError(flag.Set("stderrthreshold", "10"))
 }
 
 type testcase struct {
diff --git a/examples/flushing/flushing_test.go b/examples/flushing/flushing_test.go
index 3d46fbc..18a4a45 100644
--- a/examples/flushing/flushing_test.go
+++ b/examples/flushing/flushing_test.go
@@ -5,6 +5,8 @@ import (
 	"testing"
 
 	"go.uber.org/goleak"
+
+	"k8s.io/klog/examples/util/require"
 	"k8s.io/klog/v2"
 )
 
@@ -13,8 +15,8 @@ func main() {
 
 	// By default klog writes to stderr. Setting logtostderr to false makes klog
 	// write to a log file.
-	flag.Set("logtostderr", "false")
-	flag.Set("log_file", "myfile.log")
+	require.NoError(flag.Set("logtostderr", "false"))
+	require.NoError(flag.Set("log_file", "myfile.log"))
 	flag.Parse()
 
 	// Info writes the first log message. When the first log file is created,
diff --git a/examples/go_vet/go_vet_test.go b/examples/go_vet/go_vet_test.go
deleted file mode 100644
index 62a65c9..0000000
--- a/examples/go_vet/go_vet_test.go
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
-	"os"
-	"os/exec"
-	"path"
-	"testing"
-
-	"golang.org/x/tools/go/analysis/analysistest"
-	"golang.org/x/tools/go/analysis/passes/printf"
-)
-
-// TestGoVet checks that "go vet" detects incorrect klog calls like
-// mismatched format specifiers and arguments.
-func TestGoVet(t *testing.T) {
-	testdata := analysistest.TestData()
-	src := path.Join(testdata, "src")
-	t.Cleanup(func() {
-		os.RemoveAll(src)
-	})
-
-	// analysistest doesn't support using existing code
-	// via modules (https://github.com/golang/go/issues/37054).
-	// Populating the "testdata/src" directory with the
-	// result of "go mod vendor" is a workaround.
-	cmd := exec.Command("go", "mod", "vendor", "-o", src)
-	out, err := cmd.CombinedOutput()
-	if err != nil {
-		t.Fatalf("%s failed: %v\nOutput: %s", cmd, err, string(out))
-	}
-
-	analyzer := printf.Analyzer
-	analysistest.Run(t, testdata, analyzer, "")
-}
diff --git a/examples/go_vet/testdata/calls.go b/examples/go_vet/testdata/calls.go
deleted file mode 100644
index 60d8ab3..0000000
--- a/examples/go_vet/testdata/calls.go
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
-Copyright 2023 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package testdata
-
-import (
-	"k8s.io/klog/v2"
-)
-
-func calls() {
-	klog.Infof("%s") // want `k8s.io/klog/v2.Infof format %s reads arg #1, but call has 0 args`
-	klog.Infof("%s", "world")
-	klog.Info("%s", "world") // want `k8s.io/klog/v2.Info call has possible formatting directive %s`
-	klog.Info("world")
-	klog.Infoln("%s", "world") // want `k8s.io/klog/v2.Infoln call has possible formatting directive %s`
-	klog.Infoln("world")
-
-	klog.InfofDepth(1, "%s") // want `k8s.io/klog/v2.InfofDepth format %s reads arg #1, but call has 0 args`
-	klog.InfofDepth(1, "%s", "world")
-	klog.InfoDepth(1, "%s", "world") // want `k8s.io/klog/v2.InfoDepth call has possible formatting directive %s`
-	klog.InfoDepth(1, "world")
-	klog.InfolnDepth(1, "%s", "world") // want `k8s.io/klog/v2.InfolnDepth call has possible formatting directive %s`
-	klog.InfolnDepth(1, "world")
-
-	klog.Warningf("%s") // want `k8s.io/klog/v2.Warningf format %s reads arg #1, but call has 0 args`
-	klog.Warningf("%s", "world")
-	klog.Warning("%s", "world") // want `k8s.io/klog/v2.Warning call has possible formatting directive %s`
-	klog.Warning("world")
-	klog.Warningln("%s", "world") // want `k8s.io/klog/v2.Warningln call has possible formatting directive %s`
-	klog.Warningln("world")
-
-	klog.WarningfDepth(1, "%s") // want `k8s.io/klog/v2.WarningfDepth format %s reads arg #1, but call has 0 args`
-	klog.WarningfDepth(1, "%s", "world")
-	klog.WarningDepth(1, "%s", "world") // want `k8s.io/klog/v2.WarningDepth call has possible formatting directive %s`
-	klog.WarningDepth(1, "world")
-	klog.WarninglnDepth(1, "%s", "world") // want `k8s.io/klog/v2.WarninglnDepth call has possible formatting directive %s`
-	klog.WarninglnDepth(1, "world")
-
-	klog.Errorf("%s") // want `k8s.io/klog/v2.Errorf format %s reads arg #1, but call has 0 args`
-	klog.Errorf("%s", "world")
-	klog.Error("%s", "world") // want `k8s.io/klog/v2.Error call has possible formatting directive %s`
-	klog.Error("world")
-	klog.Errorln("%s", "world") // want `k8s.io/klog/v2.Errorln call has possible formatting directive %s`
-	klog.Errorln("world")
-
-	klog.ErrorfDepth(1, "%s") // want `k8s.io/klog/v2.ErrorfDepth format %s reads arg #1, but call has 0 args`
-	klog.ErrorfDepth(1, "%s", "world")
-	klog.ErrorDepth(1, "%s", "world") // want `k8s.io/klog/v2.ErrorDepth call has possible formatting directive %s`
-	klog.ErrorDepth(1, "world")
-	klog.ErrorlnDepth(1, "%s", "world") // want `k8s.io/klog/v2.ErrorlnDepth call has possible formatting directive %s`
-	klog.ErrorlnDepth(1, "world")
-
-	klog.Fatalf("%s") // want `k8s.io/klog/v2.Fatalf format %s reads arg #1, but call has 0 args`
-	klog.Fatalf("%s", "world")
-	klog.Fatal("%s", "world") // want `k8s.io/klog/v2.Fatal call has possible formatting directive %s`
-	klog.Fatal("world")
-	klog.Fatalln("%s", "world") // want `k8s.io/klog/v2.Fatalln call has possible formatting directive %s`
-	klog.Fatalln("world")
-
-	klog.FatalfDepth(1, "%s") // want `k8s.io/klog/v2.FatalfDepth format %s reads arg #1, but call has 0 args`
-	klog.FatalfDepth(1, "%s", "world")
-	klog.FatalDepth(1, "%s", "world") // want `k8s.io/klog/v2.FatalDepth call has possible formatting directive %s`
-	klog.FatalDepth(1, "world")
-	klog.FatallnDepth(1, "%s", "world") // want `k8s.io/klog/v2.FatallnDepth call has possible formatting directive %s`
-	klog.FatallnDepth(1, "world")
-
-	klog.V(1).Infof("%s") // want `\(k8s.io/klog/v2.Verbose\).Infof format %s reads arg #1, but call has 0 args`
-	klog.V(1).Infof("%s", "world")
-	klog.V(1).Info("%s", "world") // want `\(k8s.io/klog/v2.Verbose\).Info call has possible formatting directive %s`
-	klog.V(1).Info("world")
-	klog.V(1).Infoln("%s", "world") // want `\(k8s.io/klog/v2.Verbose\).Infoln call has possible formatting directive %s`
-	klog.V(1).Infoln("world")
-
-	klog.V(1).InfofDepth(1, "%s") // want `\(k8s.io/klog/v2.Verbose\).InfofDepth format %s reads arg #1, but call has 0 args`
-	klog.V(1).InfofDepth(1, "%s", "world")
-	klog.V(1).InfoDepth(1, "%s", "world") // want `\(k8s.io/klog/v2.Verbose\).InfoDepth call has possible formatting directive %s`
-	klog.V(1).InfoDepth(1, "world")
-	klog.V(1).InfolnDepth(1, "%s", "world") // want `\(k8s.io/klog/v2.Verbose\).InfolnDepth call has possible formatting directive %s`
-	klog.V(1).InfolnDepth(1, "world")
-
-	// Detecting format specifiers for klog.InfoS and other structured logging calls would be nice,
-	// but doesn't work the same way because of the extra "msg" string parameter. logcheck
-	// can be used instead of "go vet".
-	klog.InfoS("%s", "world")
-}
diff --git a/examples/output_test/output_test.go b/examples/output_test/output_test.go
index 872612f..e9a79d4 100644
--- a/examples/output_test/output_test.go
+++ b/examples/output_test/output_test.go
@@ -34,7 +34,9 @@ import (
 	"k8s.io/klog/v2/textlogger"
 )
 
-func newLogger(out io.Writer, v int, vmodule string) logr.Logger {
+// newLogger is a test.OutputConfig.NewLogger callback which creates a zapr
+// logger. The vmodule parameter is ignored because zapr does not support that.
+func newLogger(out io.Writer, v int, _ string) logr.Logger {
 	return newZaprLogger(out, v)
 }
 
diff --git a/exit_test.go b/exit_test.go
index e295eb8..8c0a037 100644
--- a/exit_test.go
+++ b/exit_test.go
@@ -27,12 +27,15 @@ func ExampleFlushAndExit() {
 
 	var fs flag.FlagSet
 	klog.InitFlags(&fs)
-	fs.Set("skip_headers", "true")
-	defer flag.Set("skip_headers", "false")
-	fs.Set("logtostderr", "false")
-	defer fs.Set("logtostderr", "true")
+	state := klog.CaptureState()
+	defer state.Restore()
+	if err := fs.Set("skip_headers", "true"); err != nil {
+		panic(err)
+	}
+	if err := fs.Set("logtostderr", "false"); err != nil {
+		panic(err)
+	}
 	klog.SetOutput(os.Stdout)
-	defer klog.SetOutput(nil)
 	klog.OsExit = func(exitCode int) {
 		fmt.Printf("os.Exit(%d)\n", exitCode)
 	}
diff --git a/format_test.go b/format_test.go
index cd19145..490c966 100644
--- a/format_test.go
+++ b/format_test.go
@@ -43,7 +43,7 @@ func TestFormat(t *testing.T) {
 `, klog.Format(obj).(fmt.Stringer).String(), "Format(config).String()")
 	// fmt.Sprintf would call String if it was available.
 	str := fmt.Sprintf("%s", klog.Format(obj).(logr.Marshaler).MarshalLog())
-	if strings.Index(str, "kind is config") >= 0 {
+	if strings.Contains(str, "kind is config") {
 		t.Errorf("fmt.Sprintf called TypeMeta.String for klog.Format(obj).MarshalLog():\n%s", str)
 	}
 
diff --git a/internal/clock/testing/fake_clock.go b/internal/clock/testing/fake_clock.go
index a91114a..61d26a5 100644
--- a/internal/clock/testing/fake_clock.go
+++ b/internal/clock/testing/fake_clock.go
@@ -25,7 +25,6 @@ import (
 
 var (
 	_ = clock.PassiveClock(&FakePassiveClock{})
-	_ = clock.WithTicker(&FakeClock{})
 	_ = clock.Clock(&IntervalClock{})
 )
 
@@ -259,36 +258,30 @@ func (i *IntervalClock) Since(ts time.Time) time.Duration {
 
 // After is unimplemented, will panic.
 // TODO: make interval clock use FakeClock so this can be implemented.
-func (*IntervalClock) After(d time.Duration) <-chan time.Time {
+func (*IntervalClock) After(time.Duration) <-chan time.Time {
 	panic("IntervalClock doesn't implement After")
 }
 
 // NewTimer is unimplemented, will panic.
 // TODO: make interval clock use FakeClock so this can be implemented.
-func (*IntervalClock) NewTimer(d time.Duration) clock.Timer {
+func (*IntervalClock) NewTimer(time.Duration) clock.Timer {
 	panic("IntervalClock doesn't implement NewTimer")
 }
 
 // AfterFunc is unimplemented, will panic.
 // TODO: make interval clock use FakeClock so this can be implemented.
-func (*IntervalClock) AfterFunc(d time.Duration, f func()) clock.Timer {
+func (*IntervalClock) AfterFunc(time.Duration, func()) clock.Timer {
 	panic("IntervalClock doesn't implement AfterFunc")
 }
 
-// Tick is unimplemented, will panic.
-// TODO: make interval clock use FakeClock so this can be implemented.
-func (*IntervalClock) Tick(d time.Duration) <-chan time.Time {
-	panic("IntervalClock doesn't implement Tick")
-}
-
 // NewTicker has no implementation yet and is omitted.
 // TODO: make interval clock use FakeClock so this can be implemented.
-func (*IntervalClock) NewTicker(d time.Duration) clock.Ticker {
+func (*IntervalClock) NewTicker(time.Duration) clock.Ticker {
 	panic("IntervalClock doesn't implement NewTicker")
 }
 
 // Sleep is unimplemented, will panic.
-func (*IntervalClock) Sleep(d time.Duration) {
+func (*IntervalClock) Sleep(time.Duration) {
 	panic("IntervalClock doesn't implement Sleep")
 }
 
diff --git a/internal/test/require/require.go b/internal/test/require/require.go
new file mode 100644
index 0000000..93a8692
--- /dev/null
+++ b/internal/test/require/require.go
@@ -0,0 +1,26 @@
+/*
+Copyright 2023 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package require
+
+import "testing"
+
+func NoError(tb testing.TB, err error) {
+	if err != nil {
+		tb.Helper()
+		tb.Fatalf("Unexpected error: %v", err)
+	}
+}
diff --git a/internal/verbosity/verbosity_test.go b/internal/verbosity/verbosity_test.go
index 766147b..5735d48 100644
--- a/internal/verbosity/verbosity_test.go
+++ b/internal/verbosity/verbosity_test.go
@@ -19,11 +19,13 @@ package verbosity
 
 import (
 	"testing"
+
+	"k8s.io/klog/v2/internal/test/require"
 )
 
 func TestV(t *testing.T) {
 	vs := New()
-	vs.verbosity.Set("2")
+	require.NoError(t, vs.verbosity.Set("2"))
 	depth := 0
 	if !vs.Enabled(1, depth) {
 		t.Error("not enabled for 1")
@@ -38,7 +40,7 @@ func TestV(t *testing.T) {
 
 func TestVmoduleOn(t *testing.T) {
 	vs := New()
-	vs.vmodule.Set("verbosity_test=2")
+	require.NoError(t, vs.vmodule.Set("verbosity_test=2"))
 	depth := 0
 	if !vs.Enabled(1, depth) {
 		t.Error("not enabled for 1")
@@ -81,7 +83,7 @@ var vGlobs = map[string]bool{
 // Test that vmodule globbing works as advertised.
 func testVmoduleGlob(pat string, match bool, t *testing.T) {
 	vs := New()
-	vs.vmodule.Set(pat)
+	require.NoError(t, vs.vmodule.Set(pat))
 	depth := 0
 	actual := vs.Enabled(2, depth)
 	if actual != match {
diff --git a/klog_test.go b/klog_test.go
index 21b4dd1..4528bc2 100644
--- a/klog_test.go
+++ b/klog_test.go
@@ -40,6 +40,7 @@ import (
 	testingclock "k8s.io/klog/v2/internal/clock/testing"
 	"k8s.io/klog/v2/internal/severity"
 	"k8s.io/klog/v2/internal/test"
+	"k8s.io/klog/v2/internal/test/require"
 )
 
 // TODO: This test package should be refactored so that tests cannot
@@ -76,9 +77,7 @@ func (l *loggingT) swap(writers [severity.NumSeverity]flushSyncWriter) (old [sev
 	l.mu.Lock()
 	defer l.mu.Unlock()
 	old = l.file
-	for i, w := range writers {
-		logging.file[i] = w
-	}
+	logging.file = writers
 	return
 }
 
@@ -93,7 +92,7 @@ func contents(s severity.Severity) string {
 }
 
 // contains reports whether the string is contained in the log.
-func contains(s severity.Severity, str string, t *testing.T) bool {
+func contains(s severity.Severity, str string) bool {
 	return strings.Contains(contents(s), str)
 }
 
@@ -109,10 +108,10 @@ func TestInfo(t *testing.T) {
 	setFlags()
 	defer logging.swap(logging.newBuffers())
 	Info("test")
-	if !contains(severity.InfoLog, "I", t) {
+	if !contains(severity.InfoLog, "I") {
 		t.Errorf("Info has wrong character: %q", contents(severity.InfoLog))
 	}
-	if !contains(severity.InfoLog, "test", t) {
+	if !contains(severity.InfoLog, "test") {
 		t.Error("Info failed")
 	}
 }
@@ -182,10 +181,10 @@ func TestStandardLog(t *testing.T) {
 	setFlags()
 	defer logging.swap(logging.newBuffers())
 	stdLog.Print("test")
-	if !contains(severity.InfoLog, "I", t) {
+	if !contains(severity.InfoLog, "I") {
 		t.Errorf("Info has wrong character: %q", contents(severity.InfoLog))
 	}
-	if !contains(severity.InfoLog, "test", t) {
+	if !contains(severity.InfoLog, "test") {
 		t.Error("Info failed")
 	}
 }
@@ -240,17 +239,17 @@ func TestError(t *testing.T) {
 	setFlags()
 	defer logging.swap(logging.newBuffers())
 	Error("test")
-	if !contains(severity.ErrorLog, "E", t) {
+	if !contains(severity.ErrorLog, "E") {
 		t.Errorf("Error has wrong character: %q", contents(severity.ErrorLog))
 	}
-	if !contains(severity.ErrorLog, "test", t) {
+	if !contains(severity.ErrorLog, "test") {
 		t.Error("Error failed")
 	}
 	str := contents(severity.ErrorLog)
-	if !contains(severity.WarningLog, str, t) {
+	if !contains(severity.WarningLog, str) {
 		t.Error("Warning failed")
 	}
-	if !contains(severity.InfoLog, str, t) {
+	if !contains(severity.InfoLog, str) {
 		t.Error("Info failed")
 	}
 }
@@ -264,17 +263,17 @@ func TestErrorWithOneOutput(t *testing.T) {
 	logging.oneOutput = true
 	defer logging.swap(logging.newBuffers())
 	Error("test")
-	if !contains(severity.ErrorLog, "E", t) {
+	if !contains(severity.ErrorLog, "E") {
 		t.Errorf("Error has wrong character: %q", contents(severity.ErrorLog))
 	}
-	if !contains(severity.ErrorLog, "test", t) {
+	if !contains(severity.ErrorLog, "test") {
 		t.Error("Error failed")
 	}
 	str := contents(severity.ErrorLog)
-	if contains(severity.WarningLog, str, t) {
+	if contains(severity.WarningLog, str) {
 		t.Error("Warning failed")
 	}
-	if contains(severity.InfoLog, str, t) {
+	if contains(severity.InfoLog, str) {
 		t.Error("Info failed")
 	}
 }
@@ -287,14 +286,14 @@ func TestWarning(t *testing.T) {
 	setFlags()
 	defer logging.swap(logging.newBuffers())
 	Warning("test")
-	if !contains(severity.WarningLog, "W", t) {
+	if !contains(severity.WarningLog, "W") {
 		t.Errorf("Warning has wrong character: %q", contents(severity.WarningLog))
 	}
-	if !contains(severity.WarningLog, "test", t) {
+	if !contains(severity.WarningLog, "test") {
 		t.Error("Warning failed")
 	}
 	str := contents(severity.WarningLog)
-	if !contains(severity.InfoLog, str, t) {
+	if !contains(severity.InfoLog, str) {
 		t.Error("Info failed")
 	}
 }
@@ -308,14 +307,14 @@ func TestWarningWithOneOutput(t *testing.T) {
 	logging.oneOutput = true
 	defer logging.swap(logging.newBuffers())
 	Warning("test")
-	if !contains(severity.WarningLog, "W", t) {
+	if !contains(severity.WarningLog, "W") {
 		t.Errorf("Warning has wrong character: %q", contents(severity.WarningLog))
 	}
-	if !contains(severity.WarningLog, "test", t) {
+	if !contains(severity.WarningLog, "test") {
 		t.Error("Warning failed")
 	}
 	str := contents(severity.WarningLog)
-	if contains(severity.InfoLog, str, t) {
+	if contains(severity.InfoLog, str) {
 		t.Error("Info failed")
 	}
 }
@@ -325,12 +324,12 @@ func TestV(t *testing.T) {
 	defer CaptureState().Restore()
 	setFlags()
 	defer logging.swap(logging.newBuffers())
-	logging.verbosity.Set("2")
+	require.NoError(t, logging.verbosity.Set("2"))
 	V(2).Info("test")
-	if !contains(severity.InfoLog, "I", t) {
+	if !contains(severity.InfoLog, "I") {
 		t.Errorf("Info has wrong character: %q", contents(severity.InfoLog))
 	}
-	if !contains(severity.InfoLog, "test", t) {
+	if !contains(severity.InfoLog, "test") {
 		t.Error("Info failed")
 	}
 }
@@ -340,7 +339,7 @@ func TestVmoduleOn(t *testing.T) {
 	defer CaptureState().Restore()
 	setFlags()
 	defer logging.swap(logging.newBuffers())
-	logging.vmodule.Set("klog_test=2")
+	require.NoError(t, logging.vmodule.Set("klog_test=2"))
 	if !V(1).Enabled() {
 		t.Error("V not enabled for 1")
 	}
@@ -351,10 +350,10 @@ func TestVmoduleOn(t *testing.T) {
 		t.Error("V enabled for 3")
 	}
 	V(2).Info("test")
-	if !contains(severity.InfoLog, "I", t) {
+	if !contains(severity.InfoLog, "I") {
 		t.Errorf("Info has wrong character: %q", contents(severity.InfoLog))
 	}
-	if !contains(severity.InfoLog, "test", t) {
+	if !contains(severity.InfoLog, "test") {
 		t.Error("Info failed")
 	}
 }
@@ -364,7 +363,7 @@ func TestVmoduleOff(t *testing.T) {
 	defer CaptureState().Restore()
 	setFlags()
 	defer logging.swap(logging.newBuffers())
-	logging.vmodule.Set("notthisfile=2")
+	require.NoError(t, logging.vmodule.Set("notthisfile=2"))
 	for i := 1; i <= 3; i++ {
 		if V(Level(i)).Enabled() {
 			t.Errorf("V enabled for %d", i)
@@ -376,7 +375,7 @@ func TestVmoduleOff(t *testing.T) {
 	}
 }
 
-func TestSetOutputDataRace(t *testing.T) {
+func TestSetOutputDataRace(*testing.T) {
 	defer CaptureState().Restore()
 	setFlags()
 	defer logging.swap(logging.newBuffers())
@@ -456,7 +455,7 @@ func testVmoduleGlob(pat string, match bool, t *testing.T) {
 	defer CaptureState().Restore()
 	setFlags()
 	defer logging.swap(logging.newBuffers())
-	logging.vmodule.Set(pat)
+	require.NoError(t, logging.vmodule.Set(pat))
 	if V(2).Enabled() != match {
 		t.Errorf("incorrect match for %q: got %#v expected %#v", pat, V(2), match)
 	}
@@ -804,7 +803,7 @@ func BenchmarkLogs(b *testing.B) {
 	}
 	defer os.Remove(testFile.Name())
 
-	logging.verbosity.Set("0")
+	require.NoError(b, logging.verbosity.Set("0"))
 	logging.toStderr = false
 	logging.alsoToStderr = false
 	logging.stderrThreshold = severityValue{
@@ -875,14 +874,14 @@ func TestInitFlags(t *testing.T) {
 
 	fs1 := flag.NewFlagSet("test1", flag.PanicOnError)
 	InitFlags(fs1)
-	fs1.Set("log_dir", "/test1")
-	fs1.Set("log_file_max_size", "1")
+	require.NoError(t, fs1.Set("log_dir", "/test1"))
+	require.NoError(t, fs1.Set("log_file_max_size", "1"))
 	fs2 := flag.NewFlagSet("test2", flag.PanicOnError)
 	InitFlags(fs2)
 	if logging.logDir != "/test1" {
 		t.Fatalf("Expected log_dir to be %q, got %q", "/test1", logging.logDir)
 	}
-	fs2.Set("log_file_max_size", "2048")
+	require.NoError(t, fs2.Set("log_file_max_size", "2048"))
 	if logging.logFileMaxSizeMB != 2048 {
 		t.Fatal("Expected log_file_max_size to be 2048")
 	}
@@ -913,7 +912,7 @@ func TestCommandLine(t *testing.T) {
   -skip_log_headers
     	If true, avoid headers when opening log files (no effect when -logtostderr=true)
   -stderrthreshold value
-    	logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=true) (default 2)
+    	logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2)
   -v value
     	number for the log level verbosity
   -vmodule value
@@ -966,7 +965,7 @@ func TestInfoObjectRef(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			Info(tt.ref)
-			if !contains(severity.InfoLog, tt.want, t) {
+			if !contains(severity.InfoLog, tt.want) {
 				t.Errorf("expected %v, got %v", tt.want, contents(severity.InfoLog))
 			}
 		})
@@ -1170,7 +1169,7 @@ second value line`},
 		},
 	}
 
-	logging.verbosity.Set("2")
+	require.NoError(t, logging.verbosity.Set("2"))
 
 	for l := Level(0); l < Level(4); l++ {
 		for _, data := range testDataInfo {
@@ -1466,7 +1465,7 @@ func TestLogFilter(t *testing.T) {
 		for _, tc := range testcases {
 			logging.newBuffers()
 			f.logFunc(tc.args...)
-			got := contains(f.severity, "[FILTERED]", t)
+			got := contains(f.severity, "[FILTERED]")
 			if got != tc.expectFiltered {
 				t.Errorf("%s filter application failed, got %v, want %v", f.name, got, tc.expectFiltered)
 			}
@@ -1805,7 +1804,7 @@ func (l *testLogr) reset() {
 	l.entries = []testLogrEntry{}
 }
 
-func (l *testLogr) Info(level int, msg string, keysAndValues ...interface{}) {
+func (l *testLogr) Info(_ int, msg string, keysAndValues ...interface{}) {
 	l.mutex.Lock()
 	defer l.mutex.Unlock()
 	l.entries = append(l.entries, testLogrEntry{
@@ -1826,12 +1825,12 @@ func (l *testLogr) Error(err error, msg string, keysAndValues ...interface{}) {
 	})
 }
 
-func (l *testLogr) Init(info logr.RuntimeInfo)             {}
-func (l *testLogr) Enabled(level int) bool                 { return true }
+func (l *testLogr) Init(logr.RuntimeInfo)                  {}
+func (l *testLogr) Enabled(int) bool                       { return true }
 func (l *testLogr) V(int) logr.Logger                      { panic("not implemented") }
 func (l *testLogr) WithName(string) logr.LogSink           { panic("not implemented") }
 func (l *testLogr) WithValues(...interface{}) logr.LogSink { panic("not implemented") }
-func (l *testLogr) WithCallDepth(depth int) logr.LogSink   { return l }
+func (l *testLogr) WithCallDepth(int) logr.LogSink         { return l }
 
 var _ logr.LogSink = &testLogr{}
 var _ logr.CallDepthLogSink = &testLogr{}
@@ -1857,7 +1856,7 @@ func (l *callDepthTestLogr) WithCallDepth(depth int) logr.LogSink {
 	return l
 }
 
-func (l *callDepthTestLogr) Info(level int, msg string, keysAndValues ...interface{}) {
+func (l *callDepthTestLogr) Info(_ int, msg string, keysAndValues ...interface{}) {
 	l.mutex.Lock()
 	defer l.mutex.Unlock()
 	// Add 2 to depth for the wrapper function caller and for invocation in
@@ -2202,12 +2201,12 @@ func TestSettingsDeepCopy(t *testing.T) {
 			},
 		},
 	}
-	copy := settings.deepCopy()
-	if !reflect.DeepEqual(settings, copy) {
-		t.Fatalf("Copy not identical to original settings. Original:\n    %+v\nCopy:    %+v", settings, copy)
+	clone := settings.deepCopy()
+	if !reflect.DeepEqual(settings, clone) {
+		t.Fatalf("Copy not identical to original settings. Original:\n    %+v\nCopy:    %+v", settings, clone)
 	}
 	settings.vmodule.filter[1].pattern = "x"
-	if copy.vmodule.filter[1].pattern == settings.vmodule.filter[1].pattern {
+	if clone.vmodule.filter[1].pattern == settings.vmodule.filter[1].pattern {
 		t.Fatal("Copy should not have shared vmodule.filter.")
 	}
 }
diff --git a/klogr/calldepth-test/call_depth_main_test.go b/klogr/calldepth-test/call_depth_main_test.go
index 6d7e59f..83fe430 100644
--- a/klogr/calldepth-test/call_depth_main_test.go
+++ b/klogr/calldepth-test/call_depth_main_test.go
@@ -13,16 +13,17 @@ import (
 	"testing"
 
 	"k8s.io/klog/v2"
+	"k8s.io/klog/v2/internal/test/require"
 	"k8s.io/klog/v2/klogr"
 )
 
 func TestCallDepth(t *testing.T) {
 	klog.InitFlags(nil)
-	flag.CommandLine.Set("v", "10")
-	flag.CommandLine.Set("skip_headers", "false")
-	flag.CommandLine.Set("logtostderr", "false")
-	flag.CommandLine.Set("alsologtostderr", "false")
-	flag.CommandLine.Set("stderrthreshold", "10")
+	require.NoError(t, flag.CommandLine.Set("v", "10"))
+	require.NoError(t, flag.CommandLine.Set("skip_headers", "false"))
+	require.NoError(t, flag.CommandLine.Set("logtostderr", "false"))
+	require.NoError(t, flag.CommandLine.Set("alsologtostderr", "false"))
+	require.NoError(t, flag.CommandLine.Set("stderrthreshold", "10"))
 	flag.Parse()
 
 	t.Run("call-depth", func(t *testing.T) {
diff --git a/klogr/klogr_test.go b/klogr/klogr_test.go
index 53b9e45..0aeec03 100644
--- a/klogr/klogr_test.go
+++ b/klogr/klogr_test.go
@@ -8,6 +8,7 @@ import (
 	"testing"
 
 	"k8s.io/klog/v2"
+	"k8s.io/klog/v2/internal/test/require"
 	"k8s.io/klog/v2/test"
 
 	"github.com/go-logr/logr"
@@ -19,7 +20,7 @@ const (
 )
 
 func testOutput(t *testing.T, format string) {
-	new := func() logr.Logger {
+	createLogger := func() logr.Logger {
 		switch format {
 		case formatNew:
 			return New()
@@ -38,7 +39,7 @@ func testOutput(t *testing.T, format string) {
 		expectedKlogOutput string
 	}{
 		"should log with values passed to keysAndValues": {
-			klogr:         new().V(0),
+			klogr:         createLogger().V(0),
 			text:          "test",
 			keysAndValues: []interface{}{"akey", "avalue"},
 			expectedOutput: ` "msg"="test" "akey"="avalue"
@@ -47,7 +48,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"should log with name and values passed to keysAndValues": {
-			klogr:         new().V(0).WithName("me"),
+			klogr:         createLogger().V(0).WithName("me"),
 			text:          "test",
 			keysAndValues: []interface{}{"akey", "avalue"},
 			expectedOutput: `me "msg"="test" "akey"="avalue"
@@ -56,7 +57,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"should log with multiple names and values passed to keysAndValues": {
-			klogr:         new().V(0).WithName("hello").WithName("world"),
+			klogr:         createLogger().V(0).WithName("hello").WithName("world"),
 			text:          "test",
 			keysAndValues: []interface{}{"akey", "avalue"},
 			expectedOutput: `hello/world "msg"="test" "akey"="avalue"
@@ -65,7 +66,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"may print duplicate keys with the same value": {
-			klogr:         new().V(0),
+			klogr:         createLogger().V(0),
 			text:          "test",
 			keysAndValues: []interface{}{"akey", "avalue", "akey", "avalue"},
 			expectedOutput: ` "msg"="test" "akey"="avalue"
@@ -74,7 +75,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"may print duplicate keys when the values are passed to Info": {
-			klogr:         new().V(0),
+			klogr:         createLogger().V(0),
 			text:          "test",
 			keysAndValues: []interface{}{"akey", "avalue", "akey", "avalue2"},
 			expectedOutput: ` "msg"="test" "akey"="avalue2"
@@ -83,7 +84,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"should only print the duplicate key that is passed to Info if one was passed to the logger": {
-			klogr:         new().WithValues("akey", "avalue"),
+			klogr:         createLogger().WithValues("akey", "avalue"),
 			text:          "test",
 			keysAndValues: []interface{}{"akey", "avalue"},
 			expectedOutput: ` "msg"="test" "akey"="avalue"
@@ -92,7 +93,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"should sort within logger and parameter key/value pairs in the default format and dump the logger pairs first": {
-			klogr:         new().WithValues("akey9", "avalue9", "akey8", "avalue8", "akey1", "avalue1"),
+			klogr:         createLogger().WithValues("akey9", "avalue9", "akey8", "avalue8", "akey1", "avalue1"),
 			text:          "test",
 			keysAndValues: []interface{}{"akey5", "avalue5", "akey4", "avalue4"},
 			expectedOutput: ` "msg"="test" "akey1"="avalue1" "akey4"="avalue4" "akey5"="avalue5" "akey8"="avalue8" "akey9"="avalue9"
@@ -101,7 +102,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"should only print the key passed to Info when one is already set on the logger": {
-			klogr:         new().WithValues("akey", "avalue"),
+			klogr:         createLogger().WithValues("akey", "avalue"),
 			text:          "test",
 			keysAndValues: []interface{}{"akey", "avalue2"},
 			expectedOutput: ` "msg"="test" "akey"="avalue2"
@@ -110,7 +111,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"should correctly handle odd-numbers of KVs": {
-			klogr:         new(),
+			klogr:         createLogger(),
 			text:          "test",
 			keysAndValues: []interface{}{"akey", "avalue", "akey2"},
 			expectedOutput: ` "msg"="test" "akey"="avalue" "akey2"="(MISSING)"
@@ -119,7 +120,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"should correctly handle odd-numbers of KVs in WithValue": {
-			klogr:         new().WithValues("keyWithoutValue"),
+			klogr:         createLogger().WithValues("keyWithoutValue"),
 			text:          "test",
 			keysAndValues: []interface{}{"akey", "avalue", "akey2"},
 			// klogr format sorts all key/value pairs.
@@ -129,7 +130,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"should correctly html characters": {
-			klogr:         new(),
+			klogr:         createLogger(),
 			text:          "test",
 			keysAndValues: []interface{}{"akey", "<&>"},
 			expectedOutput: ` "msg"="test" "akey"="<&>"
@@ -138,7 +139,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"should correctly handle odd-numbers of KVs in both log values and Info args": {
-			klogr:         new().WithValues("basekey1", "basevar1", "basekey2"),
+			klogr:         createLogger().WithValues("basekey1", "basevar1", "basekey2"),
 			text:          "test",
 			keysAndValues: []interface{}{"akey", "avalue", "akey2"},
 			// klogr format sorts all key/value pairs.
@@ -148,7 +149,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"should correctly print regular error types": {
-			klogr:         new().V(0),
+			klogr:         createLogger().V(0),
 			text:          "test",
 			keysAndValues: []interface{}{"err", errors.New("whoops")},
 			expectedOutput: ` "msg"="test" "err"="whoops"
@@ -157,7 +158,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"should use MarshalJSON in the default format if an error type implements it": {
-			klogr:         new().V(0),
+			klogr:         createLogger().V(0),
 			text:          "test",
 			keysAndValues: []interface{}{"err", &customErrorJSON{"whoops"}},
 			expectedOutput: ` "msg"="test" "err"="WHOOPS"
@@ -166,7 +167,7 @@ func testOutput(t *testing.T, format string) {
 `,
 		},
 		"should correctly print regular error types when using logr.Error": {
-			klogr: new().V(0),
+			klogr: createLogger().V(0),
 			text:  "test",
 			err:   errors.New("whoops"),
 			expectedOutput: ` "msg"="test" "error"="whoops" 
@@ -205,7 +206,7 @@ func testOutput(t *testing.T, format string) {
 
 func TestOutput(t *testing.T) {
 	fs := test.InitKlog(t)
-	fs.Set("skip_headers", "true")
+	require.NoError(t, fs.Set("skip_headers", "true"))
 
 	formats := []string{
 		formatNew,
diff --git a/ktesting/init/init.go b/ktesting/init/init.go
index 3ee86f6..7eb4dc6 100644
--- a/ktesting/init/init.go
+++ b/ktesting/init/init.go
@@ -17,6 +17,11 @@ limitations under the License.
 // Package init registers the command line flags for k8s.io/klogr/testing in
 // the flag.CommandLine. This is done during initialization, so merely
 // importing it is enough.
+//
+// # Experimental
+//
+// Notice: This package is EXPERIMENTAL and may be changed or removed in a
+// later release.
 package init
 
 import (
diff --git a/ktesting/options.go b/ktesting/options.go
index 868446c..a1b4f6a 100644
--- a/ktesting/options.go
+++ b/ktesting/options.go
@@ -29,6 +29,11 @@ import (
 // bind command line flags to the instance before passing it to NewTestContext.
 //
 // Must be constructed with NewConfig.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type Config struct {
 	vstate *verbosity.VState
 	co     configOptions
@@ -49,6 +54,11 @@ func (c *Config) VModule() flag.Value {
 }
 
 // ConfigOption implements functional parameters for NewConfig.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type ConfigOption func(co *configOptions)
 
 type configOptions struct {
@@ -62,6 +72,11 @@ type configOptions struct {
 // AnyToString overrides the default formatter for values that are not
 // supported directly by klog. The default is `fmt.Sprintf("%+v")`.
 // The formatter must not panic.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func AnyToString(anyToString func(value interface{}) string) ConfigOption {
 	return func(co *configOptions) {
 		co.anyToString = anyToString
@@ -69,6 +84,11 @@ func AnyToString(anyToString func(value interface{}) string) ConfigOption {
 }
 
 // VerbosityFlagName overrides the default -testing.v for the verbosity level.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func VerbosityFlagName(name string) ConfigOption {
 	return func(co *configOptions) {
 		co.verbosityFlagName = name
@@ -77,6 +97,11 @@ func VerbosityFlagName(name string) ConfigOption {
 
 // VModulFlagName overrides the default -testing.vmodule for the per-module
 // verbosity levels.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func VModuleFlagName(name string) ConfigOption {
 	return func(co *configOptions) {
 		co.vmoduleFlagName = name
@@ -89,6 +114,11 @@ func VModuleFlagName(name string) ConfigOption {
 // https://github.com/kubernetes/community/blob/9406b4352fe2d5810cb21cc3cb059ce5886de157/contributors/devel/sig-instrumentation/logging.md#logging-conventions),
 // which is useful when debugging a failed test. `go test` only shows the log
 // output for failed tests. To see all output, use `go test -v`.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func Verbosity(level int) ConfigOption {
 	return func(co *configOptions) {
 		co.verbosityDefault = level
@@ -99,6 +129,11 @@ func Verbosity(level int) ConfigOption {
 // to being printed. Off by default. Unit tests that want to verify that
 // log entries are emitted as expected can turn this on and then retrieve
 // the captured log through the Underlier LogSink interface.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func BufferLogs(enabled bool) ConfigOption {
 	return func(co *configOptions) {
 		co.bufferLogs = enabled
@@ -107,6 +142,11 @@ func BufferLogs(enabled bool) ConfigOption {
 
 // NewConfig returns a configuration with recommended defaults and optional
 // modifications. Command line flags are not bound to any FlagSet yet.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func NewConfig(opts ...ConfigOption) *Config {
 	c := &Config{
 		co: configOptions{
@@ -120,11 +160,17 @@ func NewConfig(opts ...ConfigOption) *Config {
 	}
 
 	c.vstate = verbosity.New()
-	c.vstate.V().Set(strconv.FormatInt(int64(c.co.verbosityDefault), 10))
+	// Cannot fail for this input.
+	_ = c.vstate.V().Set(strconv.FormatInt(int64(c.co.verbosityDefault), 10))
 	return c
 }
 
 // AddFlags registers the command line flags that control the configuration.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func (c *Config) AddFlags(fs *flag.FlagSet) {
 	fs.Var(c.vstate.V(), c.co.verbosityFlagName, "number for the log level verbosity of the testing logger")
 	fs.Var(c.vstate.VModule(), c.co.vmoduleFlagName, "comma-separated list of pattern=N log level settings for files matching the patterns")
diff --git a/ktesting/setup.go b/ktesting/setup.go
index bf1d034..16b218e 100644
--- a/ktesting/setup.go
+++ b/ktesting/setup.go
@@ -24,12 +24,22 @@ import (
 
 // DefaultConfig is the global default logging configuration for a unit
 // test. It is used by NewTestContext and k8s.io/klogr/testing/init.
+//
+// # Experimental
+//
+// Notice: This variable is EXPERIMENTAL and may be changed or removed in a
+// later release.
 var DefaultConfig = NewConfig()
 
 // NewTestContext returns a logger and context for use in a unit test case or
 // benchmark. The tl parameter can be a testing.T or testing.B pointer that
 // will receive all log output. Importing k8s.io/klogr/testing/init will add
 // command line flags that modify the configuration of that log output.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func NewTestContext(tl TL) (logr.Logger, context.Context) {
 	logger := NewLogger(tl, DefaultConfig)
 	ctx := logr.NewContext(context.Background(), logger)
diff --git a/ktesting/testinglogger.go b/ktesting/testinglogger.go
index da0e2b8..ae30f05 100644
--- a/ktesting/testinglogger.go
+++ b/ktesting/testinglogger.go
@@ -34,6 +34,11 @@ limitations under the License.
 //
 // Serialization of the structured log parameters is done in the same way
 // as for klog.InfoS.
+//
+// # Experimental
+//
+// Notice: This package is EXPERIMENTAL and may be changed or removed in a
+// later release.
 package ktesting
 
 import (
@@ -53,6 +58,11 @@ import (
 )
 
 // TL is the relevant subset of testing.TB.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type TL interface {
 	Helper()
 	Log(args ...interface{})
@@ -60,14 +70,24 @@ type TL interface {
 
 // NopTL implements TL with empty stubs. It can be used when only capturing
 // output in memory is relevant.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type NopTL struct{}
 
-func (n NopTL) Helper()                 {}
-func (n NopTL) Log(args ...interface{}) {}
+func (n NopTL) Helper()            {}
+func (n NopTL) Log(...interface{}) {}
 
 var _ TL = NopTL{}
 
 // BufferTL implements TL with an in-memory buffer.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type BufferTL struct {
 	strings.Builder
 }
@@ -89,6 +109,11 @@ var _ TL = &BufferTL{}
 //
 // Verbosity can be modified at any time through the Config.V and
 // Config.VModule API.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func NewLogger(t TL, c *Config) logr.Logger {
 	l := tlogger{
 		shared: &tloggerShared{
@@ -116,6 +141,11 @@ func NewLogger(t TL, c *Config) logr.Logger {
 
 // Buffer stores log entries as formatted text and structured data.
 // It is safe to use this concurrently.
+//
+// # Experimental
+//
+// Notice: This interface is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type Buffer interface {
 	// String returns the log entries in a format that is similar to the
 	// klog text output.
@@ -126,10 +156,20 @@ type Buffer interface {
 }
 
 // Log contains log entries in the order in which they were generated.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type Log []LogEntry
 
 // DeepCopy returns a copy of the log. The error instance and key/value
 // pairs remain shared.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func (l Log) DeepCopy() Log {
 	log := make(Log, 0, len(l))
 	log = append(log, l...)
@@ -137,6 +177,11 @@ func (l Log) DeepCopy() Log {
 }
 
 // LogEntry represents all information captured for a log entry.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type LogEntry struct {
 	// Timestamp stores the time when the log entry was created.
 	Timestamp time.Time
@@ -169,18 +214,38 @@ type LogEntry struct {
 
 // LogType determines whether a log entry was created with an Error or Info
 // call.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type LogType string
 
 const (
 	// LogError is the special value used for Error log entries.
+	//
+	// Experimental
+	//
+	// Notice: This value is EXPERIMENTAL and may be changed or removed in
+	// a later release.
 	LogError = LogType("ERROR")
 
 	// LogInfo is the special value used for Info log entries.
+	//
+	// Experimental
+	//
+	// Notice: This value is EXPERIMENTAL and may be changed or removed in
+	// a later release.
 	LogInfo = LogType("INFO")
 )
 
 // Underlier is implemented by the LogSink of this logger. It provides access
 // to additional APIs that are normally hidden behind the Logger API.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type Underlier interface {
 	// GetUnderlying returns the testing instance that logging goes to.
 	// It returns nil when the test has completed already.
diff --git a/ktesting/testinglogger_test.go b/ktesting/testinglogger_test.go
index 6de1834..1ec709b 100644
--- a/ktesting/testinglogger_test.go
+++ b/ktesting/testinglogger_test.go
@@ -18,6 +18,7 @@ import (
 	"testing"
 
 	"k8s.io/klog/v2"
+	"k8s.io/klog/v2/internal/test/require"
 	"k8s.io/klog/v2/ktesting"
 )
 
@@ -173,10 +174,10 @@ func TestStop(t *testing.T) {
 	var output bytes.Buffer
 	var fs flag.FlagSet
 	klog.InitFlags(&fs)
-	fs.Set("alsologtostderr", "false")
-	fs.Set("logtostderr", "false")
-	fs.Set("stderrthreshold", "FATAL")
-	fs.Set("one_output", "true")
+	require.NoError(t, fs.Set("alsologtostderr", "false"))
+	require.NoError(t, fs.Set("logtostderr", "false"))
+	require.NoError(t, fs.Set("stderrthreshold", "FATAL"))
+	require.NoError(t, fs.Set("one_output", "true"))
 	klog.SetOutput(&output)
 
 	var logger klog.Logger
diff --git a/test/output.go b/test/output.go
index ce769dc..e3bc181 100644
--- a/test/output.go
+++ b/test/output.go
@@ -15,6 +15,11 @@ limitations under the License.
 */
 
 // Package test contains a reusable unit test for logging output and behavior.
+//
+// # Experimental
+//
+// Notice: This package is EXPERIMENTAL and may be changed or removed in a
+// later release.
 package test
 
 import (
@@ -43,6 +48,11 @@ import (
 //
 // The returned flag set has the klog flags registered. It can
 // be used to make further changes to the klog configuration.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func InitKlog(tb testing.TB) *flag.FlagSet {
 	state := klog.CaptureState()
 	tb.Cleanup(state.Restore)
@@ -67,6 +77,11 @@ func InitKlog(tb testing.TB) *flag.FlagSet {
 }
 
 // OutputConfig contains optional settings for Output.
+//
+// # Experimental
+//
+// Notice: This type is EXPERIMENTAL and may be changed or removed in a
+// later release.
 type OutputConfig struct {
 	// NewLogger is called to create a new logger. If nil, output via klog
 	// is tested. Support for -vmodule is optional.  ClearLogger is called
@@ -538,6 +553,12 @@ var _, _, printWithKlogLine, _ = runtime.Caller(0) // anchor for finding the lin
 //
 // Loggers will be tested with direct calls to Info or
 // as backend for klog.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release. The test cases and thus the expected output also may
+// change.
 func Output(t *testing.T, config OutputConfig) {
 	for n, test := range tests {
 		t.Run(n, func(t *testing.T) {
@@ -858,6 +879,12 @@ func Output(t *testing.T, config OutputConfig) {
 //
 // Loggers will be tested with direct calls to Info or
 // as backend for klog.
+//
+// # Experimental
+//
+// Notice: This function is EXPERIMENTAL and may be changed or removed in a
+// later release. The test cases and thus the expected output also may
+// change.
 func Benchmark(b *testing.B, config OutputConfig) {
 	for n, test := range tests {
 		b.Run(n, func(b *testing.B) {
diff --git a/test/zapr.go b/test/zapr.go
index dcd3575..6c7e809 100644
--- a/test/zapr.go
+++ b/test/zapr.go
@@ -18,6 +18,11 @@ package test
 
 // ZaprOutputMappingDirect provides a mapping from klog output to the
 // corresponding zapr output when zapr is called directly.
+//
+// # Experimental
+//
+// Notice: This package is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func ZaprOutputMappingDirect() map[string]string {
 	return map[string]string{
 		`I output.go:<LINE>] "test" akey="<&>"
@@ -277,6 +282,11 @@ I output.go:<LINE>] "odd WithValues" keyWithoutValue="(MISSING)"
 //   - zap drops keys with missing values, here we get "(MISSING)".
 //   - zap does not de-duplicate key/value pairs, here klog does that
 //     for it.
+//
+// # Experimental
+//
+// Notice: This package is EXPERIMENTAL and may be changed or removed in a
+// later release.
 func ZaprOutputMappingIndirect() map[string]string {
 	mapping := ZaprOutputMappingDirect()