github.com/pingcap/tidb-lightning@v5.0.0-rc.0.20210428090220-84b649866577+incompatible/lightning/log/log.go (about)

     1  // Copyright 2019 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package log
    15  
    16  import (
    17  	"context"
    18  	"time"
    19  
    20  	"github.com/pingcap/errors"
    21  	pclog "github.com/pingcap/log"
    22  	"github.com/pingcap/tidb/util/logutil"
    23  	"go.uber.org/zap"
    24  	"go.uber.org/zap/zapcore"
    25  	"google.golang.org/grpc/codes"
    26  	"google.golang.org/grpc/status"
    27  )
    28  
    29  const (
    30  	defaultLogLevel   = "info"
    31  	defaultLogMaxDays = 7
    32  	defaultLogMaxSize = 512 // MB
    33  )
    34  
    35  // Config serializes log related config in toml/json.
    36  type Config struct {
    37  	// Log level.
    38  	Level string `toml:"level" json:"level"`
    39  	// Log filename, leave empty to disable file log.
    40  	File string `toml:"file" json:"file"`
    41  	// Max size for a single file, in MB.
    42  	FileMaxSize int `toml:"max-size" json:"max-size"`
    43  	// Max log keep days, default is never deleting.
    44  	FileMaxDays int `toml:"max-days" json:"max-days"`
    45  	// Maximum number of old log files to retain.
    46  	FileMaxBackups int `toml:"max-backups" json:"max-backups"`
    47  }
    48  
    49  func (cfg *Config) Adjust() {
    50  	if len(cfg.Level) == 0 {
    51  		cfg.Level = defaultLogLevel
    52  	}
    53  	if cfg.Level == "warning" {
    54  		cfg.Level = "warn"
    55  	}
    56  	if cfg.FileMaxSize == 0 {
    57  		cfg.FileMaxSize = defaultLogMaxSize
    58  	}
    59  	if cfg.FileMaxDays == 0 {
    60  		cfg.FileMaxDays = defaultLogMaxDays
    61  	}
    62  }
    63  
    64  // Logger is a simple wrapper around *zap.Logger which provides some extra
    65  // methods to simplify Lightning's log usage.
    66  type Logger struct {
    67  	*zap.Logger
    68  }
    69  
    70  // logger for lightning, different from tidb logger.
    71  var (
    72  	appLogger = Logger{zap.NewNop()}
    73  	appLevel  = zap.NewAtomicLevel()
    74  )
    75  
    76  // InitLogger initializes Lightning's and also the TiDB library's loggers.
    77  func InitLogger(cfg *Config, tidbLoglevel string) error {
    78  	logutil.InitLogger(&logutil.LogConfig{Config: pclog.Config{Level: tidbLoglevel}})
    79  
    80  	logCfg := &pclog.Config{
    81  		Level: cfg.Level,
    82  	}
    83  	if len(cfg.File) > 0 {
    84  		logCfg.File = pclog.FileLogConfig{
    85  			Filename:   cfg.File,
    86  			MaxSize:    cfg.FileMaxSize,
    87  			MaxDays:    cfg.FileMaxDays,
    88  			MaxBackups: cfg.FileMaxBackups,
    89  		}
    90  	}
    91  	logger, props, err := pclog.InitLogger(logCfg)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	// Do not log stack traces at all, as we'll get the stack trace from the
    97  	// error itself.
    98  	appLogger = Logger{logger.WithOptions(zap.AddStacktrace(zap.DPanicLevel))}
    99  	appLevel = props.Level
   100  
   101  	return nil
   102  }
   103  
   104  // L returns the current logger for Lightning.
   105  func L() Logger {
   106  	return appLogger
   107  }
   108  
   109  // SetAppLogger replaces the default logger in this package to given one
   110  func SetAppLogger(l *zap.Logger) {
   111  	appLogger = Logger{l.WithOptions(zap.AddStacktrace(zap.DPanicLevel))}
   112  }
   113  
   114  // Level returns the current global log level.
   115  func Level() zapcore.Level {
   116  	return appLevel.Level()
   117  }
   118  
   119  // SetLevel modifies the log level of the global logger. Returns the previous
   120  // level.
   121  func SetLevel(level zapcore.Level) zapcore.Level {
   122  	oldLevel := appLevel.Level()
   123  	appLevel.SetLevel(level)
   124  	return oldLevel
   125  }
   126  
   127  // ShortError contructs a field which only records the error message without the
   128  // verbose text (i.e. excludes the stack trace).
   129  //
   130  // In Lightning, all errors are almost always propagated back to `main()` where
   131  // the error stack is written. Including the stack in the middle thus usually
   132  // just repeats known information. You should almost always use `ShortError`
   133  // instead of `zap.Error`, unless the error is no longer propagated upwards.
   134  func ShortError(err error) zap.Field {
   135  	if err == nil {
   136  		return zap.Skip()
   137  	}
   138  	return zap.String("error", err.Error())
   139  }
   140  
   141  // With creates a child logger from the global logger and adds structured
   142  // context to it.
   143  func With(fields ...zap.Field) Logger {
   144  	return appLogger.With(fields...)
   145  }
   146  
   147  // IsContextCanceledError returns whether the error is caused by context
   148  // cancellation.
   149  func IsContextCanceledError(err error) bool {
   150  	err = errors.Cause(err)
   151  	return err == context.Canceled || status.Code(err) == codes.Canceled
   152  }
   153  
   154  // Begin marks the beginning of a task.
   155  func (logger Logger) Begin(level zapcore.Level, name string) *Task {
   156  	if ce := logger.WithOptions(zap.AddCallerSkip(1)).Check(level, name+" start"); ce != nil {
   157  		ce.Write()
   158  	}
   159  	return &Task{
   160  		Logger: logger,
   161  		level:  level,
   162  		name:   name,
   163  		since:  time.Now(),
   164  	}
   165  }
   166  
   167  // With creates a child logger and adds structured context to it.
   168  func (logger Logger) With(fields ...zap.Field) Logger {
   169  	return Logger{logger.Logger.With(fields...)}
   170  }
   171  
   172  // Named adds a new path segment to the logger's name.
   173  func (logger Logger) Named(name string) Logger {
   174  	return Logger{logger.Logger.Named(name)}
   175  }
   176  
   177  // Task is a logger for a task spanning a period of time. This structure records
   178  // when the task is started, so the time elapsed for the whole task can be
   179  // logged without book-keeping.
   180  type Task struct {
   181  	Logger
   182  	level zapcore.Level
   183  	name  string
   184  	since time.Time
   185  }
   186  
   187  // End marks the end of a task.
   188  //
   189  // The `level` is the log level if the task *failed* (i.e. `err != nil`). If the
   190  // task *succeeded* (i.e. `err == nil`), the level from `Begin()` is used
   191  // instead.
   192  //
   193  // The `extraFields` are included in the log only when the task succeeded.
   194  func (task *Task) End(level zapcore.Level, err error, extraFields ...zap.Field) time.Duration {
   195  	elapsed := time.Since(task.since)
   196  	var verb string
   197  	switch {
   198  	case err == nil:
   199  		level = task.level
   200  		verb = " completed"
   201  	case IsContextCanceledError(err):
   202  		level = zap.DebugLevel
   203  		verb = " canceled"
   204  		extraFields = nil
   205  	default:
   206  		verb = " failed"
   207  		extraFields = nil
   208  	}
   209  	if ce := task.WithOptions(zap.AddCallerSkip(1)).Check(level, task.name+verb); ce != nil {
   210  		ce.Write(append(
   211  			extraFields,
   212  			zap.Duration("takeTime", elapsed),
   213  			ShortError(err),
   214  		)...)
   215  	}
   216  	return elapsed
   217  }