1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package klog 18 19 import ( 20 "context" 21 22 "github.com/go-logr/logr" 23 ) 24 25 // This file provides the implementation of 26 // https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/1602-structured-logging 27 // 28 // SetLogger and ClearLogger were originally added to klog.go and got moved 29 // here. Contextual logging adds a way to retrieve a Logger for direct logging 30 // without the logging calls in klog.go. 31 // 32 // The global variables are expected to be modified only during sequential 33 // parts of a program (init, serial tests) and therefore are not protected by 34 // mutex locking. 35 36 var ( 37 // klogLogger is used as fallback for logging through the normal klog code 38 // when no Logger is set. 39 klogLogger logr.Logger = logr.New(&klogger{}) 40 ) 41 42 // SetLogger sets a Logger implementation that will be used as backing 43 // implementation of the traditional klog log calls. klog will do its own 44 // verbosity checks before calling logger.V().Info. logger.Error is always 45 // called, regardless of the klog verbosity settings. 46 // 47 // If set, all log lines will be suppressed from the regular output, and 48 // redirected to the logr implementation. 49 // Use as: 50 // 51 // ... 52 // klog.SetLogger(zapr.NewLogger(zapLog)) 53 // 54 // To remove a backing logr implemention, use ClearLogger. Setting an 55 // empty logger with SetLogger(logr.Logger{}) does not work. 56 // 57 // Modifying the logger is not thread-safe and should be done while no other 58 // goroutines invoke log calls, usually during program initialization. 59 func SetLogger(logger logr.Logger) { 60 SetLoggerWithOptions(logger) 61 } 62 63 // SetLoggerWithOptions is a more flexible version of SetLogger. Without 64 // additional options, it behaves exactly like SetLogger. By passing 65 // ContextualLogger(true) as option, it can be used to set a logger that then 66 // will also get called directly by applications which retrieve it via 67 // FromContext, Background, or TODO. 68 // 69 // Supporting direct calls is recommended because it avoids the overhead of 70 // routing log entries through klogr into klog and then into the actual Logger 71 // backend. 72 func SetLoggerWithOptions(logger logr.Logger, opts ...LoggerOption) { 73 logging.loggerOptions = loggerOptions{} 74 for _, opt := range opts { 75 opt(&logging.loggerOptions) 76 } 77 logging.logger = &logWriter{ 78 Logger: logger, 79 writeKlogBuffer: logging.loggerOptions.writeKlogBuffer, 80 } 81 } 82 83 // ContextualLogger determines whether the logger passed to 84 // SetLoggerWithOptions may also get called directly. Such a logger cannot rely 85 // on verbosity checking in klog. 86 func ContextualLogger(enabled bool) LoggerOption { 87 return func(o *loggerOptions) { 88 o.contextualLogger = enabled 89 } 90 } 91 92 // FlushLogger provides a callback for flushing data buffered by the logger. 93 func FlushLogger(flush func()) LoggerOption { 94 return func(o *loggerOptions) { 95 o.flush = flush 96 } 97 } 98 99 // WriteKlogBuffer sets a callback that will be invoked by klog to write output 100 // produced by non-structured log calls like Infof. 101 // 102 // The buffer will contain exactly the same data that klog normally would write 103 // into its own output stream(s). In particular this includes the header, if 104 // klog is configured to write one. The callback then can divert that data into 105 // its own output streams. The buffer may or may not end in a line break. 106 // 107 // Without such a callback, klog will call the logger's Info or Error method 108 // with just the message string (i.e. no header). 109 func WriteKlogBuffer(write func([]byte)) LoggerOption { 110 return func(o *loggerOptions) { 111 o.writeKlogBuffer = write 112 } 113 } 114 115 // LoggerOption implements the functional parameter paradigm for 116 // SetLoggerWithOptions. 117 type LoggerOption func(o *loggerOptions) 118 119 type loggerOptions struct { 120 contextualLogger bool 121 flush func() 122 writeKlogBuffer func([]byte) 123 } 124 125 // logWriter combines a logger (always set) with a write callback (optional). 126 type logWriter struct { 127 Logger 128 writeKlogBuffer func([]byte) 129 } 130 131 // ClearLogger removes a backing Logger implementation if one was set earlier 132 // with SetLogger. 133 // 134 // Modifying the logger is not thread-safe and should be done while no other 135 // goroutines invoke log calls, usually during program initialization. 136 func ClearLogger() { 137 logging.logger = nil 138 logging.loggerOptions = loggerOptions{} 139 } 140 141 // EnableContextualLogging controls whether contextual logging is enabled. 142 // By default it is enabled. When disabled, FromContext avoids looking up 143 // the logger in the context and always returns the global logger. 144 // LoggerWithValues, LoggerWithName, and NewContext become no-ops 145 // and return their input logger respectively context. This may be useful 146 // to avoid the additional overhead for contextual logging. 147 // 148 // This must be called during initialization before goroutines are started. 149 func EnableContextualLogging(enabled bool) { 150 logging.contextualLoggingEnabled = enabled 151 } 152 153 // FromContext retrieves a logger set by the caller or, if not set, 154 // falls back to the program's global logger (a Logger instance or klog 155 // itself). 156 func FromContext(ctx context.Context) Logger { 157 if logging.contextualLoggingEnabled { 158 if logger, err := logr.FromContext(ctx); err == nil { 159 return logger 160 } 161 } 162 163 return Background() 164 } 165 166 // TODO can be used as a last resort by code that has no means of 167 // receiving a logger from its caller. FromContext or an explicit logger 168 // parameter should be used instead. 169 func TODO() Logger { 170 return Background() 171 } 172 173 // Background retrieves the fallback logger. It should not be called before 174 // that logger was initialized by the program and not by code that should 175 // better receive a logger via its parameters. TODO can be used as a temporary 176 // solution for such code. 177 func Background() Logger { 178 if logging.loggerOptions.contextualLogger { 179 // Is non-nil because logging.loggerOptions.contextualLogger is 180 // only true if a logger was set. 181 return logging.logger.Logger 182 } 183 184 return klogLogger 185 } 186 187 // LoggerWithValues returns logger.WithValues(...kv) when 188 // contextual logging is enabled, otherwise the logger. 189 func LoggerWithValues(logger Logger, kv ...interface{}) Logger { 190 if logging.contextualLoggingEnabled { 191 return logger.WithValues(kv...) 192 } 193 return logger 194 } 195 196 // LoggerWithName returns logger.WithName(name) when contextual logging is 197 // enabled, otherwise the logger. 198 func LoggerWithName(logger Logger, name string) Logger { 199 if logging.contextualLoggingEnabled { 200 return logger.WithName(name) 201 } 202 return logger 203 } 204 205 // NewContext returns logr.NewContext(ctx, logger) when 206 // contextual logging is enabled, otherwise ctx. 207 func NewContext(ctx context.Context, logger Logger) context.Context { 208 if logging.contextualLoggingEnabled { 209 return logr.NewContext(ctx, logger) 210 } 211 return ctx 212 } 213