github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/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 "context" 19 "io" 20 "os" 21 "strconv" 22 "strings" 23 24 "github.com/IBM/sarama" 25 "github.com/gin-gonic/gin" 26 "github.com/go-sql-driver/mysql" 27 "github.com/pingcap/errors" 28 "github.com/pingcap/log" 29 "go.uber.org/zap" 30 "go.uber.org/zap/zapcore" 31 "google.golang.org/grpc/grpclog" 32 ) 33 34 // globalP is the global ZapProperties in log 35 var globalP *log.ZapProperties 36 37 const ( 38 defaultLogLevel = "info" 39 defaultLogMaxDays = 7 40 defaultLogMaxSize = 512 // MB 41 ) 42 43 // Config serializes log related config in toml/json. 44 type Config struct { 45 // Log level. 46 Level string `toml:"level" json:"level"` 47 // Log filename, leave empty to disable file log. 48 File string `toml:"file" json:"file"` 49 // Max size for a single file, in MB. 50 FileMaxSize int `toml:"max-size" json:"max-size"` 51 // Max log keep days, default is never deleting. 52 FileMaxDays int `toml:"max-days" json:"max-days"` 53 // Maximum number of old log files to retain. 54 FileMaxBackups int `toml:"max-backups" json:"max-backups"` 55 // ZapInternalErrOutput specify where the internal error of zap logger should be send to. 56 ZapInternalErrOutput string `toml:"error-output" json:"error-output"` 57 } 58 59 // Adjust adjusts config 60 func (cfg *Config) Adjust() { 61 if len(cfg.Level) == 0 { 62 cfg.Level = defaultLogLevel 63 } 64 if cfg.Level == "warning" { 65 cfg.Level = "warn" 66 } 67 if cfg.FileMaxSize == 0 { 68 cfg.FileMaxSize = defaultLogMaxSize 69 } 70 if cfg.FileMaxDays == 0 { 71 cfg.FileMaxDays = defaultLogMaxDays 72 } 73 } 74 75 // SetLogLevel changes TiCDC log level dynamically. 76 func SetLogLevel(level string) error { 77 oldLevel := log.GetLevel() 78 if strings.EqualFold(oldLevel.String(), level) { 79 return nil 80 } 81 var lv zapcore.Level 82 err := lv.UnmarshalText([]byte(level)) 83 if err != nil { 84 return errors.Trace(err) 85 } 86 log.SetLevel(lv) 87 return nil 88 } 89 90 // loggerOp is the op for logger control 91 type loggerOp struct { 92 isInitGRPCLogger bool 93 isInitSaramaLogger bool 94 isInitMySQLLogger bool 95 output zapcore.WriteSyncer 96 } 97 98 func (op *loggerOp) applyOpts(opts []LoggerOpt) { 99 for _, opt := range opts { 100 opt(op) 101 } 102 } 103 104 // LoggerOpt is the logger option 105 type LoggerOpt func(*loggerOp) 106 107 // WithInitGRPCLogger enables grpc logger initialization when initializes global logger 108 func WithInitGRPCLogger() LoggerOpt { 109 return func(op *loggerOp) { 110 op.isInitGRPCLogger = true 111 } 112 } 113 114 // WithInitSaramaLogger enables sarama logger initialization when initializes global logger 115 func WithInitSaramaLogger() LoggerOpt { 116 return func(op *loggerOp) { 117 op.isInitSaramaLogger = true 118 } 119 } 120 121 // WithInitMySQLLogger enables mysql logger initialization when initializes global logger 122 func WithInitMySQLLogger() LoggerOpt { 123 return func(op *loggerOp) { 124 op.isInitMySQLLogger = true 125 } 126 } 127 128 // WithOutputWriteSyncer will replace the WriteSyncer of global logger with customized WriteSyncer 129 // Easy for test when using zaptest.Buffer as WriteSyncer 130 func WithOutputWriteSyncer(output zapcore.WriteSyncer) LoggerOpt { 131 return func(op *loggerOp) { 132 op.output = output 133 } 134 } 135 136 // InitLogger initializes logger 137 func InitLogger(cfg *Config, opts ...LoggerOpt) error { 138 var op loggerOp 139 op.applyOpts(opts) 140 141 pclogConfig := &log.Config{ 142 Level: cfg.Level, 143 File: log.FileLogConfig{ 144 Filename: cfg.File, 145 MaxSize: cfg.FileMaxSize, 146 MaxDays: cfg.FileMaxDays, 147 MaxBackups: cfg.FileMaxBackups, 148 }, 149 ErrorOutputPath: cfg.ZapInternalErrOutput, 150 } 151 152 var lg *zap.Logger 153 var err error 154 if op.output == nil { 155 lg, globalP, err = log.InitLogger(pclogConfig) 156 } else { 157 lg, globalP, err = log.InitLoggerWithWriteSyncer(pclogConfig, op.output, nil) 158 } 159 if err != nil { 160 return err 161 } 162 163 // Do not log stack traces at all, as we'll get the stack trace from the 164 // error itself. 165 lg = lg.WithOptions(zap.AddStacktrace(zap.DPanicLevel)) 166 log.ReplaceGlobals(lg, globalP) 167 168 return initOptionalComponent(&op, cfg) 169 } 170 171 // initOptionalComponent initializes some optional components 172 func initOptionalComponent(op *loggerOp, cfg *Config) error { 173 var level zapcore.Level 174 if op.isInitGRPCLogger || op.isInitSaramaLogger { 175 err := level.UnmarshalText([]byte(cfg.Level)) 176 if err != nil { 177 return errors.Trace(err) 178 } 179 } 180 181 if op.isInitGRPCLogger { 182 if err := initGRPCLogger(level); err != nil { 183 return err 184 } 185 } 186 187 if op.isInitSaramaLogger { 188 if err := initSaramaLogger(level); err != nil { 189 return err 190 } 191 } 192 193 if op.isInitMySQLLogger { 194 if err := initMySQLLogger(); err != nil { 195 return err 196 } 197 } 198 199 return nil 200 } 201 202 // ZapErrorFilter wraps zap.Error, if err is in given filterErrors, it will be set to nil 203 func ZapErrorFilter(err error, filterErrors ...error) zap.Field { 204 cause := errors.Cause(err) 205 for _, ferr := range filterErrors { 206 if cause == ferr { 207 return zap.Error(nil) 208 } 209 } 210 return zap.Error(err) 211 } 212 213 // initMySQLLogger setup logger used in mysql lib 214 func initMySQLLogger() error { 215 // MySQL lib only prints error logs. 216 level := zapcore.ErrorLevel 217 logger, err := zap.NewStdLogAt(log.L().With(zap.String("component", "[mysql]")), level) 218 if err != nil { 219 return errors.Trace(err) 220 } 221 return mysql.SetLogger(logger) 222 } 223 224 // initSaramaLogger hacks logger used in sarama lib 225 func initSaramaLogger(level zapcore.Level) error { 226 // only available less than info level 227 if !zapcore.InfoLevel.Enabled(level) { 228 logger, err := zap.NewStdLogAt(log.L().With(zap.String("component", "sarama")), level) 229 if err != nil { 230 return errors.Trace(err) 231 } 232 sarama.Logger = logger 233 } 234 return nil 235 } 236 237 type loggerWriter struct { 238 logFunc func(msg string, fields ...zap.Field) 239 } 240 241 func (l *loggerWriter) Write(p []byte) (int, error) { 242 p = bytes.TrimSpace(p) 243 l.logFunc(string(p)) 244 return len(p), nil 245 } 246 247 func levelToFunc(logger *zap.Logger, level zapcore.Level) (func(string, ...zap.Field), error) { 248 switch level { 249 case zap.DebugLevel: 250 return logger.Debug, nil 251 case zap.InfoLevel: 252 return logger.Info, nil 253 case zap.WarnLevel: 254 return logger.Warn, nil 255 case zap.ErrorLevel: 256 return logger.Error, nil 257 case zap.DPanicLevel: 258 return logger.DPanic, nil 259 case zap.PanicLevel: 260 return logger.Panic, nil 261 case zap.FatalLevel: 262 return logger.Fatal, nil 263 } 264 return nil, errors.Errorf("unrecognized level: %q", level) 265 } 266 267 func initGRPCLogger(level zapcore.Level) error { 268 // Inherit Golang gRPC verbosity setting. 269 // https://github.com/grpc/grpc-go/blob/v1.26.0/grpclog/loggerv2.go#L134-L138 270 var v int 271 vLevel := os.Getenv("GRPC_GO_LOG_VERBOSITY_LEVEL") 272 if vl, err := strconv.Atoi(vLevel); err == nil { 273 v = vl 274 } 275 if v <= 0 && level.Enabled(zapcore.ErrorLevel) { 276 // Sometimes gRPC log is very verbose. 277 level = zapcore.ErrorLevel 278 return nil 279 } 280 281 logger := log.L().With(zap.String("component", "grpc")) 282 // For gRPC 1.26.0, logging call stack: 283 // 284 // github.com/pingcap/tiflow/pkg/util.levelToFunc.func1 285 // github.com/pingcap/tiflow/pkg/util.(*grpcLoggerWriter).Write 286 // log.(*Logger).Output 287 // log.(*Logger).Printf 288 // google.golang.org/grpc/grpclog.(*loggerT).Infof 289 // google.golang.org/grpc/grpclog.Infof(...) 290 // Caller 291 logger = logger.WithOptions(zap.AddCallerSkip(5)) 292 logFunc, err := levelToFunc(logger, level) 293 if err != nil { 294 return err 295 } 296 writer := &loggerWriter{logFunc: logFunc} 297 grpclog.SetLoggerV2(grpclog.NewLoggerV2WithVerbosity(writer, writer, writer, v)) 298 return nil 299 } 300 301 // ErrorFilterContextCanceled log the msg and fields but do nothing if fields have cancel error 302 func ErrorFilterContextCanceled(logger *zap.Logger, msg string, fields ...zap.Field) { 303 for _, field := range fields { 304 switch field.Type { 305 case zapcore.StringType: 306 if field.Key == "error" && strings.Contains(field.String, context.Canceled.Error()) { 307 return 308 } 309 case zapcore.ErrorType: 310 err, ok := field.Interface.(error) 311 if ok && errors.Cause(err) == context.Canceled { 312 return 313 } 314 } 315 } 316 317 logger.WithOptions(zap.AddCallerSkip(1)).Error(msg, fields...) 318 } 319 320 // ShortError contructs a field which only records the error message without the 321 // verbose text (i.e. excludes the stack trace). 322 func ShortError(err error) zap.Field { 323 if err == nil { 324 return zap.Skip() 325 } 326 return zap.String("error", err.Error()) 327 } 328 329 // WithComponent return a logger with specified component scope 330 func WithComponent(component string) *zap.Logger { 331 return log.L().With(zap.String("component", component)) 332 } 333 334 // InitGinLogWritter initialize loggers for Gin. 335 func InitGinLogWritter() io.Writer { 336 currentLevel := log.GetLevel() 337 if currentLevel == zapcore.DebugLevel { 338 gin.SetMode(gin.DebugMode) 339 } else { 340 gin.SetMode(gin.ReleaseMode) 341 } 342 logger := WithComponent("gin") 343 logFunc, _ := levelToFunc(logger, currentLevel) 344 gin.DefaultWriter = &loggerWriter{logFunc: logFunc} 345 logFunc, _ = levelToFunc(logger, zapcore.ErrorLevel) 346 gin.DefaultErrorWriter = &loggerWriter{logFunc: logFunc} 347 return gin.DefaultErrorWriter 348 }