code.vegaprotocol.io/vega@v0.79.0/logging/log.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package logging 17 18 import ( 19 "fmt" 20 "strings" 21 22 "go.uber.org/zap" 23 "go.uber.org/zap/zapcore" 24 ) 25 26 // A Level is a logging priority. Higher levels are more important. 27 type Level int8 28 29 // Logging levels (matching zap core internals). 30 const ( 31 // DebugLevel logs are typically voluminous, and are usually disabled in 32 // production. 33 DebugLevel Level = -1 34 // InfoLevel is the default logging priority. 35 InfoLevel Level = 0 36 // WarnLevel logs are more important than Info, but don't need individual 37 // human review. 38 WarnLevel Level = 1 39 // ErrorLevel logs are high-priority. If an application is running smoothly, 40 // it shouldn't generate any error-level logs. 41 ErrorLevel Level = 2 42 // PanicLevel logs a message, then panics. 43 PanicLevel Level = 4 44 // FatalLevel logs a message, then calls os.Exit(1). 45 FatalLevel Level = 5 46 ) 47 48 // ParseLevel parse a log level from a string. 49 func ParseLevel(l string) (Level, error) { 50 l = strings.ToLower(l) 51 switch l { 52 case "debug": 53 return DebugLevel, nil 54 case "info": 55 return InfoLevel, nil 56 case "warning": 57 return WarnLevel, nil 58 case "error": 59 return ErrorLevel, nil 60 case "panic": 61 return PanicLevel, nil 62 case "fatal": 63 return FatalLevel, nil 64 default: 65 return Level(100), fmt.Errorf("log level \"%s\" is not supported", l) 66 } 67 } 68 69 // String marshal a log level to a string representation. 70 func (l Level) String() string { 71 switch l { 72 case DebugLevel: 73 return "Debug" 74 case InfoLevel: 75 return "Info" 76 case WarnLevel: 77 return "Warning" 78 case ErrorLevel: 79 return "Error" 80 case PanicLevel: 81 return "Panic" 82 case FatalLevel: 83 return "Fatal" 84 default: 85 return "Unknown" 86 } 87 } 88 89 // ZapLevel return the log level of internal zap level. 90 func (l *Level) ZapLevel() zapcore.Level { 91 return zapcore.Level(*l) 92 } 93 94 // Logger is an abstraction on to of the zap logger. 95 type Logger struct { 96 *zap.Logger 97 config *zap.Config 98 environment string 99 name string 100 } 101 102 func (log *Logger) ToSugared() *zap.SugaredLogger { 103 return log.WithOptions(zap.AddCallerSkip(1)).Sugar() 104 } 105 106 // Clone will clone the internal logger. 107 func (log *Logger) Clone() *Logger { 108 newConfig := cloneConfig(log.config) 109 newLogger, err := newConfig.Build() 110 if err != nil { 111 panic(err) 112 } 113 return New(newLogger, newConfig, log.environment, log.name) 114 } 115 116 // GetLevel returns the log level. 117 func (log *Logger) GetLevel() Level { 118 return (Level)(log.config.Level.Level()) 119 } 120 121 // IsDebug returns true if logger level is less or equal to DebugLevel. 122 func (log *Logger) IsDebug() bool { 123 return log.GetLevel() <= DebugLevel 124 } 125 126 // GetLevelString return a string representation of the current 127 // log level. 128 func (log *Logger) GetLevelString() string { 129 return log.config.Level.String() 130 } 131 132 // GetEnvironment returns the current environment name. 133 func (log *Logger) GetEnvironment() string { 134 return log.environment 135 } 136 137 // GetName return the name of this logger. 138 func (log *Logger) GetName() string { 139 return log.name 140 } 141 142 // Named instantiate a new logger by cloning it first 143 // and name it with the string specified. 144 func (log *Logger) Named(name string) *Logger { 145 var ( 146 c = log.Clone() 147 newName string 148 ) 149 if log.name == "" || log.name == "root" { 150 newName = name 151 } else { 152 newName = fmt.Sprintf("%s.%s", log.name, name) 153 } 154 c.Logger = c.Logger.Named(newName) 155 c.name = newName 156 return c 157 } 158 159 // New instantiate a new logger. 160 func New(zaplogger *zap.Logger, zapconfig *zap.Config, environment, name string) *Logger { 161 return &Logger{ 162 Logger: zaplogger, 163 config: zapconfig, 164 environment: environment, 165 name: name, 166 } 167 } 168 169 // SetLevel change the level of this logger. 170 func (log *Logger) SetLevel(level Level) { 171 lvl := (zapcore.Level)(level) 172 if log.config.Level.Level() == lvl { 173 return 174 } 175 log.config.Level.SetLevel(lvl) 176 } 177 178 // With will add default field to each logs. 179 func (log *Logger) With(fields ...zap.Field) *Logger { 180 c := log.Clone() 181 c.Logger = c.Logger.With(fields...) 182 return c 183 } 184 185 // AtExit flushes the logs before exiting the process. Useful when an 186 // app shuts down so we store all logging possible. This is meant to be used 187 // with defer when initializing your logger. 188 func (log *Logger) AtExit() { 189 if log.Logger != nil { 190 log.Logger.Sync() 191 } 192 } 193 194 func cloneConfig(cfg *zap.Config) *zap.Config { 195 c := zap.Config{ 196 Level: zap.NewAtomicLevelAt(cfg.Level.Level()), 197 Development: cfg.Development, 198 DisableCaller: cfg.DisableCaller, 199 DisableStacktrace: cfg.DisableStacktrace, 200 Sampling: nil, 201 Encoding: cfg.Encoding, 202 EncoderConfig: cfg.EncoderConfig, 203 OutputPaths: cfg.OutputPaths, 204 ErrorOutputPaths: cfg.ErrorOutputPaths, 205 InitialFields: make(map[string]interface{}), 206 } 207 for k, v := range cfg.InitialFields { 208 c.InitialFields[k] = v 209 } 210 if cfg.Sampling != nil { 211 c.Sampling = &zap.SamplingConfig{ 212 Initial: cfg.Sampling.Initial, 213 Thereafter: cfg.Sampling.Thereafter, 214 } 215 } 216 return &c 217 } 218 219 // newLoggerFromConfig creates a logger according to the given custom config. 220 func newLoggerFromConfig(config Config) *Logger { 221 encoderConfig := zapcore.EncoderConfig{ 222 CallerKey: config.Custom.ZapEncoder.CallerKey, 223 LevelKey: config.Custom.ZapEncoder.LevelKey, 224 LineEnding: config.Custom.ZapEncoder.LineEnding, 225 MessageKey: config.Custom.ZapEncoder.MessageKey, 226 NameKey: config.Custom.ZapEncoder.NameKey, 227 TimeKey: config.Custom.ZapEncoder.TimeKey, 228 } 229 230 encoderConfig.EncodeCaller.UnmarshalText([]byte(config.Custom.ZapEncoder.EncodeCaller)) 231 encoderConfig.EncodeDuration.UnmarshalText([]byte(config.Custom.ZapEncoder.EncodeDuration)) 232 encoderConfig.EncodeLevel.UnmarshalText([]byte(config.Custom.ZapEncoder.EncodeLevel)) 233 encoderConfig.EncodeName.UnmarshalText([]byte(config.Custom.ZapEncoder.EncodeName)) 234 encoderConfig.EncodeTime.UnmarshalText([]byte(config.Custom.ZapEncoder.EncodeTime)) 235 236 zapconfig := zap.Config{ 237 Level: zap.NewAtomicLevelAt(zapcore.Level(config.Custom.Zap.Level)), 238 Development: config.Custom.Zap.Development, 239 Encoding: config.Custom.Zap.Encoding, 240 EncoderConfig: encoderConfig, 241 OutputPaths: config.Custom.Zap.OutputPaths, 242 ErrorOutputPaths: config.Custom.Zap.ErrorOutputPaths, 243 } 244 245 zaplogger, err := zapconfig.Build() 246 if err != nil { 247 panic(err) 248 } 249 zaplogger = zaplogger.Named("root") 250 return New(zaplogger, &zapconfig, config.Environment, "root") 251 } 252 253 // NewDevLogger creates a new logger suitable for development environments. 254 func NewDevLogger() *Logger { 255 config := Config{ 256 Environment: "dev", 257 Custom: &Custom{ 258 Zap: &Zap{ 259 Development: true, 260 Encoding: "console", 261 Level: DebugLevel, 262 OutputPaths: []string{"stdout"}, 263 ErrorOutputPaths: []string{"stderr"}, 264 }, 265 ZapEncoder: &ZapEncoder{ 266 CallerKey: "C", 267 EncodeCaller: "short", 268 EncodeDuration: "string", 269 EncodeLevel: "capital", 270 EncodeName: "full", 271 EncodeTime: "iso8601", 272 LevelKey: "L", 273 LineEnding: "\n", 274 MessageKey: "M", 275 NameKey: "N", 276 TimeKey: "T", 277 }, 278 }, 279 } 280 return newLoggerFromConfig(config) 281 } 282 283 // NewTestLogger creates a new logger suitable for golang unit test 284 // environments, ie when running "go test ./...". 285 func NewTestLogger() *Logger { 286 config := Config{ 287 Environment: "test", 288 Custom: &Custom{ 289 Zap: &Zap{ 290 Development: true, 291 Encoding: "console", 292 Level: DebugLevel, 293 OutputPaths: []string{"stdout"}, 294 ErrorOutputPaths: []string{"stderr"}, 295 }, 296 ZapEncoder: &ZapEncoder{ 297 CallerKey: "C", 298 EncodeCaller: "short", 299 EncodeDuration: "string", 300 EncodeLevel: "capital", 301 EncodeName: "full", 302 EncodeTime: "iso8601", 303 LevelKey: "L", 304 LineEnding: "\n", 305 MessageKey: "M", 306 NameKey: "N", 307 TimeKey: "T", 308 }, 309 }, 310 } 311 return newLoggerFromConfig(config) 312 } 313 314 // NewProdLogger creates a new logger suitable for production environments, 315 // including sending logs to ElasticSearch. 316 func NewProdLogger() *Logger { 317 config := Config{ 318 Environment: "prod", 319 Custom: &Custom{ 320 Zap: &Zap{ 321 Development: false, 322 Encoding: "json", 323 Level: InfoLevel, 324 OutputPaths: []string{"stdout"}, 325 ErrorOutputPaths: []string{"stderr"}, 326 }, 327 ZapEncoder: &ZapEncoder{ 328 CallerKey: "caller", 329 EncodeCaller: "short", 330 EncodeDuration: "string", 331 EncodeLevel: "lowercase", 332 EncodeName: "full", 333 EncodeTime: "iso8601", 334 LevelKey: "level", 335 LineEnding: "\n", 336 MessageKey: "message", 337 NameKey: "logger", 338 TimeKey: "@timestamp", 339 }, 340 }, 341 } 342 return newLoggerFromConfig(config) 343 } 344 345 // NewLoggerFromConfig creates a standard or custom logger. 346 func NewLoggerFromConfig(config Config) *Logger { 347 switch config.Environment { 348 case "dev": 349 return NewDevLogger() 350 case "test": 351 return NewTestLogger() 352 case "prod": 353 return NewProdLogger() 354 case "custom": 355 return newLoggerFromConfig(config) 356 } 357 358 // Default: 359 return NewDevLogger() 360 } 361 362 // Check helps avoid spending CPU time on log entries that will never be printed. 363 func (log *Logger) Check(l Level) bool { 364 return log.Logger.Check(l.ZapLevel(), "") != nil 365 } 366 367 // Errorf implement badger interface. 368 func (log *Logger) Errorf(s string, args ...interface{}) { 369 if ce := log.Logger.Check(zap.ErrorLevel, ""); ce != nil { 370 log.Logger.WithOptions(zap.AddCallerSkip(2)).Sugar().Errorf(strings.TrimSpace(s), args...) 371 } 372 } 373 374 // Warningf implement badger interface. 375 func (log *Logger) Warningf(s string, args ...interface{}) { 376 if ce := log.Logger.Check(zap.WarnLevel, ""); ce != nil { 377 log.Logger.WithOptions(zap.AddCallerSkip(2)).Sugar().Warnf(strings.TrimSpace(s), args...) 378 } 379 } 380 381 // Infof implement badger interface. 382 func (log *Logger) Infof(s string, args ...interface{}) { 383 if ce := log.Logger.Check(zap.InfoLevel, ""); ce != nil { 384 log.Logger.WithOptions(zap.AddCallerSkip(2)).Sugar().Infof(strings.TrimSpace(s), args...) 385 } 386 } 387 388 // Debugf implement badger interface. 389 func (log *Logger) Debugf(s string, args ...interface{}) { 390 if ce := log.Logger.Check(zap.DebugLevel, ""); ce != nil { 391 log.Logger.WithOptions(zap.AddCallerSkip(2)).Sugar().Debugf(strings.TrimSpace(s), args...) 392 } 393 } 394 395 type ZapGooseLogger struct { 396 zap.SugaredLogger 397 } 398 399 func (l *ZapGooseLogger) Print(v ...interface{}) { 400 l.Info(v...) 401 } 402 403 func (l *ZapGooseLogger) Println(v ...interface{}) { 404 l.Info(v...) 405 } 406 407 func (l *ZapGooseLogger) Printf(format string, v ...interface{}) { 408 // l.Logger.WithOptions(zap.AddCallerSkip(2)) 409 l.Infof(strings.TrimSpace(format), v...) 410 } 411 412 func (log *Logger) GooseLogger() *ZapGooseLogger { 413 gl := log.Logger.WithOptions(zap.AddCallerSkip(2)).Sugar() 414 return &ZapGooseLogger{SugaredLogger: *gl} 415 }