github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/pkg/logutil/log.go (about)

     1  // Copyright 2020 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 logutil
    15  
    16  import (
    17  	"bytes"
    18  	"os"
    19  	"strconv"
    20  	"strings"
    21  
    22  	"github.com/Shopify/sarama"
    23  	"github.com/pingcap/errors"
    24  	"github.com/pingcap/log"
    25  	"go.uber.org/zap"
    26  	"go.uber.org/zap/zapcore"
    27  	"google.golang.org/grpc/grpclog"
    28  )
    29  
    30  // _globalP is the global ZapProperties in log
    31  var _globalP *log.ZapProperties
    32  
    33  const (
    34  	defaultLogLevel   = "info"
    35  	defaultLogMaxDays = 7
    36  	defaultLogMaxSize = 512 // MB
    37  )
    38  
    39  // Config serializes log related config in toml/json.
    40  type Config struct {
    41  	// Log level.
    42  	Level string `toml:"level" json:"level"`
    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  // SetLogLevel changes TiCDC log level dynamically.
    70  func SetLogLevel(level string) error {
    71  	oldLevel := log.GetLevel()
    72  	if strings.EqualFold(oldLevel.String(), level) {
    73  		return nil
    74  	}
    75  	var lv zapcore.Level
    76  	err := lv.UnmarshalText([]byte(level))
    77  	if err != nil {
    78  		return errors.Trace(err)
    79  	}
    80  	log.SetLevel(lv)
    81  	return nil
    82  }
    83  
    84  // InitLogger initializes logger
    85  func InitLogger(cfg *Config) error {
    86  	pclogConfig := &log.Config{
    87  		Level: cfg.Level,
    88  		File: log.FileLogConfig{
    89  			Filename:   cfg.File,
    90  			MaxSize:    cfg.FileMaxSize,
    91  			MaxDays:    cfg.FileMaxDays,
    92  			MaxBackups: cfg.FileMaxBackups,
    93  		},
    94  	}
    95  
    96  	var lg *zap.Logger
    97  	var err error
    98  	lg, _globalP, err = log.InitLogger(pclogConfig)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	// Do not log stack traces at all, as we'll get the stack trace from the
   104  	// error itself.
   105  	lg = lg.WithOptions(zap.AddStacktrace(zap.DPanicLevel))
   106  
   107  	log.ReplaceGlobals(lg, _globalP)
   108  
   109  	var level zapcore.Level
   110  	err = level.UnmarshalText([]byte(cfg.Level))
   111  	if err != nil {
   112  		return errors.Trace(err)
   113  	}
   114  	err = initSaramaLogger(level)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	err = initGRPCLogger(level)
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	return nil
   124  }
   125  
   126  // ZapErrorFilter wraps zap.Error, if err is in given filterErrors, it will be set to nil
   127  func ZapErrorFilter(err error, filterErrors ...error) zap.Field {
   128  	cause := errors.Cause(err)
   129  	for _, ferr := range filterErrors {
   130  		if cause == ferr {
   131  			return zap.Error(nil)
   132  		}
   133  	}
   134  	return zap.Error(err)
   135  }
   136  
   137  // InitSaramaLogger hacks logger used in sarama lib
   138  func initSaramaLogger(level zapcore.Level) error {
   139  	// only available less than info level
   140  	if !zapcore.InfoLevel.Enabled(level) {
   141  		logger, err := zap.NewStdLogAt(log.L().With(zap.String("name", "sarama")), level)
   142  		if err != nil {
   143  			return errors.Trace(err)
   144  		}
   145  		sarama.Logger = logger
   146  	}
   147  	return nil
   148  }
   149  
   150  type grpcLoggerWriter struct {
   151  	logFunc func(msg string, fields ...zap.Field)
   152  }
   153  
   154  func (l *grpcLoggerWriter) Write(p []byte) (int, error) {
   155  	p = bytes.TrimSpace(p)
   156  	l.logFunc(string(p))
   157  	return len(p), nil
   158  }
   159  
   160  func levelToFunc(logger *zap.Logger, level zapcore.Level) (func(string, ...zap.Field), error) {
   161  	switch level {
   162  	case zap.DebugLevel:
   163  		return logger.Debug, nil
   164  	case zap.InfoLevel:
   165  		return logger.Info, nil
   166  	case zap.WarnLevel:
   167  		return logger.Warn, nil
   168  	case zap.ErrorLevel:
   169  		return logger.Error, nil
   170  	case zap.DPanicLevel:
   171  		return logger.DPanic, nil
   172  	case zap.PanicLevel:
   173  		return logger.Panic, nil
   174  	case zap.FatalLevel:
   175  		return logger.Fatal, nil
   176  	}
   177  	return nil, errors.Errorf("unrecognized level: %q", level)
   178  }
   179  
   180  func initGRPCLogger(level zapcore.Level) error {
   181  	// Inherit Golang gRPC verbosity setting.
   182  	// https://github.com/grpc/grpc-go/blob/v1.26.0/grpclog/loggerv2.go#L134-L138
   183  	var v int
   184  	vLevel := os.Getenv("GRPC_GO_LOG_VERBOSITY_LEVEL")
   185  	if vl, err := strconv.Atoi(vLevel); err == nil {
   186  		v = vl
   187  	}
   188  	if v <= 0 && level.Enabled(zapcore.ErrorLevel) {
   189  		// Sometimes gRPC log is very verbose.
   190  		level = zapcore.ErrorLevel
   191  		return nil
   192  	}
   193  
   194  	logger := log.L().With(zap.String("name", "grpc"))
   195  	// For gRPC 1.26.0, logging call stack:
   196  	//
   197  	// github.com/pingcap/ticdc/pkg/util.levelToFunc.func1
   198  	// github.com/pingcap/ticdc/pkg/util.(*grpcLoggerWriter).Write
   199  	// log.(*Logger).Output
   200  	// log.(*Logger).Printf
   201  	// google.golang.org/grpc/grpclog.(*loggerT).Infof
   202  	// google.golang.org/grpc/grpclog.Infof(...)
   203  	// Caller
   204  	logger = logger.WithOptions(zap.AddCallerSkip(5))
   205  	logFunc, err := levelToFunc(logger, level)
   206  	if err != nil {
   207  		return err
   208  	}
   209  	writer := &grpcLoggerWriter{logFunc: logFunc}
   210  	grpclog.SetLoggerV2(grpclog.NewLoggerV2WithVerbosity(writer, writer, writer, v))
   211  	return nil
   212  }