client-go/rest: finish conversion to contextual logging

The remaining calls can be converted without API changes.
This commit is contained in:
Patrick Ohly 2024-09-02 17:55:12 +02:00
parent b15a1943d5
commit 7821abf2ae
9 changed files with 53 additions and 36 deletions

View File

@ -143,6 +143,7 @@ linters-settings: # please keep this alphabetized
contextual k8s.io/api/.* contextual k8s.io/api/.*
contextual k8s.io/apimachinery/pkg/util/runtime/.* contextual k8s.io/apimachinery/pkg/util/runtime/.*
contextual k8s.io/client-go/metadata/.* contextual k8s.io/client-go/metadata/.*
contextual k8s.io/client-go/rest/.*
contextual k8s.io/client-go/tools/cache/.* contextual k8s.io/client-go/tools/cache/.*
contextual k8s.io/client-go/tools/events/.* contextual k8s.io/client-go/tools/events/.*
contextual k8s.io/client-go/tools/record/.* contextual k8s.io/client-go/tools/record/.*

View File

@ -189,6 +189,7 @@ linters-settings: # please keep this alphabetized
contextual k8s.io/api/.* contextual k8s.io/api/.*
contextual k8s.io/apimachinery/pkg/util/runtime/.* contextual k8s.io/apimachinery/pkg/util/runtime/.*
contextual k8s.io/client-go/metadata/.* contextual k8s.io/client-go/metadata/.*
contextual k8s.io/client-go/rest/.*
contextual k8s.io/client-go/tools/cache/.* contextual k8s.io/client-go/tools/cache/.*
contextual k8s.io/client-go/tools/events/.* contextual k8s.io/client-go/tools/events/.*
contextual k8s.io/client-go/tools/record/.* contextual k8s.io/client-go/tools/record/.*

View File

@ -191,6 +191,7 @@ linters-settings: # please keep this alphabetized
contextual k8s.io/api/.* contextual k8s.io/api/.*
contextual k8s.io/apimachinery/pkg/util/runtime/.* contextual k8s.io/apimachinery/pkg/util/runtime/.*
contextual k8s.io/client-go/metadata/.* contextual k8s.io/client-go/metadata/.*
contextual k8s.io/client-go/rest/.*
contextual k8s.io/client-go/tools/cache/.* contextual k8s.io/client-go/tools/cache/.*
contextual k8s.io/client-go/tools/events/.* contextual k8s.io/client-go/tools/events/.*
contextual k8s.io/client-go/tools/record/.* contextual k8s.io/client-go/tools/record/.*

View File

@ -27,6 +27,7 @@ structured k8s.io/apiserver/pkg/server/options/encryptionconfig/.*
contextual k8s.io/api/.* contextual k8s.io/api/.*
contextual k8s.io/apimachinery/pkg/util/runtime/.* contextual k8s.io/apimachinery/pkg/util/runtime/.*
contextual k8s.io/client-go/metadata/.* contextual k8s.io/client-go/metadata/.*
contextual k8s.io/client-go/rest/.*
contextual k8s.io/client-go/tools/cache/.* contextual k8s.io/client-go/tools/cache/.*
contextual k8s.io/client-go/tools/events/.* contextual k8s.io/client-go/tools/events/.*
contextual k8s.io/client-go/tools/record/.* contextual k8s.io/client-go/tools/record/.*

View File

@ -558,6 +558,7 @@ func InClusterConfig() (*Config, error) {
tlsClientConfig := TLSClientConfig{} tlsClientConfig := TLSClientConfig{}
if _, err := certutil.NewPool(rootCAFile); err != nil { if _, err := certutil.NewPool(rootCAFile); err != nil {
//nolint:logcheck // The decision to log this instead of returning an error goes back to ~2016. It's part of the client-go API now, so not changing it just to support contextual logging.
klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err) klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
} else { } else {
tlsClientConfig.CAFile = rootCAFile tlsClientConfig.CAFile = rootCAFile

View File

@ -21,8 +21,6 @@ import (
"net/http" "net/http"
"sync" "sync"
"k8s.io/klog/v2"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api" clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
) )
@ -65,7 +63,10 @@ func RegisterAuthProviderPlugin(name string, plugin Factory) error {
if _, found := plugins[name]; found { if _, found := plugins[name]; found {
return fmt.Errorf("auth Provider Plugin %q was registered twice", name) return fmt.Errorf("auth Provider Plugin %q was registered twice", name)
} }
klog.V(4).Infof("Registered Auth Provider Plugin %q", name) // RegisterAuthProviderPlugin gets called during the init phase before
// logging is initialized and therefore should not emit logs. If you
// need this message for debugging something, then uncomment it.
// klog.V(4).Infof("Registered Auth Provider Plugin %q", name)
plugins[name] = plugin plugins[name] = plugin
return nil return nil
} }

