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