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  }