View File

@ -54,7 +54,7 @@ import (
"k8s.io/utils/clock" "k8s.io/utils/clock"
) )
var ( const (
// longThrottleLatency defines threshold for logging requests. All requests being // longThrottleLatency defines threshold for logging requests. All requests being
// throttled (via the provided rateLimiter) for more than longThrottleLatency will // throttled (via the provided rateLimiter) for more than longThrottleLatency will
// be logged. // be logged.
@ -676,21 +676,17 @@ func (r *Request) tryThrottleWithInfo(ctx context.Context, retryInfo string) err
} }
latency := time.Since(now) latency := time.Since(now)
var message string
switch {
case len(retryInfo) > 0:
message = fmt.Sprintf("Waited for %v, %s - request: %s:%s", latency, retryInfo, r.verb, r.URL().String())
default:
message = fmt.Sprintf("Waited for %v due to client-side throttling, not priority and fairness, request: %s:%s", latency, r.verb, r.URL().String())
}
if latency > longThrottleLatency { if latency > longThrottleLatency {
klog.V(3).Info(message) if retryInfo == "" {
} retryInfo = "client-side throttling, not priority and fairness"
if latency > extraLongThrottleLatency { }
// If the rate limiter latency is very high, the log message should be printed at a higher log level, klog.FromContext(ctx).V(3).Info("Waited before sending request", "delay", latency, "reason", retryInfo, "verb", r.verb, "URL", r.URL())
// but we use a throttled logger to prevent spamming.
globalThrottledLogger.Infof("%s", message) if latency > extraLongThrottleLatency {
// If the rate limiter latency is very high, the log message should be printed at a higher log level,
// but we use a throttled logger to prevent spamming.
globalThrottledLogger.info(klog.FromContext(ctx), "Waited before sending request", "delay", latency, "reason", retryInfo, "verb", r.verb, "URL", r.URL())
}
} }
metrics.RateLimiterLatency.Observe(ctx, r.verb, r.finalURLTemplate(), latency) metrics.RateLimiterLatency.Observe(ctx, r.verb, r.finalURLTemplate(), latency)
@ -702,7 +698,7 @@ func (r *Request) tryThrottle(ctx context.Context) error {
} }
type throttleSettings struct { type throttleSettings struct {
logLevel klog.Level logLevel int
minLogInterval time.Duration minLogInterval time.Duration
lastLogTime time.Time lastLogTime time.Time
@ -727,9 +723,9 @@ var globalThrottledLogger = &throttledLogger{
}, },
} }
func (b *throttledLogger) attemptToLog() (klog.Level, bool) { func (b *throttledLogger) attemptToLog(logger klog.Logger) (int, bool) {
for _, setting := range b.settings { for _, setting := range b.settings {
if bool(klog.V(setting.logLevel).Enabled()) { if bool(logger.V(setting.logLevel).Enabled()) {
// Return early without write locking if possible. // Return early without write locking if possible.
if func() bool { if func() bool {
setting.lock.RLock() setting.lock.RLock()
@ -751,9 +747,9 @@ func (b *throttledLogger) attemptToLog() (klog.Level, bool) {
// Infof will write a log message at each logLevel specified by the receiver's throttleSettings // Infof will write a log message at each logLevel specified by the receiver's throttleSettings
// as long as it hasn't written a log message more recently than minLogInterval. // as long as it hasn't written a log message more recently than minLogInterval.
func (b *throttledLogger) Infof(message string, args ...interface{}) { func (b *throttledLogger) info(logger klog.Logger, message string, kv ...any) {
if logLevel, ok := b.attemptToLog(); ok { if logLevel, ok := b.attemptToLog(logger); ok {
klog.V(logLevel).Infof(message, args...) logger.V(logLevel).Info(message, kv...)
} }
} }
@ -1000,7 +996,7 @@ func (r *Request) newStreamWatcher(ctx context.Context, resp *http.Response) (wa
contentType := resp.Header.Get("Content-Type") contentType := resp.Header.Get("Content-Type")
mediaType, params, err := mime.ParseMediaType(contentType) mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil { if err != nil {
klog.V(4).Infof("Unexpected content type from the server: %q: %v", contentType, err) klog.FromContext(ctx).V(4).Info("Unexpected content type from the server", "contentType", contentType, "err", err)
} }
objectDecoder, streamingSerializer, framer, err := r.contentConfig.Negotiator.StreamDecoder(mediaType, params) objectDecoder, streamingSerializer, framer, err := r.contentConfig.Negotiator.StreamDecoder(mediaType, params)
if err != nil { if err != nil {
@ -1202,7 +1198,7 @@ func (r *Request) request(ctx context.Context, fn func(*http.Request, *http.Resp
}() }()
if r.err != nil { if r.err != nil {
klog.V(4).Infof("Error in request: %v", r.err) klog.FromContext(ctx).V(4).Info("Error in request", "err", r.err)
return r.err return r.err
} }
@ -1303,7 +1299,7 @@ func (r *Request) Do(ctx context.Context) Result {
result = r.transformResponse(ctx, resp, req) result = r.transformResponse(ctx, resp, req)
}) })
if err != nil { if err != nil {
return Result{err: err} return Result{err: err, loggingCtx: context.WithoutCancel(ctx)}
} }
if result.err == nil || len(result.body) > 0 { if result.err == nil || len(result.body) > 0 {
metrics.ResponseSize.Observe(ctx, r.verb, r.URL().Host, float64(len(result.body))) metrics.ResponseSize.Observe(ctx, r.verb, r.URL().Host, float64(len(result.body)))
@ -1350,16 +1346,18 @@ func (r *Request) transformResponse(ctx context.Context, resp *http.Response, re
// 2. Apiserver sends back the headers and then part of the body // 2. Apiserver sends back the headers and then part of the body
// 3. Apiserver closes connection. // 3. Apiserver closes connection.
// 4. client-go should catch this and return an error. // 4. client-go should catch this and return an error.
klog.V(2).Infof("Stream error %#v when reading response body, may be caused by closed connection.", err) klog.FromContext(ctx).V(2).Info("Stream error when reading response body, may be caused by closed connection", "err", err)
streamErr := fmt.Errorf("stream error when reading response body, may be caused by closed connection. Please retry. Original error: %w", err) streamErr := fmt.Errorf("stream error when reading response body, may be caused by closed connection. Please retry. Original error: %w", err)
return Result{ return Result{
err: streamErr, err: streamErr,
loggingCtx: context.WithoutCancel(ctx),
} }
default: default:
klog.Errorf("Unexpected error when reading response body: %v", err) klog.FromContext(ctx).Error(err, "Unexpected error when reading response body")
unexpectedErr := fmt.Errorf("unexpected error when reading response body. Please retry. Original error: %w", err) unexpectedErr := fmt.Errorf("unexpected error when reading response body. Please retry. Original error: %w", err)
return Result{ return Result{
err: unexpectedErr, err: unexpectedErr,
loggingCtx: context.WithoutCancel(ctx),
} }
} }
} }
@ -1377,7 +1375,7 @@ func (r *Request) transformResponse(ctx context.Context, resp *http.Response, re
var err error var err error
mediaType, params, err := mime.ParseMediaType(contentType) mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil { if err != nil {
return Result{err: errors.NewInternalError(err)} return Result{err: errors.NewInternalError(err), loggingCtx: context.WithoutCancel(ctx)}
} }
decoder, err = r.contentConfig.Negotiator.Decoder(mediaType, params) decoder, err = r.contentConfig.Negotiator.Decoder(mediaType, params)
if err != nil { if err != nil {
@ -1386,13 +1384,14 @@ func (r *Request) transformResponse(ctx context.Context, resp *http.Response, re
case resp.StatusCode == http.StatusSwitchingProtocols: case resp.StatusCode == http.StatusSwitchingProtocols:
// no-op, we've been upgraded // no-op, we've been upgraded
case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent: case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent:
return Result{err: r.transformUnstructuredResponseError(resp, req, body)} return Result{err: r.transformUnstructuredResponseError(resp, req, body), loggingCtx: context.WithoutCancel(ctx)}
} }
return Result{ return Result{
body: body, body: body,
contentType: contentType, contentType: contentType,
statusCode: resp.StatusCode, statusCode: resp.StatusCode,
warnings: handleWarnings(ctx, resp.Header, r.warningHandler), warnings: handleWarnings(ctx, resp.Header, r.warningHandler),
loggingCtx: context.WithoutCancel(ctx),
} }
} }
} }
@ -1412,6 +1411,7 @@ func (r *Request) transformResponse(ctx context.Context, resp *http.Response, re
decoder: decoder, decoder: decoder,
err: err, err: err,
warnings: handleWarnings(ctx, resp.Header, r.warningHandler), warnings: handleWarnings(ctx, resp.Header, r.warningHandler),
loggingCtx: context.WithoutCancel(ctx),
} }
} }
@ -1421,6 +1421,7 @@ func (r *Request) transformResponse(ctx context.Context, resp *http.Response, re
statusCode: resp.StatusCode, statusCode: resp.StatusCode,
decoder: decoder, decoder: decoder,
warnings: handleWarnings(ctx, resp.Header, r.warningHandler), warnings: handleWarnings(ctx, resp.Header, r.warningHandler),
loggingCtx: context.WithoutCancel(ctx),
} }
} }
@ -1552,6 +1553,10 @@ type Result struct {
err error err error
statusCode int statusCode int
// Log calls in Result methods use the same context for logging as the
// method which created the Result. This context has no cancellation.
loggingCtx context.Context
decoder runtime.Decoder decoder runtime.Decoder
} }
@ -1656,7 +1661,11 @@ func (r Result) Error() error {
// to be backwards compatible with old servers that do not return a version, default to "v1" // to be backwards compatible with old servers that do not return a version, default to "v1"
out, _, err := r.decoder.Decode(r.body, &schema.GroupVersionKind{Version: "v1"}, nil) out, _, err := r.decoder.Decode(r.body, &schema.GroupVersionKind{Version: "v1"}, nil)
if err != nil { if err != nil {
klog.V(5).Infof("body was not decodable (unable to check for Status): %v", err) ctx := r.loggingCtx
if ctx == nil {
ctx = context.Background()
}
klog.FromContext(ctx).V(5).Info("Body was not decodable (unable to check for Status)", "err", err)
return r.err return r.err
} }
switch t := out.(type) { switch t := out.(type) {

View File

@ -2476,6 +2476,7 @@ func TestRequestPreflightCheck(t *testing.T) {
} }
func TestThrottledLogger(t *testing.T) { func TestThrottledLogger(t *testing.T) {
logger := klog.Background()
now := time.Now() now := time.Now()
oldClock := globalThrottledLogger.clock oldClock := globalThrottledLogger.clock
defer func() { defer func() {
@ -2490,7 +2491,7 @@ func TestThrottledLogger(t *testing.T) {
wg.Add(10) wg.Add(10)
for j := 0; j < 10; j++ { for j := 0; j < 10; j++ {
go func() { go func() {
if _, ok := globalThrottledLogger.attemptToLog(); ok { if _, ok := globalThrottledLogger.attemptToLog(logger); ok {
logMessages++ logMessages++
} }
wg.Done() wg.Done()
@ -4175,6 +4176,7 @@ warnings.go] "Warning: warning 2" logger="TestLogger"
} }
for name, tc := range testcases { for name, tc := range testcases {
//nolint:logcheck // Intentionally testing with plain klog here.
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
state := klog.CaptureState() state := klog.CaptureState()
defer state.Restore() defer state.Restore()

View File

@ -231,7 +231,7 @@ func (r *withRetry) Before(ctx context.Context, request *Request) error {
return err return err
} }
klog.V(4).Infof("Got a Retry-After %s response for attempt %d to %v", r.retryAfter.Wait, r.retryAfter.Attempt, request.URL().String()) klog.FromContext(ctx).V(4).Info("Got a Retry-After response", "delay", r.retryAfter.Wait, "attempt", r.retryAfter.Attempt, "url", request.URL())
return nil return nil
} }