Repository: kubernetes/klog
Pull request #311 (2022-03-18 18:23:38)

promote contextual logging APIs to stable
**What this PR does / why we need it**:

In practice we cannot change them anymore once they are published because dependencies of Kubernetes may start using them.

**Which issue(s) this PR fixes** :
Related-to https://github.com/kubernetes/kubernetes/pull/108725#discussion_r830100796

**Special notes for your reviewer**:

I *think* we can still treat test code as experimental. I can think of scenarios where these functions get pulled into regular packages, but that seems very unlikely.

**Release note**:
```release-note
The contextual logging API is no longer experimental.
```

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/klog/pull/311#" title="Author self-approved">pohly</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **dims** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @dims` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fklog).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes/klog/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["dims"]} -->

Patch:
diff --git a/OWNERS b/OWNERS
index 8cccebf..ad5063f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,13 +1,19 @@
 # See the OWNERS docs at https://go.k8s.io/owners
 reviewers:
+  - jayunit100
+  - hoegaarden
+  - andyxning
+  - neolit123
   - pohly
+  - yagonobre
+  - vincepri
+  - detiber
 approvers:
   - dims
   - thockin
-  - serathius
-emeritus_approvers:
-  - brancz
   - justinsb
-  - lavalamp
-  - piosz
   - tallclair
+  - piosz
+  - brancz
+  - lavalamp
+  - serathius
diff --git a/README.md b/README.md
index a9c945e..7de2212 100644
--- a/README.md
+++ b/README.md
@@ -28,12 +28,15 @@ Historical context is available here:
 Semantic versioning is used in this repository. It contains several Go modules
 with different levels of stability:
 - `k8s.io/klog/v2` - stable API, `vX.Y.Z` tags
-- `k8s.io/tools` - no stable API yet (may change eventually), `tools/v0.Y.Z` tags
+- `k8s.io/hack/tools` - no stable API yet (may change eventually or get moved to separate repo), `hack/tools/v0.Y.Z` tags
 - `examples` - no stable API, no tags, no intention to ever stabilize
 
 Exempt from the API stability guarantee are items (packages, functions, etc.)
 which are marked explicitly as `EXPERIMENTAL` in their docs comment. Those
-may still change in incompatible ways or get removed entirely.
+may still change in incompatible ways or get removed entirely. This can only
+be used for code that is used in tests to avoid situations where non-test
+code from two different Kubernetes dependencies depends on incompatible
+releases of klog because an experimental API was changed.
 
 ----
 
