github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/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  	"fmt"
    19  	"strings"
    20  
    21  	"github.com/pingcap/errors"
    22  	pclog "github.com/pingcap/log"
    23  	lightningLog "github.com/pingcap/tidb/pkg/lightning/log"
    24  	"github.com/pingcap/tidb/pkg/util/logutil"
    25  	"github.com/pingcap/tiflow/dm/pkg/helper"
    26  	"github.com/pingcap/tiflow/dm/pkg/terror"
    27  	"go.uber.org/zap"
    28  	"go.uber.org/zap/zapcore"
    29  )
    30  
    31  const (
    32  	defaultLogLevel   = "info"
    33  	defaultLogMaxDays = 7
    34  	defaultLogMaxSize = 512 // MB
    35  )
    36  
    37  // Config serializes log related config in toml/json.
    38  type Config struct {
    39  	// Log level.
    40  	Level string `toml:"level" json:"level"`
    41  	// the format of the log, "text" or "json"
    42  	Format string `toml:"format" json:"format"`
    43  	// Log filename, leave empty to disable file log.
    44  	File string `toml:"file" json:"file"`
    45  	// Max size for a single file, in MB.
    46  	FileMaxSize int `toml:"max-size" json:"max-size"`
    47  	// Max log keep days, default is never deleting.
    48  	FileMaxDays int `toml:"max-days" json:"max-days"`
    49  	// Maximum number of old log files to retain.
    50  	FileMaxBackups int `toml:"max-backups" json:"max-backups"`
    51  }
    52  
    53  // Adjust adjusts config.
    54  func (cfg *Config) Adjust() {
    55  	if len(cfg.Level) == 0 {
    56  		cfg.Level = defaultLogLevel
    57  	}
    58  	if cfg.Level == "warning" {
    59  		cfg.Level = "warn"
    60  	}
    61  	if cfg.FileMaxSize == 0 {
    62  		cfg.FileMaxSize = defaultLogMaxSize
    63  	}
    64  	if cfg.FileMaxDays == 0 {
    65  		cfg.FileMaxDays = defaultLogMaxDays
    66  	}
    67  }
    68  
    69  // Logger is a simple wrapper around *zap.Logger which provides some extra
    70  // methods to simplify DM's log usage.
    71  type Logger struct {
    72  	*zap.Logger
    73  }
    74  
    75  // WithFields return new Logger with specified fields.
    76  func (l Logger) WithFields(fields ...zap.Field) Logger {
    77  	return Logger{l.With(fields...)}
    78  }
    79  
    80  // ErrorFilterContextCanceled wraps Logger.Error() and will filter error log when error is context.Canceled.
    81  func (l Logger) ErrorFilterContextCanceled(msg string, fields ...zap.Field) {
    82  	for _, field := range fields {
    83  		switch field.Type {
    84  		case zapcore.StringType:
    85  			if field.Key == "error" && strings.Contains(field.String, context.Canceled.Error()) {
    86  				return
    87  			}
    88  		case zapcore.ErrorType:
    89  			err, ok := field.Interface.(error)
    90  			if ok && errors.Cause(err) == context.Canceled {
    91  				return
    92  			}
    93  		}
    94  	}
    95  	l.Logger.WithOptions(zap.AddCallerSkip(1)).Error(msg, fields...)
    96  }
    97  
    98  // logger for DM.
    99  var (
   100  	appLogger = Logger{zap.NewNop()}
   101  	appLevel  zap.AtomicLevel
   102  	appProps  *pclog.ZapProperties
   103  )
   104  
   105  // InitLogger initializes DM's and also the TiDB library's loggers.
   106  func InitLogger(cfg *Config) error {
   107  	inDev := strings.ToLower(cfg.Level) == "debug"
   108  	// init DM logger
   109  	logger, props, err := pclog.InitLogger(&pclog.Config{
   110  		Level:  cfg.Level,
   111  		Format: cfg.Format,
   112  		File: pclog.FileLogConfig{
   113  			Filename:   cfg.File,
   114  			MaxSize:    cfg.FileMaxSize,
   115  			MaxDays:    cfg.FileMaxDays,
   116  			MaxBackups: cfg.FileMaxBackups,
   117  		},
   118  		Development: inDev,
   119  	})
   120  	if err != nil {
   121  		return terror.ErrInitLoggerFail.Delegate(err)
   122  	}
   123  
   124  	// Do not log stack traces at all, as we'll get the stack trace from the
   125  	// error itself.
   126  	logger = logger.WithOptions(zap.AddStacktrace(zap.DPanicLevel))
   127  	pclog.ReplaceGlobals(logger, props)
   128  
   129  	lightningLogger := logger.With(zap.String("component", "lightning"))
   130  	lightningLog.SetAppLogger(lightningLogger)
   131  
   132  	appLogger = Logger{logger}
   133  	appLevel = props.Level
   134  	appProps = props
   135  	// init and set tidb slow query logger to stdout if log level is debug
   136  	if inDev {
   137  		slowQueryLogger := zap.NewExample()
   138  		slowQueryLogger = slowQueryLogger.With(zap.String("component", "slow query logger"))
   139  		logutil.SlowQueryLogger = slowQueryLogger
   140  	} else {
   141  		logutil.SlowQueryLogger = zap.NewNop()
   142  	}
   143  	return nil
   144  }
   145  
   146  // With creates a child logger from the global logger and adds structured
   147  // context to it.
   148  func With(fields ...zap.Field) Logger {
   149  	return Logger{appLogger.With(fields...)}
   150  }
   151  
   152  // SetLevel modifies the log level of the global logger. Returns the previous
   153  // level.
   154  func SetLevel(level zapcore.Level) zapcore.Level {
   155  	oldLevel := appLevel.Level()
   156  	appLevel.SetLevel(level)
   157  	return oldLevel
   158  }
   159  
   160  // ShortError contructs a field which only records the error message without the
   161  // verbose text (i.e. excludes the stack trace).
   162  //
   163  // In DM, all errors are almost always propagated back to `main()` where
   164  // the error stack is written. Including the stack in the middle thus usually
   165  // just repeats known information. You should almost always use `ShortError`
   166  // instead of `zap.Error`, unless the error is no longer propagated upwards.
   167  func ShortError(err error) zap.Field {
   168  	if err == nil {
   169  		return zap.Skip()
   170  	}
   171  	return zap.String("error", err.Error())
   172  }
   173  
   174  // L returns the current logger for DM.
   175  func L() Logger {
   176  	return appLogger
   177  }
   178  
   179  // Props returns the current logger's props.
   180  func Props() *pclog.ZapProperties {
   181  	return appProps
   182  }
   183  
   184  // WrapStringerField returns a wrap stringer field.
   185  func WrapStringerField(message string, object fmt.Stringer) zap.Field {
   186  	if helper.IsNil(object) {
   187  		return zap.String(message, "NULL")
   188  	}
   189  
   190  	return zap.Stringer(message, object)
   191  }
   192  
   193  // WithCtx adds fields from ctx to the logger.
   194  func WithCtx(ctx context.Context) Logger {
   195  	return Logger{appLogger.With(getZapFieldsFromCtx(ctx)...)}
   196  }