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 }