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  }