diff --git a/contextual.go b/contextual.go
index 33743ff..17e899a 100644
--- a/contextual.go
+++ b/contextual.go
@@ -46,6 +46,10 @@ var (
 	// globalLogger.
 	globalLoggerOptions loggerOptions
 
+	// contextualLogger defines whether globalLogger may get called
+	// directly.
+	contextualLogger bool
+
 	// klogLogger is used as fallback for logging through the normal klog code
 	// when no Logger is set.
 	klogLogger logr.Logger = logr.New(&klogger{})
@@ -80,11 +84,6 @@ func SetLogger(logger logr.Logger) {
 // Supporting direct calls is recommended because it avoids the overhead of
 // routing log entries through klogr into klog and then into the actual Logger
 // backend.
-//
-// Experimental
-//
-// Notice: This function is EXPERIMENTAL and may be changed or removed in a
-// later release.
 func SetLoggerWithOptions(logger logr.Logger, opts ...LoggerOption) {
 	globalLogger = &logger
 	globalLoggerOptions = loggerOptions{}
@@ -96,11 +95,6 @@ func SetLoggerWithOptions(logger logr.Logger, opts ...LoggerOption) {
 // ContextualLogger determines whether the logger passed to
 // SetLoggerWithOptions may also get called directly. Such a logger cannot rely
 // on verbosity checking in klog.
-//
-// Experimental
-//
-// Notice: This function is EXPERIMENTAL and may be changed or removed in a
-// later release.
 func ContextualLogger(enabled bool) LoggerOption {
 	return func(o *loggerOptions) {
 		o.contextualLogger = enabled
@@ -108,11 +102,6 @@ func ContextualLogger(enabled bool) LoggerOption {
 }
 
 // FlushLogger provides a callback for flushing data buffered by the logger.
-//
-// Experimental
-//
-// Notice: This function is EXPERIMENTAL and may be changed or removed in a
-// later release.
 func FlushLogger(flush func()) LoggerOption {
 	return func(o *loggerOptions) {
 		o.flush = flush
@@ -121,11 +110,6 @@ func FlushLogger(flush func()) LoggerOption {
 
 // LoggerOption implements the functional parameter paradigm for
 // SetLoggerWithOptions.
-//
-// Experimental
-//
-// Notice: This type is EXPERIMENTAL and may be changed or removed in a
-// later release.
 type LoggerOption func(o *loggerOptions)
 
 type loggerOptions struct {
@@ -133,6 +117,15 @@ type loggerOptions struct {
 	flush            func()
 }
 
+// SetContextualLogger does the same as SetLogger, but in addition the
+// logger may also get called directly by code that retrieves it
+// with FromContext, TODO or Background. The logger therefore must
+// implements its own verbosity checking.
+func SetContextualLogger(logger logr.Logger) {
+	globalLogger = &logger
+	contextualLogger = true
+}
+
 // ClearLogger removes a backing Logger implementation if one was set earlier
 // with SetLogger.
 //
@@ -151,11 +144,6 @@ func ClearLogger() {
 // to avoid the additional overhead for contextual logging.
 //
 // This must be called during initialization before goroutines are started.
-//
-// Experimental
-//
-// Notice: This function is EXPERIMENTAL and may be changed or removed in a
-// later release.
 func EnableContextualLogging(enabled bool) {
 	contextualLoggingEnabled = enabled
 }
@@ -163,11 +151,6 @@ func EnableContextualLogging(enabled bool) {
 // FromContext retrieves a logger set by the caller or, if not set,
 // falls back to the program's global logger (a Logger instance or klog
 // itself).
-//
-// Experimental
-//
-// Notice: This function is EXPERIMENTAL and may be changed or removed in a
-// later release.
 func FromContext(ctx context.Context) Logger {
 	if contextualLoggingEnabled {
 		if logger, err := logr.FromContext(ctx); err == nil {
@@ -181,11 +164,6 @@ func FromContext(ctx context.Context) Logger {
 // TODO can be used as a last resort by code that has no means of
 // receiving a logger from its caller. FromContext or an explicit logger
 // parameter should be used instead.
-//
-// Experimental
-//
-// Notice: This function is EXPERIMENTAL and may be changed or removed in a
-// later release.
 func TODO() Logger {
 	return Background()
 }
@@ -194,11 +172,6 @@ func TODO() Logger {
 // that logger was initialized by the program and not by code that should
 // better receive a logger via its parameters. TODO can be used as a temporary
 // solution for such code.
-//
-// Experimental
-//
-// Notice: This function is EXPERIMENTAL and may be changed or removed in a
-// later release.
 func Background() Logger {
 	if globalLoggerOptions.contextualLogger {
 		// Is non-nil because globalLoggerOptions.contextualLogger is
@@ -211,11 +184,6 @@ func Background() Logger {
 
 // LoggerWithValues returns logger.WithValues(...kv) when
 // contextual logging is enabled, otherwise the logger.
-//
-// Experimental
-//
-// Notice: This function is EXPERIMENTAL and may be changed or removed in a
-// later release.
 func LoggerWithValues(logger Logger, kv ...interface{}) Logger {
 	if contextualLoggingEnabled {
 		return logger.WithValues(kv...)
@@ -225,11 +193,6 @@ func LoggerWithValues(logger Logger, kv ...interface{}) Logger {
 
 // LoggerWithName returns logger.WithName(name) when contextual logging is
 // enabled, otherwise the logger.
-//
-// Experimental
-//
-// Notice: This function is EXPERIMENTAL and may be changed or removed in a
-// later release.
 func LoggerWithName(logger Logger, name string) Logger {
 	if contextualLoggingEnabled {
 		return logger.WithName(name)
@@ -239,11 +202,6 @@ func LoggerWithName(logger Logger, name string) Logger {
 
 // NewContext returns logr.NewContext(ctx, logger) when
 // contextual logging is enabled, otherwise ctx.
-//
-// Experimental
-//
-// Notice: This function is EXPERIMENTAL and may be changed or removed in a
-// later release.
 func NewContext(ctx context.Context, logger Logger) context.Context {
 	if contextualLoggingEnabled {
 		return logr.NewContext(ctx, logger)
diff --git a/go.mod b/go.mod
index 31aefba..6385c6c 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,7 @@ module k8s.io/klog/v2
 
 go 1.13
 
-require github.com/go-logr/logr v1.2.0
+require (
+	github.com/go-logr/logr v1.2.0
+	k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
+)
diff --git a/go.sum b/go.sum
index 919fbad..ef73139 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,13 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
 github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE=
 github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
+k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc=
+k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
diff --git a/imports.go b/imports.go
index 43cd081..602c3ed 100644
--- a/imports.go
+++ b/imports.go
@@ -24,35 +24,15 @@ import (
 // without directly importing it.
 
 // Logger in this package is exactly the same as logr.Logger.
-//
-// Experimental
-//
-// Notice: This type is EXPERIMENTAL and may be changed or removed in a
-// later release.
 type Logger = logr.Logger
 
 // LogSink in this package is exactly the same as logr.LogSink.
-//
-// Experimental
-//
-// Notice: This type is EXPERIMENTAL and may be changed or removed in a
-// later release.
 type LogSink = logr.LogSink
 
 // Runtimeinfo in this package is exactly the same as logr.RuntimeInfo.
-//
-// Experimental
-//
-// Notice: This type is EXPERIMENTAL and may be changed or removed in a
-// later release.
 type RuntimeInfo = logr.RuntimeInfo
 
 var (
 	// New is an alias for logr.New.
-	//
-	// Experimental
-	//
-	// Notice: This variable is EXPERIMENTAL and may be changed or removed in a
-	// later release.
 	New = logr.New
 )
diff --git a/internal/clock/README.md b/internal/clock/README.md
deleted file mode 100644
index 03d692c..0000000
--- a/internal/clock/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Clock
-
-This package provides an interface for time-based operations.  It allows
-mocking time for testing.
-
-This is a copy of k8s.io/utils/clock. We have to copy it to avoid a circular
-dependency (k8s.io/klog -> k8s.io/utils -> k8s.io/klog).
diff --git a/internal/clock/clock.go b/internal/clock/clock.go
deleted file mode 100644
index b8b6af5..0000000
--- a/internal/clock/clock.go
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
-Copyright 2014 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 clock
-
-import "time"
-
-// PassiveClock allows for injecting fake or real clocks into code
-// that needs to read the current time but does not support scheduling
-// activity in the future.
-type PassiveClock interface {
-	Now() time.Time
-	Since(time.Time) time.Duration
-}
-
-// Clock allows for injecting fake or real clocks into code that
-// needs to do arbitrary things based on time.
-type Clock interface {
-	PassiveClock
-	// After returns the channel of a new Timer.
-	// This method does not allow to free/GC the backing timer before it fires. Use
-	// NewTimer instead.
-	After(d time.Duration) <-chan time.Time
-	// NewTimer returns a new Timer.
-	NewTimer(d time.Duration) Timer
-	// 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
-}
-
-// WithDelayedExecution allows for injecting fake or real clocks into
-// code that needs to make use of AfterFunc functionality.
-type WithDelayedExecution interface {
-	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.
-	AfterFunc(d time.Duration, f func()) Timer
-}
-
-// WithTickerAndDelayedExecution allows for injecting fake or real clocks
-// into code that needs Ticker and AfterFunc functionality
-type WithTickerAndDelayedExecution interface {
-	WithTicker
-	// 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.
-	AfterFunc(d time.Duration, f func()) Timer
-}
-
-// Ticker defines the Ticker interface.
-type Ticker interface {
-	C() <-chan time.Time
-	Stop()
-}
-
-var _ = WithTicker(RealClock{})
-
-// RealClock really calls time.Now()
-type RealClock struct{}
-
-// Now returns the current time.
-func (RealClock) Now() time.Time {
-	return time.Now()
-}
-
-// Since returns time since the specified timestamp.
-func (RealClock) Since(ts time.Time) time.Duration {
-	return time.Since(ts)
-}
-
-// After is the same as time.After(d).
-// This method does not allow to free/GC the backing timer before it fires. Use
-// NewTimer instead.
-func (RealClock) After(d time.Duration) <-chan time.Time {
-	return time.After(d)
-}
-
-// NewTimer is the same as time.NewTimer(d)
-func (RealClock) NewTimer(d time.Duration) Timer {
-	return &realTimer{
-		timer: time.NewTimer(d),
-	}
-}
-
-// AfterFunc is the same as time.AfterFunc(d, f).
-func (RealClock) AfterFunc(d time.Duration, f func()) Timer {
-	return &realTimer{
-		timer: time.AfterFunc(d, f),
-	}
-}
-
-// 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{
-		ticker: time.NewTicker(d),
-	}
-}
-
-// Sleep is the same as time.Sleep(d)
-// Consider making the sleep interruptible by using 'select' on a context channel and a timer channel.
-func (RealClock) Sleep(d time.Duration) {
-	time.Sleep(d)
-}
-
-// Timer allows for injecting fake or real timers into code that
-// needs to do arbitrary things based on time.
-type Timer interface {
-	C() <-chan time.Time
-	Stop() bool
-	Reset(d time.Duration) bool
-}
-
-var _ = Timer(&realTimer{})
-
-// realTimer is backed by an actual time.Timer.
-type realTimer struct {
-	timer *time.Timer
-}
-
-// C returns the underlying timer's channel.
-func (r *realTimer) C() <-chan time.Time {
-	return r.timer.C
-}
-
-// Stop calls Stop() on the underlying timer.
-func (r *realTimer) Stop() bool {
-	return r.timer.Stop()
-}
-
-// Reset calls Reset() on the underlying timer.
-func (r *realTimer) Reset(d time.Duration) bool {
-	return r.timer.Reset(d)
-}
-
-type realTicker struct {
-	ticker *time.Ticker
-}
-
-func (r *realTicker) C() <-chan time.Time {
-	return r.ticker.C
-}
-
-func (r *realTicker) Stop() {
-	r.ticker.Stop()
-}
diff --git a/klog.go b/klog.go
index cb04590..bb6f64b 100644
--- a/klog.go
+++ b/klog.go
@@ -91,9 +91,9 @@ import (
 	"github.com/go-logr/logr"
 
 	"k8s.io/klog/v2/internal/buffer"
-	"k8s.io/klog/v2/internal/clock"
 	"k8s.io/klog/v2/internal/serialize"
 	"k8s.io/klog/v2/internal/severity"
+	"k8s.io/utils/clock"
 )
 
 // severityValue identifies the sort of log: info, warning etc. It also implements
diff --git a/klogr.go b/klogr.go
index cdb3834..351d7a7 100644
--- a/klogr.go
+++ b/klogr.go
@@ -25,11 +25,6 @@ import (
 // NewKlogr returns a logger that is functionally identical to
 // klogr.NewWithOptions(klogr.FormatKlog), i.e. it passes through to klog. The
 // difference is that it uses a simpler implementation.
-//
-// Experimental
-//
-// Notice: This function is EXPERIMENTAL and may be changed or removed in a
-// later release.
 func NewKlogr() Logger {
 	return New(&klogger{})
 }


Test patch:
diff --git a/internal/clock/testing/fake_clock.go b/internal/clock/testing/fake_clock.go
deleted file mode 100644
index a91114a..0000000
--- a/internal/clock/testing/fake_clock.go
+++ /dev/null
@@ -1,361 +0,0 @@
-/*
-Copyright 2014 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 testing
-
-import (
-	"sync"
-	"time"
-
-	"k8s.io/klog/v2/internal/clock"
-)
-
-var (
-	_ = clock.PassiveClock(&FakePassiveClock{})
-	_ = clock.WithTicker(&FakeClock{})
-	_ = clock.Clock(&IntervalClock{})
-)
-
-// FakePassiveClock implements PassiveClock, but returns an arbitrary time.
-type FakePassiveClock struct {
-	lock sync.RWMutex
-	time time.Time
-}
-
-// FakeClock implements clock.Clock, but returns an arbitrary time.
-type FakeClock struct {
-	FakePassiveClock
-
-	// waiters are waiting for the fake time to pass their specified time
-	waiters []*fakeClockWaiter
-}
-
-type fakeClockWaiter struct {
-	targetTime    time.Time
-	stepInterval  time.Duration
-	skipIfBlocked bool
-	destChan      chan time.Time
-	fired         bool
-	afterFunc     func()
-}
-
-// NewFakePassiveClock returns a new FakePassiveClock.
-func NewFakePassiveClock(t time.Time) *FakePassiveClock {
-	return &FakePassiveClock{
-		time: t,
-	}
-}
-
-// NewFakeClock constructs a fake clock set to the provided time.
-func NewFakeClock(t time.Time) *FakeClock {
-	return &FakeClock{
-		FakePassiveClock: *NewFakePassiveClock(t),
-	}
-}
-
-// Now returns f's time.
-func (f *FakePassiveClock) Now() time.Time {
-	f.lock.RLock()
-	defer f.lock.RUnlock()
-	return f.time
-}
-
-// Since returns time since the time in f.
-func (f *FakePassiveClock) Since(ts time.Time) time.Duration {
-	f.lock.RLock()
-	defer f.lock.RUnlock()
-	return f.time.Sub(ts)
-}
-
-// SetTime sets the time on the FakePassiveClock.
-func (f *FakePassiveClock) SetTime(t time.Time) {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-	f.time = t
-}
-
-// After is the fake version of time.After(d).
-func (f *FakeClock) After(d time.Duration) <-chan time.Time {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-	stopTime := f.time.Add(d)
-	ch := make(chan time.Time, 1) // Don't block!
-	f.waiters = append(f.waiters, &fakeClockWaiter{
-		targetTime: stopTime,
-		destChan:   ch,
-	})
-	return ch
-}
-
-// NewTimer constructs a fake timer, akin to time.NewTimer(d).
-func (f *FakeClock) NewTimer(d time.Duration) clock.Timer {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-	stopTime := f.time.Add(d)
-	ch := make(chan time.Time, 1) // Don't block!
-	timer := &fakeTimer{
-		fakeClock: f,
-		waiter: fakeClockWaiter{
-			targetTime: stopTime,
-			destChan:   ch,
-		},
-	}
-	f.waiters = append(f.waiters, &timer.waiter)
-	return timer
-}
-
-// AfterFunc is the Fake version of time.AfterFunc(d, cb).
-func (f *FakeClock) AfterFunc(d time.Duration, cb func()) clock.Timer {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-	stopTime := f.time.Add(d)
-	ch := make(chan time.Time, 1) // Don't block!
-
-	timer := &fakeTimer{
-		fakeClock: f,
-		waiter: fakeClockWaiter{
-			targetTime: stopTime,
-			destChan:   ch,
-			afterFunc:  cb,
-		},
-	}
-	f.waiters = append(f.waiters, &timer.waiter)
-	return timer
-}
-
-// Tick constructs a fake ticker, akin to time.Tick
-func (f *FakeClock) Tick(d time.Duration) <-chan time.Time {
-	if d <= 0 {
-		return nil
-	}
-	f.lock.Lock()
-	defer f.lock.Unlock()
-	tickTime := f.time.Add(d)
-	ch := make(chan time.Time, 1) // hold one tick
-	f.waiters = append(f.waiters, &fakeClockWaiter{
-		targetTime:    tickTime,
-		stepInterval:  d,
-		skipIfBlocked: true,
-		destChan:      ch,
-	})
-
-	return ch
-}
-
-// NewTicker returns a new Ticker.
-func (f *FakeClock) NewTicker(d time.Duration) clock.Ticker {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-	tickTime := f.time.Add(d)
-	ch := make(chan time.Time, 1) // hold one tick
-	f.waiters = append(f.waiters, &fakeClockWaiter{
-		targetTime:    tickTime,
-		stepInterval:  d,
-		skipIfBlocked: true,
-		destChan:      ch,
-	})
-
-	return &fakeTicker{
-		c: ch,
-	}
-}
-
-// Step moves the clock by Duration and notifies anyone that's called After,
-// Tick, or NewTimer.
-func (f *FakeClock) Step(d time.Duration) {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-	f.setTimeLocked(f.time.Add(d))
-}
-
-// SetTime sets the time.
-func (f *FakeClock) SetTime(t time.Time) {
-	f.lock.Lock()
-	defer f.lock.Unlock()
-	f.setTimeLocked(t)
-}
-
-// Actually changes the time and checks any waiters. f must be write-locked.
-func (f *FakeClock) setTimeLocked(t time.Time) {
-	f.time = t
-	newWaiters := make([]*fakeClockWaiter, 0, len(f.waiters))
-	for i := range f.waiters {
-		w := f.waiters[i]
-		if !w.targetTime.After(t) {
-			if w.skipIfBlocked {
-				select {
-				case w.destChan <- t:
-					w.fired = true
-				default:
-				}
-			} else {
-				w.destChan <- t
-				w.fired = true
-			}
-
-			if w.afterFunc != nil {
-				w.afterFunc()
-			}
-
-			if w.stepInterval > 0 {
-				for !w.targetTime.After(t) {
-					w.targetTime = w.targetTime.Add(w.stepInterval)
-				}
-				newWaiters = append(newWaiters, w)
-			}
-
-		} else {
-			newWaiters = append(newWaiters, f.waiters[i])
-		}
-	}
-	f.waiters = newWaiters
-}
-
-// HasWaiters returns true if After or AfterFunc has been called on f but not yet satisfied (so you can
-// write race-free tests).
-func (f *FakeClock) HasWaiters() bool {
-	f.lock.RLock()
-	defer f.lock.RUnlock()
-	return len(f.waiters) > 0
-}
-
-// Sleep is akin to time.Sleep
-func (f *FakeClock) Sleep(d time.Duration) {
-	f.Step(d)
-}
-
-// IntervalClock implements clock.PassiveClock, but each invocation of Now steps the clock forward the specified duration.
-// IntervalClock technically implements the other methods of clock.Clock, but each implementation is just a panic.
-//
-// Deprecated: See SimpleIntervalClock for an alternative that only has the methods of PassiveClock.
-type IntervalClock struct {
-	Time     time.Time
-	Duration time.Duration
-}
-
-// Now returns i's time.
-func (i *IntervalClock) Now() time.Time {
-	i.Time = i.Time.Add(i.Duration)
-	return i.Time
-}
-
-// Since returns time since the time in i.
-func (i *IntervalClock) Since(ts time.Time) time.Duration {
-	return i.Time.Sub(ts)
-}
-
-// 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 {
-	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 {
-	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 {
-	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 {
-	panic("IntervalClock doesn't implement NewTicker")
-}
-
-// Sleep is unimplemented, will panic.
-func (*IntervalClock) Sleep(d time.Duration) {
-	panic("IntervalClock doesn't implement Sleep")
-}
-
-var _ = clock.Timer(&fakeTimer{})
-
-// fakeTimer implements clock.Timer based on a FakeClock.
-type fakeTimer struct {
-	fakeClock *FakeClock
-	waiter    fakeClockWaiter
-}
-
-// C returns the channel that notifies when this timer has fired.
-func (f *fakeTimer) C() <-chan time.Time {
-	return f.waiter.destChan
-}
-
-// Stop stops the timer and returns true if the timer has not yet fired, or false otherwise.
-func (f *fakeTimer) Stop() bool {
-	f.fakeClock.lock.Lock()
-	defer f.fakeClock.lock.Unlock()
-
-	newWaiters := make([]*fakeClockWaiter, 0, len(f.fakeClock.waiters))
-	for i := range f.fakeClock.waiters {
-		w := f.fakeClock.waiters[i]
-		if w != &f.waiter {
-			newWaiters = append(newWaiters, w)
-		}
-	}
-
-	f.fakeClock.waiters = newWaiters
-
-	return !f.waiter.fired
-}
-
-// Reset resets the timer to the fake clock's "now" + d. It returns true if the timer has not yet
-// fired, or false otherwise.
-func (f *fakeTimer) Reset(d time.Duration) bool {
-	f.fakeClock.lock.Lock()
-	defer f.fakeClock.lock.Unlock()
-
-	active := !f.waiter.fired
-
-	f.waiter.fired = false
-	f.waiter.targetTime = f.fakeClock.time.Add(d)
-
-	var isWaiting bool
-	for i := range f.fakeClock.waiters {
-		w := f.fakeClock.waiters[i]
-		if w == &f.waiter {
-			isWaiting = true
-			break
-		}
-	}
-	if !isWaiting {
-		f.fakeClock.waiters = append(f.fakeClock.waiters, &f.waiter)
-	}
-
-	return active
-}
-
-type fakeTicker struct {
-	c <-chan time.Time
-}
-
-func (t *fakeTicker) C() <-chan time.Time {
-	return t.c
-}
-
-func (t *fakeTicker) Stop() {
-}
diff --git a/internal/clock/testing/simple_interval_clock.go b/internal/clock/testing/simple_interval_clock.go
deleted file mode 100644
index be26773..0000000
--- a/internal/clock/testing/simple_interval_clock.go
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-Copyright 2021 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 testing
-
-import (
-	"time"
-
-	"k8s.io/klog/v2/internal/clock"
-)
-
-var (
-	_ = clock.PassiveClock(&SimpleIntervalClock{})
-)
-
-// SimpleIntervalClock implements clock.PassiveClock, but each invocation of Now steps the clock forward the specified duration
-type SimpleIntervalClock struct {
-	Time     time.Time
-	Duration time.Duration
-}
-
-// Now returns i's time.
-func (i *SimpleIntervalClock) Now() time.Time {
-	i.Time = i.Time.Add(i.Duration)
-	return i.Time
-}
-
-// Since returns time since the time in i.
-func (i *SimpleIntervalClock) Since(ts time.Time) time.Duration {
-	return i.Time.Sub(ts)
-}
diff --git a/klog_test.go b/klog_test.go
index a23f571..0a16574 100644
--- a/klog_test.go
+++ b/klog_test.go
@@ -37,9 +37,9 @@ import (
 	"github.com/go-logr/logr"
 
 	"k8s.io/klog/v2/internal/buffer"
-	testingclock "k8s.io/klog/v2/internal/clock/testing"
 	"k8s.io/klog/v2/internal/severity"
 	"k8s.io/klog/v2/internal/test"
+	testingclock "k8s.io/utils/clock/testing"
 )
 
 // TODO: This test package should be refactored so that tests cannot