github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/pkg/util/logs/vzlog/vzlog.go (about) 1 // Copyright (c) 2022, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package vzlog 5 6 import ( 7 "errors" 8 "fmt" 9 "sync" 10 "time" 11 12 vzlogInit "github.com/verrazzano/verrazzano-monitoring-operator/pkg/util/logs" 13 14 "go.uber.org/zap" 15 ) 16 17 // ResourceConfig is the configuration of a logger for a resource that is being reconciled 18 type ResourceConfig struct { 19 // Name is the name of the resource 20 Name string 21 22 // Namespace is the namespace of the resource 23 Namespace string 24 25 // ID is the resource uid 26 ID string 27 28 // Generation is the resource generation 29 Generation int64 30 31 // Controller name is the name of the controller 32 ControllerName string 33 } 34 35 // LogContextMap contains a map of LogContext objects 36 var LogContextMap = make(map[string]*LogContext) 37 38 // Lock for map access 39 var lock sync.Mutex 40 41 // SugaredLogger is a logger interface that provides base logging 42 type SugaredLogger interface { 43 // Debug logs a message at Debug log level 44 Debug(args ...interface{}) 45 46 // Debugf formats a message and logs it once at Debug log level 47 Debugf(template string, args ...interface{}) 48 49 // Info logs a message at Info log level 50 Info(args ...interface{}) 51 52 // Infof formats a message and logs it once at Info log level 53 Infof(template string, args ...interface{}) 54 55 // Error logs a message at Error log level 56 Error(args ...interface{}) 57 58 // Errorf formats a message and logs it once at Error log level 59 Errorf(template string, args ...interface{}) 60 } 61 62 // ProgressLogger is a logger interface that provides Verrazzano base and progress logging 63 type ProgressLogger interface { 64 // Once logs a message once at Info log level 65 Once(args ...interface{}) 66 67 // Oncef formats a message and logs it once at Info log level 68 Oncef(template string, args ...interface{}) 69 70 // Progress logs a message periodically at Info log level 71 Progress(args ...interface{}) 72 73 // Progress formats a message and logs it periodically at Info log level 74 Progressf(template string, args ...interface{}) 75 76 // ErrorNewErr logs and error, then returns the error 77 ErrorNewErr(args ...interface{}) error 78 79 // ErrorfNewErr formats an error, logs it, then returns the formatted error 80 ErrorfNewErr(template string, args ...interface{}) error 81 82 // ErrorfThrottledNewErr Records a formatted error message throttled at the ProgressLogger frequency then returns the formatted error 83 ErrorfThrottledNewErr(template string, args ...interface{}) error 84 85 // ErrorfThrottled Records a formatted error message throttled at the ProgressLogger frequency 86 ErrorfThrottled(template string, args ...interface{}) 87 88 // SetFrequency sets the logging frequency of a progress message 89 SetFrequency(secs int) VerrazzanoLogger 90 } 91 92 // VerrazzanoLogger is a logger interface that provides sugared and progress logging 93 type VerrazzanoLogger interface { 94 SugaredLogger 95 ProgressLogger 96 97 // SetZapLogger sets the zap logger 98 SetZapLogger(zap *zap.SugaredLogger) 99 100 // GetZapLogger gets the zap logger 101 GetZapLogger() *zap.SugaredLogger 102 103 // GetRootZapLogger gets the root zap logger 104 GetRootZapLogger() *zap.SugaredLogger 105 106 // GetContext gets the log context 107 GetContext() *LogContext 108 } 109 110 // LogContext is the log context for a given resource. 111 // This logger can be used to manage logging for the resource and sub-resources, such as components 112 type LogContext struct { 113 // loggerMap contains a map of verrazzanoLogger objects 114 loggerMap map[string]*verrazzanoLogger 115 116 // Generation is the generation of the resource being logged 117 Generation int64 118 119 // RootZapLogger is the zap SugaredLogger for the resource. Component loggers are derived from this. 120 RootZapLogger *zap.SugaredLogger 121 } 122 123 // verrazzanoLogger implements the VerrazzanoLogger interface 124 // Notice that the SugaredLogger methods (Debug, Info, Error) have another level 125 // of indirection (Debug2). This is because it has to match the 2 levels of call stack 126 // used by Progrss and Once, with both call doLog. We setup the logger to skip 2 calls 127 // in the stack frame when logging, so the caller file/line is displayed. Otherwise, you 128 // would see "vzlog.go/line xyz" 129 type verrazzanoLogger struct { 130 // context is the LogContext 131 context *LogContext 132 133 // zapLogger is the zap SugaredLogger 134 zapLogger *zap.SugaredLogger 135 136 // sLogger is the interface used to log 137 sLogger SugaredLogger 138 139 // frequency between logs in seconds 140 frequencySecs int 141 142 // trashMessages is a set of log messages that can never be displayed again 143 trashMessages map[string]bool 144 145 // progressHistory is a map of progress log messages that are displayed periodically 146 progressHistory map[string]*logEvent 147 } 148 149 // lastLog tracks the last message logged for progress messages 150 type logEvent struct { 151 // logTime is the last time the message was logged 152 logTime *time.Time 153 154 // msgLogged is the message that was logged 155 msgLogged string 156 } 157 158 // DefaultLogger ensures the default logger exists. This is typically used for testing 159 func DefaultLogger() VerrazzanoLogger { 160 return EnsureContext("default").EnsureLogger("default", zap.S(), zap.S()) 161 } 162 163 // EnsureResourceLogger ensures that a logger exists for a specific generation of a Kubernetes resource. 164 // When a resource is getting reconciled, the status may frequently get updated during 165 // the reconciliation. This is the case for the Verrazzano resource. As a result, 166 // the controller-runtime queue gets filled with updated instances of a resource that 167 // have the same generation. The side-effect is that after a resource is completely reconciled, 168 // the controller Reconcile method may still be called many times. In this case, the existing 169 // context must be used so that 'once' and 'progress' messages don't start from a new context, 170 // causing them to be displayed when they shouldn't. This mehod ensures that the same 171 // logger is used for a given resource and generation. 172 func EnsureResourceLogger(config *ResourceConfig) (VerrazzanoLogger, error) { 173 // Build a logger, skipping 2 call frames so that the correct caller file/line is displayed in the log. 174 // If the callerSkip was 0, then you would see the vzlog.go/line# instead of the file/line of the caller 175 // that called the VerrazzanoLogger 176 zaplog, err := vzlogInit.BuildZapLogger(2) 177 if err != nil { 178 // This is a fatal error which should never happen 179 return nil, errors.New("Failed initializing logger for controller") 180 } 181 182 // Ensure a Verrazzano logger exists, using zap SugaredLogger as the underlying logger. 183 zaplog = zaplog.With(vzlogInit.FieldResourceNamespace, config.Namespace, vzlogInit.FieldResourceName, 184 config.Name, vzlogInit.FieldController, config.ControllerName) 185 186 // Get a log context. If the generation doesn't match then delete it and 187 // create a new one. This will ensure we have a new context for a new 188 // generation of a resource 189 context := EnsureContext(config.ID) 190 if context.Generation != 0 && context.Generation != config.Generation { 191 DeleteLogContext(config.ID) 192 context = EnsureContext(config.ID) 193 } 194 context.Generation = config.Generation 195 context.RootZapLogger = zaplog 196 197 // Finally, get the logger using this context. 198 logger := context.EnsureLogger("default", zaplog, zaplog) 199 return logger, nil 200 } 201 202 // EnsureContext ensures that a LogContext exists 203 // The key must be unique for the process, for example a namespace/name combo. 204 func EnsureContext(key string) *LogContext { 205 lock.Lock() 206 defer lock.Unlock() 207 log, ok := LogContextMap[key] 208 if !ok { 209 log = &LogContext{ 210 loggerMap: make(map[string]*verrazzanoLogger), 211 } 212 LogContextMap[key] = log 213 } 214 return log 215 } 216 217 // DeleteLogContext deletes the LogContext for the given key 218 func DeleteLogContext(key string) { 219 lock.Lock() 220 defer lock.Unlock() 221 _, ok := LogContextMap[key] 222 if ok { 223 delete(LogContextMap, key) 224 } 225 } 226 227 // EnsureLogger ensures that a new VerrazzanoLogger exists for the given key 228 func (c *LogContext) EnsureLogger(key string, sLogger SugaredLogger, zap *zap.SugaredLogger) VerrazzanoLogger { 229 lock.Lock() 230 defer lock.Unlock() 231 log, ok := c.loggerMap[key] 232 if !ok { 233 log = &verrazzanoLogger{ 234 context: c, 235 frequencySecs: 60, 236 trashMessages: make(map[string]bool), 237 progressHistory: make(map[string]*logEvent), 238 } 239 c.loggerMap[key] = log 240 } 241 242 // Always replace the zap logger so that we get a clean set of 243 // with clauses 244 log.sLogger = sLogger 245 log.zapLogger = zap 246 if log.context.RootZapLogger == nil { 247 log.context.RootZapLogger = zap 248 } 249 250 return log 251 } 252 253 // Oncef formats a message and logs it once 254 func (v *verrazzanoLogger) Oncef(template string, args ...interface{}) { 255 s := fmt.Sprintf(template, args...) 256 v.doLog(true, s) 257 } 258 259 // Once logs a message once 260 func (v *verrazzanoLogger) Once(args ...interface{}) { 261 v.doLog(true, args...) 262 } 263 264 // Progressf formats a message and logs it periodically 265 func (v *verrazzanoLogger) Progressf(template string, args ...interface{}) { 266 s := fmt.Sprintf(template, args...) 267 v.doLog(false, s) 268 } 269 270 // Progress logs a message periodically 271 func (v *verrazzanoLogger) Progress(args ...interface{}) { 272 v.doLog(false, args...) 273 } 274 275 // doLog logs an info message either now or sometime in the future. The message 276 // will be logged only if it is new or the next log time has been reached. 277 // This function allows a controller to constantly log info messages very frequently, 278 // such as "waiting for Verrazzano secret", but the message will only be logged 279 // once periodically according to the frequency (e.g. once every 60 seconds). 280 // If the log message is new or has changed then it is logged immediately. 281 func (v *verrazzanoLogger) doLog(once bool, args ...interface{}) { 282 msg := fmt.Sprint(args...) 283 cacheUpdated := v.shouldLogMessage(once, msg) 284 if cacheUpdated { 285 v.sLogger.Info(msg) 286 } 287 } 288 289 // doError Logs an error message first checking against the log cache; same behavior as doLog() except that messages 290 // are recorded as errors at the throttling frequency. Errors are never once-only. 291 func (v *verrazzanoLogger) doError(args ...interface{}) { 292 msg := fmt.Sprint(args...) 293 doLog := v.shouldLogMessage(false, msg) 294 if doLog { 295 v.sLogger.Error(msg) 296 } 297 } 298 299 // shouldLogMessage Checks candidate log message against the cache and returns true if that message should be recorded in the log. 300 // 301 // A message should be recorded when 302 // - A message is newly added to the cache (seen for the first time) 303 // - A message is throttled, but it has not exceeded its frequency threshold since the last occurrence 304 func (v *verrazzanoLogger) shouldLogMessage(once bool, msg string) bool { 305 lock.Lock() 306 defer lock.Unlock() 307 308 // If the message is in the trash, that means it should never be logged again. 309 _, ok := v.trashMessages[msg] 310 if ok { 311 return false 312 } 313 314 // If this is log "once", log it and save in trash so it is never logged again, then return 315 if once { 316 v.trashMessages[msg] = true 317 return true 318 } 319 320 // If this message has already been logged, then check if time to log again 321 now := time.Now() 322 history := v.progressHistory[msg] 323 if history != nil { 324 waitSecs := time.Duration(v.frequencySecs) * time.Second 325 nextLogTime := history.logTime.Add(waitSecs) 326 327 // Log now if the message wait time exceeded 328 if now.Equal(nextLogTime) || now.After(nextLogTime) { 329 history.logTime = &now 330 return true 331 } 332 } else { 333 // This is a new message log it 334 v.progressHistory[msg] = &logEvent{ 335 logTime: &now, 336 msgLogged: msg, 337 } 338 return true 339 } 340 return false 341 } 342 343 // SetFrequency sets the log frequency 344 func (v *verrazzanoLogger) SetFrequency(secs int) VerrazzanoLogger { 345 v.frequencySecs = secs 346 return v 347 } 348 349 // SetZapLogger sets the zap logger 350 func (v *verrazzanoLogger) SetZapLogger(zap *zap.SugaredLogger) { 351 v.zapLogger = zap 352 v.sLogger = zap 353 } 354 355 // GetZapLogger zap logger gets a clone of the zap logger 356 func (v *verrazzanoLogger) GetZapLogger() *zap.SugaredLogger { 357 return v.zapLogger 358 } 359 360 // GetRootZapLogger gets the root zap logger at the context level 361 func (v *verrazzanoLogger) GetRootZapLogger() *zap.SugaredLogger { 362 return v.context.RootZapLogger 363 } 364 365 // EnsureContext gets the logger context 366 func (v *verrazzanoLogger) GetContext() *LogContext { 367 return v.context 368 } 369 370 // ErrorNewErr logs an error, then returns it. 371 func (v *verrazzanoLogger) ErrorNewErr(args ...interface{}) error { 372 s := fmt.Sprint(args...) 373 err := errors.New(s) 374 v.Error2(err) 375 return err 376 } 377 378 // ErrorfNewErr formats an error, logs it, then returns it. 379 func (v *verrazzanoLogger) ErrorfNewErr(template string, args ...interface{}) error { 380 err := fmt.Errorf(template, args...) 381 v.Error2(err) 382 return err 383 } 384 385 func (v *verrazzanoLogger) ErrorfThrottledNewErr(template string, args ...interface{}) error { 386 err := fmt.Errorf(template, args...) 387 v.doError(err.Error()) 388 return err 389 } 390 391 func (v *verrazzanoLogger) ErrorfThrottled(template string, args ...interface{}) { 392 s := fmt.Sprintf(template, args...) 393 v.doError(s) 394 } 395 396 // Debug is a wrapper for SugaredLogger Debug 397 func (v *verrazzanoLogger) Debug(args ...interface{}) { 398 v.Debug2(args...) 399 } 400 401 // Debugf is a wrapper for SugaredLogger Debugf 402 func (v *verrazzanoLogger) Debugf(template string, args ...interface{}) { 403 v.Debugf2(template, args...) 404 } 405 406 // Info is a wrapper for SugaredLogger Info 407 func (v *verrazzanoLogger) Info(args ...interface{}) { 408 v.Info2(args...) 409 } 410 411 // Infof is a wrapper for SugaredLogger Infof 412 func (v *verrazzanoLogger) Infof(template string, args ...interface{}) { 413 v.Infof2(template, args...) 414 } 415 416 // Error is a wrapper for SugaredLogger Error 417 func (v *verrazzanoLogger) Error(args ...interface{}) { 418 v.Error2(args...) 419 } 420 421 // Errorf is a wrapper for SugaredLogger Errorf 422 func (v *verrazzanoLogger) Errorf(template string, args ...interface{}) { 423 v.Errorf2(template, args...) 424 } 425 426 // Debug is a wrapper for SugaredLogger Debug 427 func (v *verrazzanoLogger) Debug2(args ...interface{}) { 428 v.sLogger.Debug(args...) 429 } 430 431 // Debugf is a wrapper for SugaredLogger Debugf 432 func (v *verrazzanoLogger) Debugf2(template string, args ...interface{}) { 433 v.sLogger.Debugf(template, args...) 434 } 435 436 // Info is a wrapper for SugaredLogger Info 437 func (v *verrazzanoLogger) Info2(args ...interface{}) { 438 v.sLogger.Info(args...) 439 } 440 441 // Infof is a wrapper for SugaredLogger Infof 442 func (v *verrazzanoLogger) Infof2(template string, args ...interface{}) { 443 v.sLogger.Infof(template, args...) 444 } 445 446 // Error is a wrapper for SugaredLogger Error 447 func (v *verrazzanoLogger) Error2(args ...interface{}) { 448 v.sLogger.Error(args...) 449 } 450 451 // Errorf is a wrapper for SugaredLogger Errorf 452 func (v *verrazzanoLogger) Errorf2(template string, args ...interface{}) { 453 v.sLogger.Errorf(template, args...) 454 }