github.com/grailbio/base@v0.0.11/common/log/logger.go (about)

     1  package log
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"runtime"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/google/uuid"
    12  	"go.uber.org/zap"
    13  	"go.uber.org/zap/zapcore"
    14  	v23 "v.io/v23"
    15  	vcontext "v.io/v23/context"
    16  )
    17  
    18  const (
    19  	// DebugLevel logs are typically voluminous.
    20  	DebugLevel = zapcore.DebugLevel
    21  	// InfoLevel is the default logging priority.
    22  	InfoLevel = zapcore.InfoLevel
    23  	// WarnLevel logs are more important than Info, but don't need individual human review.
    24  	WarnLevel = zapcore.WarnLevel
    25  	// ErrorLevel logs are high-priority.
    26  	// Applications running smoothly shouldn't generate any error-level logs.
    27  	ErrorLevel = zapcore.ErrorLevel
    28  	// FatalLevel logs a message, then calls os.Exit(1).
    29  	FatalLevel = zapcore.FatalLevel
    30  	// RFC3339TrailingNano is RFC3339 format with trailing nanoseconds precision.
    31  	RFC3339TrailingNano = "2006-01-02T15:04:05.000000000Z07:00"
    32  	// LOG_LEVEL_ENV_VAR is the environment variable name used to set logging level.
    33  	LOG_LEVEL_ENV_VAR = "LOG_LEVEL"
    34  )
    35  
    36  // contextFields is a list of context key-value pairs to be logged.
    37  // Key is the name of the field.
    38  // Value is the context key.
    39  var contextFields = map[string]interface{}{
    40  	"requestID": RequestIDContextKey,
    41  }
    42  
    43  var logLvls = map[string]zapcore.Level{
    44  	"debug": DebugLevel,
    45  	"DEBUG": DebugLevel,
    46  	"info":  InfoLevel,
    47  	"INFO":  InfoLevel,
    48  	"warn":  WarnLevel,
    49  	"WARN":  WarnLevel,
    50  	"error": ErrorLevel,
    51  	"ERROR": ErrorLevel,
    52  	"fatal": FatalLevel,
    53  	"FATAL": FatalLevel,
    54  }
    55  
    56  type Logger struct {
    57  	coreLogger *zap.SugaredLogger
    58  	// Additional information that may be unique to each service (e.g. order UUID for Ensemble orders)
    59  	defaultFields []interface{}
    60  	levelToLogger map[zapcore.Level]func(msg string, keysAndValues ...interface{})
    61  	now           func() time.Time
    62  }
    63  
    64  type Config struct {
    65  	OutputPaths []string
    66  	// note: setting the environment variable LOG_LEVEL will override Config.Level
    67  	Level zapcore.Level
    68  }
    69  
    70  func setDefaultLogLevelsMap(logger *Logger) *Logger {
    71  	logger.levelToLogger = map[zapcore.Level]func(msg string, keysAndValues ...interface{}){
    72  		DebugLevel: logger.coreLogger.Debugw,
    73  		InfoLevel:  logger.coreLogger.Infow,
    74  		WarnLevel:  logger.coreLogger.Warnw,
    75  		ErrorLevel: logger.coreLogger.Errorw,
    76  		FatalLevel: logger.coreLogger.Fatalw,
    77  	}
    78  	return logger
    79  }
    80  
    81  func NewLogger(config Config) *Logger {
    82  	return NewLoggerWithDefaultFields(config, []interface{}{})
    83  }
    84  
    85  // NewLogger creates a new logger instance.
    86  // defaultFields is a list of key-value pairs to be included in every log message.
    87  func NewLoggerWithDefaultFields(config Config, defaultFields []interface{}) *Logger {
    88  	if len(defaultFields)%2 != 0 {
    89  		danglingKey := defaultFields[len(defaultFields)-1]
    90  		defaultFields = defaultFields[:len(defaultFields)-1]
    91  		errLogger := NewLogger(config)
    92  		errLog := []interface{}{
    93  			"ignored", danglingKey,
    94  		}
    95  		logErr := errLogger.levelToLogger[ErrorLevel]
    96  		logErr("defaultFields contains a key without a value.", errLog...)
    97  	}
    98  	l := Logger{
    99  		coreLogger:    mustBuildLogger(config, zap.AddCallerSkip(2)),
   100  		defaultFields: defaultFields,
   101  		now:           time.Now,
   102  	}
   103  	return setDefaultLogLevelsMap(&l)
   104  }
   105  
   106  // NewLoggerFromCore allows the caller to pass in a zap.SugaredLogger into the logger.
   107  // This allows one to make unit test assertions about logs.
   108  func NewLoggerFromCore(lager *zap.SugaredLogger) *Logger {
   109  	l := Logger{
   110  		coreLogger: lager,
   111  		now:        time.Now,
   112  	}
   113  
   114  	return setDefaultLogLevelsMap(&l)
   115  }
   116  
   117  func (l *Logger) log(ctx context.Context, level zapcore.Level, callerSkip int, msg string, keysAndValues []interface{}) {
   118  	t := l.now()
   119  	// Add default fields
   120  	keysAndValues = append(keysAndValues, l.defaultFields...)
   121  	// If there is a dangling key (i.e. odd length keysAndValues), log an error and then
   122  	// drop the dangling key and log original message.
   123  	if len(keysAndValues)%2 != 0 {
   124  		danglingKey := keysAndValues[len(keysAndValues)-1]
   125  		keysAndValues = keysAndValues[:len(keysAndValues)-1]
   126  		errLog := withDefaultFields(ctx, callerSkip, t, "ignored", danglingKey)
   127  		logErr := l.levelToLogger[ErrorLevel]
   128  		logErr("Ignored key without a value.", errLog...)
   129  	}
   130  	// Add caller and timestamp fields
   131  	prefix := withDefaultFields(ctx, callerSkip, t)
   132  	// Add context logged fields
   133  	if ctx != nil {
   134  		for k, v := range contextFields {
   135  			if ctxVal := ctx.Value(v); ctxVal != nil {
   136  				prefix = append(prefix, k, ctxVal)
   137  			}
   138  		}
   139  	}
   140  	keysAndValues = append(prefix, keysAndValues...)
   141  	// Log at the appropriate level
   142  	logLevel := l.levelToLogger[level]
   143  	logLevel(msg, keysAndValues...)
   144  }
   145  
   146  // Debug logs a message, the key-value pairs defined in contextFields from ctx, and variadic key-value pairs.
   147  // If ctx is nil, all fields from contextFields will be omitted.
   148  // If ctx does not contain a key in contextFields, that field will be omitted.
   149  func (l *Logger) Debug(ctx context.Context, msg string, keysAndValues ...interface{}) {
   150  	l.Debugv(ctx, 1, msg, keysAndValues...)
   151  }
   152  
   153  // Debugf uses fmt.Sprintf to log a templated message and the key-value pairs defined in contextFields from ctx.
   154  // If ctx is nil, all fields from contextFields will be omitted.
   155  // If ctx does not contain a key in contextFields, that field will be omitted.
   156  func (l *Logger) Debugf(ctx context.Context, fs string, args ...interface{}) {
   157  	l.Debugv(ctx, 1, fmt.Sprintf(fs, args...))
   158  }
   159  
   160  // Debugv logs a message, the key-value pairs defined in contextFields from ctx, and variadic key-value pairs.
   161  // Caller stack field is skipped by skip levels.
   162  // If ctx is nil, all fields from contextFields will be omitted.
   163  // If ctx does not contain a key in contextFields, that field will be omitted.
   164  func (l *Logger) Debugv(ctx context.Context, skip int, msg string, keysAndValues ...interface{}) {
   165  	l.log(ctx, DebugLevel, skip, msg, keysAndValues)
   166  }
   167  
   168  // DebugNoCtx logs a message and variadic key-value pairs.
   169  func (l *Logger) DebugNoCtx(msg string, keysAndValues ...interface{}) {
   170  	l.Debugv(context.Background(), 1, msg, keysAndValues...)
   171  }
   172  
   173  // Info logs a message, the key-value pairs defined in contextFields from ctx, and variadic key-value pairs.
   174  // If ctx is nil, all fields from contextFields will be omitted.
   175  // If ctx does not contain a key in contextFields, that field will be omitted.
   176  func (l *Logger) Info(ctx context.Context, msg string, keysAndValues ...interface{}) {
   177  	l.Infov(ctx, 1, msg, keysAndValues...)
   178  }
   179  
   180  // Infof uses fmt.Sprintf to log a templated message and the key-value pairs defined in contextFields from ctx.
   181  // If ctx is nil, all fields from contextFields will be omitted.
   182  // If ctx does not contain a key in contextFields, that field will be omitted.
   183  func (l *Logger) Infof(ctx context.Context, fs string, args ...interface{}) {
   184  	l.Infov(ctx, 1, fmt.Sprintf(fs, args...))
   185  }
   186  
   187  // Infov logs a message, the key-value pairs defined in contextFields from ctx, and variadic key-value pairs.
   188  // Caller stack field is skipped by skip levels.
   189  // If ctx is nil, all fields from contextFields will be omitted.
   190  // If ctx does not contain a key in contextFields, that field will be omitted.
   191  func (l *Logger) Infov(ctx context.Context, skip int, msg string, keysAndValues ...interface{}) {
   192  	l.log(ctx, InfoLevel, skip, msg, keysAndValues)
   193  }
   194  
   195  // InfoNoCtx logs a message and variadic key-value pairs.
   196  func (l *Logger) InfoNoCtx(msg string, keysAndValues ...interface{}) {
   197  	l.Infov(context.Background(), 1, msg, keysAndValues...)
   198  }
   199  
   200  // Warn logs a message, the key-value pairs defined in contextFields from ctx, and variadic key-value pairs.
   201  // If ctx is nil, all fields from contextFields will be omitted.
   202  // If ctx does not contain a key in contextFields, that field will be omitted.
   203  func (l *Logger) Warn(ctx context.Context, msg string, keysAndValues ...interface{}) {
   204  	l.Warnv(ctx, 1, msg, keysAndValues...)
   205  }
   206  
   207  // Warnf uses fmt.Sprintf to log a templated message and the key-value pairs defined in contextFields from ctx.
   208  // If ctx is nil, all fields from contextFields will be omitted.
   209  // If ctx does not contain a key in contextFields, that field will be omitted.
   210  func (l *Logger) Warnf(ctx context.Context, fs string, args ...interface{}) {
   211  	l.Warnv(ctx, 1, fmt.Sprintf(fs, args...))
   212  }
   213  
   214  // Warnv logs a message, the key-value pairs defined in contextFields from ctx, and variadic key-value pairs.
   215  // Caller stack field is skipped by skip levels.
   216  // If ctx is nil, all fields from contextFields will be omitted.
   217  // If ctx does not contain a key in contextFields, that field will be omitted.
   218  func (l *Logger) Warnv(ctx context.Context, skip int, msg string, keysAndValues ...interface{}) {
   219  	l.log(ctx, WarnLevel, skip, msg, keysAndValues)
   220  }
   221  
   222  // WarnNoCtx logs a message and variadic key-value pairs.
   223  func (l *Logger) WarnNoCtx(msg string, keysAndValues ...interface{}) {
   224  	l.Warnv(context.Background(), 1, msg, keysAndValues...)
   225  }
   226  
   227  // Fatal logs a message, the key-value pairs defined in contextFields from ctx, and variadic key-value pairs.
   228  // If ctx is nil, all fields from contextFields will be omitted.
   229  // If ctx does not contain a key in contextFields, that field will be omitted.
   230  func (l *Logger) Fatal(ctx context.Context, msg string, keysAndValues ...interface{}) {
   231  	l.Fatalv(ctx, 1, msg, keysAndValues...)
   232  }
   233  
   234  // Fatalf uses fmt.Sprintf to log a templated message and the key-value pairs defined in contextFields from ctx.
   235  // If ctx is nil, all fields from contextFields will be omitted.
   236  // If ctx does not contain a key in contextFields, that field will be omitted.
   237  func (l *Logger) Fatalf(ctx context.Context, fs string, args ...interface{}) {
   238  	l.Fatalv(ctx, 1, fmt.Sprintf(fs, args...))
   239  }
   240  
   241  // Fatalv logs a message, the key-value pairs defined in contextFields from ctx, and variadic key-value pairs.
   242  // Caller stack field is skipped by skip levels.
   243  // If ctx is nil, all fields from contextFields will be omitted.
   244  // If ctx does not contain a key in contextFields, that field will be omitted.
   245  func (l *Logger) Fatalv(ctx context.Context, skip int, msg string, keysAndValues ...interface{}) {
   246  	l.log(ctx, FatalLevel, skip, msg, keysAndValues)
   247  }
   248  
   249  // FatalNoCtx logs a message and variadic key-value pairs.
   250  func (l *Logger) FatalNoCtx(msg string, keysAndValues ...interface{}) {
   251  	l.Fatalv(context.Background(), 1, msg, keysAndValues...)
   252  }
   253  
   254  // Error logs a message, the key-value pairs defined in contextFields from ctx, and variadic key-value pairs.
   255  // If ctx is nil, all fields from contextFields will be omitted.
   256  // If ctx does not contain a key in contextFields, that field will be omitted.
   257  func (l *Logger) Error(ctx context.Context, msg string, keysAndValues ...interface{}) {
   258  	l.Errorv(ctx, 1, msg, keysAndValues...)
   259  }
   260  
   261  // Errorf uses fmt.Sprintf to log a templated message and the key-value pairs defined in contextFields from ctx.
   262  // If ctx is nil, all fields from contextFields will be omitted.
   263  // If ctx does not contain a key in contextFields, that field will be omitted.
   264  func (l *Logger) Errorf(ctx context.Context, fs string, args ...interface{}) {
   265  	l.Errorv(ctx, 1, fmt.Sprintf(fs, args...))
   266  }
   267  
   268  // Errorv logs a message, the key-value pairs defined in contextFields from ctx, and variadic key-value pairs.
   269  // Caller stack field is skipped by skip levels.
   270  // If ctx is nil, all fields from contextFields will be omitted.
   271  // If ctx does not contain a key in contextFields, that field will be omitted.
   272  func (l *Logger) Errorv(ctx context.Context, skip int, msg string, keysAndValues ...interface{}) {
   273  	l.log(ctx, ErrorLevel, skip, msg, keysAndValues)
   274  }
   275  
   276  // ErrorNoCtx logs a message and variadic key-value pairs.
   277  func (l *Logger) ErrorNoCtx(msg string, keysAndValues ...interface{}) {
   278  	l.Errorv(context.Background(), 1, msg, keysAndValues...)
   279  }
   280  
   281  // ErrorAndReturn logs a message, the key-value pairs defined in contextFields from ctx, and variadic key-value pairs.
   282  // If ctx is nil, all fields from contextFields will be omitted.
   283  // If ctx does not contain a key in contextFields, that field will be omitted.
   284  // Returns a new error constructed from the message.
   285  func (l *Logger) ErrorAndReturn(ctx context.Context, msg string, keysAndValues ...interface{}) error {
   286  	return l.ErrorvAndReturn(ctx, 1, msg, keysAndValues...)
   287  }
   288  
   289  // ErrorfAndReturn uses fmt.Errorf to construct an error from the provided arguments.
   290  // It then logs the error message, along with data from the context.
   291  // If ctx is nil, all fields from contextFields will be omitted.
   292  // If ctx does not contain a key in contextFields, that field will be omitted.
   293  // Returns the error resulting from invoking fmt.Errorf with the provided arguments.
   294  func (l *Logger) ErrorfAndReturn(ctx context.Context, fs string, args ...interface{}) error {
   295  	err := fmt.Errorf(fs, args...)
   296  	l.Errorv(ctx, 1, err.Error())
   297  	return err
   298  }
   299  
   300  // Errorv logs a message, the key-value pairs defined in contextFields from ctx, and variadic key-value pairs.
   301  // Caller is skipped by skip.
   302  // If ctx is nil, all fields from contextFields will be omitted.
   303  // If ctx does not contain a key in contextFields, that field will be omitted.
   304  // Returns a new error constructed from the message.
   305  func (l *Logger) ErrorvAndReturn(ctx context.Context, skip int, msg string, keysAndValues ...interface{}) error {
   306  	l.Errorv(ctx, skip+1, msg, keysAndValues...)
   307  	return fmt.Errorf(msg)
   308  }
   309  
   310  // ErrorNoCtxAndReturn logs a message and variadic key-value pairs.
   311  // Returns a new error constructed from the message.
   312  func (l *Logger) ErrorNoCtxAndReturn(msg string, keysAndValues ...interface{}) error {
   313  	// context.Background() is a singleton and gets initialized once
   314  	return l.ErrorvAndReturn(context.Background(), 1, msg, keysAndValues...)
   315  }
   316  
   317  // rfc3339TrailingNanoTimeEncoder serializes a time.Time to an RFC3339-formatted string
   318  // with trailing nanosecond precision.
   319  func rfc3339TrailingNanoTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
   320  	enc.AppendString(t.Format(RFC3339TrailingNano))
   321  }
   322  
   323  func mustBuildLogger(config Config, opts ...zap.Option) *zap.SugaredLogger {
   324  	zapLogger, err := newConfig(config).Build(opts...)
   325  	if err != nil {
   326  		panic(err)
   327  	}
   328  	return zapLogger.Sugar()
   329  }
   330  
   331  // newEncoderConfig is similar to Zap's NewProductionConfig with a few modifications
   332  // to better fit our needs.
   333  func newEncoderConfig() zapcore.EncoderConfig {
   334  	return zapcore.EncoderConfig{
   335  		LevelKey:       "level",
   336  		NameKey:        "logger",
   337  		MessageKey:     "msg",
   338  		LineEnding:     zapcore.DefaultLineEnding,
   339  		EncodeLevel:    zapcore.LowercaseLevelEncoder,
   340  		EncodeTime:     rfc3339TrailingNanoTimeEncoder,
   341  		EncodeDuration: zapcore.SecondsDurationEncoder,
   342  		EncodeCaller:   zapcore.ShortCallerEncoder,
   343  	}
   344  }
   345  
   346  // newConfig is similar to Zap's NewProductionConfig with a few modifications
   347  // to better fit our needs.
   348  func newConfig(override Config) zap.Config {
   349  	// Default config
   350  	config := zap.Config{
   351  		Level:       zap.NewAtomicLevelAt(zap.DebugLevel),
   352  		Development: false,
   353  		Sampling: &zap.SamplingConfig{
   354  			Initial:    100,
   355  			Thereafter: 100,
   356  		},
   357  		Encoding:         "json",
   358  		EncoderConfig:    newEncoderConfig(),
   359  		OutputPaths:      []string{"stderr"},
   360  		ErrorOutputPaths: []string{"stderr"},
   361  	}
   362  	// config overrides
   363  	if override.OutputPaths != nil {
   364  		config.OutputPaths = override.OutputPaths
   365  	}
   366  	if override.Level != zapcore.DebugLevel {
   367  		config.Level = zap.NewAtomicLevelAt(override.Level)
   368  	}
   369  	// LOG_LEVEL environment variable override
   370  	// Note: setting the environment variable LOG_LEVEL will override Config.Level
   371  	if logLvl, ok := logLvls[os.Getenv(LOG_LEVEL_ENV_VAR)]; ok {
   372  		config.Level = zap.NewAtomicLevelAt(logLvl)
   373  	}
   374  	return config
   375  }
   376  
   377  func withDefaultFields(ctx context.Context, callerSkip int, t time.Time,
   378  	keysAndValues ...interface{}) []interface{} {
   379  	defaultFields := []interface{}{
   380  		"caller", getCaller(callerSkip),
   381  		"ts", t,
   382  	}
   383  	if ctx != nil {
   384  		if vctx, ok := ctx.(*vcontext.T); ok {
   385  			if requestID := v23.GetRequestID(vctx); requestID != uuid.Nil {
   386  				defaultFields = append(defaultFields, "v23RequestID", requestID)
   387  			}
   388  		}
   389  	}
   390  	return append(defaultFields, keysAndValues...)
   391  }
   392  
   393  func getCaller(skip int) string {
   394  	skipOffset := 5
   395  	pc := make([]uintptr, 1)
   396  	numFrames := runtime.Callers(skip+skipOffset, pc)
   397  	if numFrames < 1 {
   398  		return ""
   399  	}
   400  	frame, _ := runtime.CallersFrames(pc).Next()
   401  	if frame.PC == 0 {
   402  		return ""
   403  	}
   404  	parts := strings.Split(frame.File, "/")
   405  	file := parts[len(parts)-1]
   406  	return fmt.Sprintf("%s:%d", file, frame.Line)
   407  }