github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/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 }