github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/pkg/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  	if err := logutil.InitLogger(&logutil.LogConfig{Config: pclog.Config{Level: tidbLoglevel}}); err != nil {
    79  		return errors.Trace(err)
    80  	}
    81  
    82  	logCfg := &pclog.Config{
    83  		Level:         cfg.Level,
    84  		DisableCaller: false, // FilterCore requires zap.AddCaller.
    85  	}
    86  	filterTiDBLog := zap.WrapCore(func(core zapcore.Core) zapcore.Core {
    87  		// Filter logs from TiDB and PD.
    88  		return NewFilterCore(core, "github.com/pingcap/tidb/", "github.com/tikv/pd/")
    89  	})
    90  	if len(cfg.File) > 0 {
    91  		logCfg.File = pclog.FileLogConfig{
    92  			Filename:   cfg.File,
    93  			MaxSize:    cfg.FileMaxSize,
    94  			MaxDays:    cfg.FileMaxDays,
    95  			MaxBackups: cfg.FileMaxBackups,
    96  		}
    97  	}
    98  	logger, props, err := pclog.InitLogger(logCfg, filterTiDBLog)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	pclog.ReplaceGlobals(logger, props)
   103  
   104  	// Do not log stack traces at all, as we'll get the stack trace from the
   105  	// error itself.
   106  	appLogger = Logger{logger.WithOptions(zap.AddStacktrace(zap.DPanicLevel))}
   107  	appLevel = props.Level
   108  
   109  	return nil
   110  }
   111  
   112  // L returns the current logger for Lightning.
   113  func L() Logger {
   114  	return appLogger
   115  }
   116  
   117  // Level returns the current global log level.
   118  func Level() zapcore.Level {
   119  	return appLevel.Level()
   120  }
   121  
   122  // SetLevel modifies the log level of the global logger. Returns the previous
   123  // level.
   124  func SetLevel(level zapcore.Level) zapcore.Level {
   125  	oldLevel := appLevel.Level()
   126  	appLevel.SetLevel(level)
   127  	return oldLevel
   128  }
   129  
   130  // ShortError contructs a field which only records the error message without the
   131  // verbose text (i.e. excludes the stack trace).
   132  //
   133  // In Lightning, all errors are almost always propagated back to `main()` where
   134  // the error stack is written. Including the stack in the middle thus usually
   135  // just repeats known information. You should almost always use `ShortError`
   136  // instead of `zap.Error`, unless the error is no longer propagated upwards.
   137  func ShortError(err error) zap.Field {
   138  	if err == nil {
   139  		return zap.Skip()
   140  	}
   141  	return zap.String("error", err.Error())
   142  }
   143  
   144  // With creates a child logger from the global logger and adds structured
   145  // context to it.
   146  func With(fields ...zap.Field) Logger {
   147  	return appLogger.With(fields...)
   148  }
   149  
   150  // IsContextCanceledError returns whether the error is caused by context
   151  // cancellation.
   152  func IsContextCanceledError(err error) bool {
   153  	err = errors.Cause(err)
   154  	return err == context.Canceled || status.Code(err) == codes.Canceled
   155  }
   156  
   157  // Begin marks the beginning of a task.
   158  func (logger Logger) Begin(level zapcore.Level, name string) *Task {
   159  	if ce := logger.WithOptions(zap.AddCallerSkip(1)).Check(level, name+" start"); ce != nil {
   160  		ce.Write()
   161  	}
   162  	return &Task{
   163  		Logger: logger,
   164  		level:  level,
   165  		name:   name,
   166  		since:  time.Now(),
   167  	}
   168  }
   169  
   170  // With creates a child logger and adds structured context to it.
   171  func (logger Logger) With(fields ...zap.Field) Logger {
   172  	return Logger{logger.Logger.With(fields...)}
   173  }
   174  
   175  // Named adds a new path segment to the logger's name.
   176  func (logger Logger) Named(name string) Logger {
   177  	return Logger{logger.Logger.Named(name)}
   178  }
   179  
   180  // Task is a logger for a task spanning a period of time. This structure records
   181  // when the task is started, so the time elapsed for the whole task can be
   182  // logged without book-keeping.
   183  type Task struct {
   184  	Logger
   185  	level zapcore.Level
   186  	name  string
   187  	since time.Time
   188  }
   189  
   190  // End marks the end of a task.
   191  //
   192  // The `level` is the log level if the task *failed* (i.e. `err != nil`). If the
   193  // task *succeeded* (i.e. `err == nil`), the level from `Begin()` is used
   194  // instead.
   195  //
   196  // The `extraFields` are included in the log only when the task succeeded.
   197  func (task *Task) End(level zapcore.Level, err error, extraFields ...zap.Field) time.Duration {
   198  	elapsed := time.Since(task.since)
   199  	var verb string
   200  	switch {
   201  	case err == nil:
   202  		level = task.level
   203  		verb = " completed"
   204  	case IsContextCanceledError(err):
   205  		level = zap.DebugLevel
   206  		verb = " canceled"
   207  		extraFields = nil
   208  	default:
   209  		verb = " failed"
   210  		extraFields = nil
   211  	}
   212  	if ce := task.WithOptions(zap.AddCallerSkip(1)).Check(level, task.name+verb); ce != nil {
   213  		ce.Write(append(
   214  			extraFields,
   215  			zap.Duration("takeTime", elapsed),
   216  			ShortError(err),
   217  		)...)
   218  	}
   219  	return elapsed
   220  }