github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/logutil/log.go (about) 1 // Copyright 2020 WHTCORPS INC, 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 "fmt" 20 "os" 21 "path/filepath" 22 "runtime" 23 "sort" 24 "strings" 25 "time" 26 27 "github.com/opentracing/opentracing-go" 28 tlog "github.com/opentracing/opentracing-go/log" 29 log "github.com/sirupsen/logrus" 30 "github.com/whtcorpsinc/errors" 31 zaplog "github.com/whtcorpsinc/log" 32 "go.uber.org/zap" 33 "go.uber.org/zap/zapembedded" 34 "gopkg.in/natefinch/lumberjack.v2" 35 ) 36 37 const ( 38 defaultLogTimeFormat = "2006/01/02 15:04:05.000" 39 // DefaultLogMaxSize is the default size of log files. 40 DefaultLogMaxSize = 300 // MB 41 // DefaultLogFormat is the default format of the log. 42 DefaultLogFormat = "text" 43 defaultLogLevel = log.InfoLevel 44 // DefaultSlowThreshold is the default slow log threshold in millisecond. 45 DefaultSlowThreshold = 300 46 // DefaultQueryLogMaxLen is the default max length of the query in the log. 47 DefaultQueryLogMaxLen = 4096 48 // DefaultRecordCausetInSlowLog is the default value for whether enable log query plan in the slow log. 49 DefaultRecordCausetInSlowLog = 1 50 // DefaultMilevaDBEnableSlowLog enables MilevaDB to log slow queries. 51 DefaultMilevaDBEnableSlowLog = true 52 ) 53 54 // EmptyFileLogConfig is an empty FileLogConfig. 55 var EmptyFileLogConfig = FileLogConfig{} 56 57 // FileLogConfig serializes file log related config in toml/json. 58 type FileLogConfig struct { 59 zaplog.FileLogConfig 60 } 61 62 // NewFileLogConfig creates a FileLogConfig. 63 func NewFileLogConfig(maxSize uint) FileLogConfig { 64 return FileLogConfig{FileLogConfig: zaplog.FileLogConfig{ 65 MaxSize: int(maxSize), 66 }, 67 } 68 } 69 70 // LogConfig serializes log related config in toml/json. 71 type LogConfig struct { 72 zaplog.Config 73 74 // SlowQueryFile filename, default to File log config on empty. 75 SlowQueryFile string 76 } 77 78 // NewLogConfig creates a LogConfig. 79 func NewLogConfig(level, format, slowQueryFile string, fileCfg FileLogConfig, disableTimestamp bool, opts ...func(*zaplog.Config)) *LogConfig { 80 c := &LogConfig{ 81 Config: zaplog.Config{ 82 Level: level, 83 Format: format, 84 DisableTimestamp: disableTimestamp, 85 File: fileCfg.FileLogConfig, 86 }, 87 SlowQueryFile: slowQueryFile, 88 } 89 for _, opt := range opts { 90 opt(&c.Config) 91 } 92 return c 93 } 94 95 // isSKippedPackageName tests wether path name is on log library calling stack. 96 func isSkippedPackageName(name string) bool { 97 return strings.Contains(name, "github.com/sirupsen/logrus") || 98 strings.Contains(name, "github.com/embeddedos/pkg/capnslog") 99 } 100 101 // modifyHook injects file name and line pos into log entry. 102 type contextHook struct{} 103 104 // Fire implements logrus.Hook interface 105 // https://github.com/sirupsen/logrus/issues/63 106 func (hook *contextHook) Fire(entry *log.Entry) error { 107 pc := make([]uintptr, 4) 108 cnt := runtime.Callers(8, pc) 109 110 for i := 0; i < cnt; i++ { 111 fu := runtime.FuncForPC(pc[i] - 1) 112 name := fu.Name() 113 if !isSkippedPackageName(name) { 114 file, line := fu.FileLine(pc[i] - 1) 115 entry.Data["file"] = filepath.Base(file) 116 entry.Data["line"] = line 117 break 118 } 119 } 120 return nil 121 } 122 123 // Levels implements logrus.Hook interface. 124 func (hook *contextHook) Levels() []log.Level { 125 return log.AllLevels 126 } 127 128 func stringToLogLevel(level string) log.Level { 129 switch strings.ToLower(level) { 130 case "fatal": 131 return log.FatalLevel 132 case "error": 133 return log.ErrorLevel 134 case "warn", "warning": 135 return log.WarnLevel 136 case "debug": 137 return log.DebugLevel 138 case "info": 139 return log.InfoLevel 140 } 141 return defaultLogLevel 142 } 143 144 // textFormatter is for compatibility with ngaut/log 145 type textFormatter struct { 146 DisableTimestamp bool 147 EnableEntryOrder bool 148 } 149 150 // Format implements logrus.Formatter 151 func (f *textFormatter) Format(entry *log.Entry) ([]byte, error) { 152 var b *bytes.Buffer 153 if entry.Buffer != nil { 154 b = entry.Buffer 155 } else { 156 b = &bytes.Buffer{} 157 } 158 159 if !f.DisableTimestamp { 160 fmt.Fprintf(b, "%s ", entry.Time.Format(defaultLogTimeFormat)) 161 } 162 if file, ok := entry.Data["file"]; ok { 163 fmt.Fprintf(b, "%s:%v:", file, entry.Data["line"]) 164 } 165 fmt.Fprintf(b, " [%s] %s", entry.Level.String(), entry.Message) 166 167 if f.EnableEntryOrder { 168 keys := make([]string, 0, len(entry.Data)) 169 for k := range entry.Data { 170 if k != "file" && k != "line" { 171 keys = append(keys, k) 172 } 173 } 174 sort.Strings(keys) 175 for _, k := range keys { 176 fmt.Fprintf(b, " %v=%v", k, entry.Data[k]) 177 } 178 } else { 179 for k, v := range entry.Data { 180 if k != "file" && k != "line" { 181 fmt.Fprintf(b, " %v=%v", k, v) 182 } 183 } 184 } 185 186 b.WriteByte('\n') 187 188 return b.Bytes(), nil 189 } 190 191 const ( 192 // SlowLogTimeFormat is the time format for slow log. 193 SlowLogTimeFormat = time.RFC3339Nano 194 // OldSlowLogTimeFormat is the first version of the the time format for slow log, This is use for compatibility. 195 OldSlowLogTimeFormat = "2006-01-02-15:04:05.999999999 -0700" 196 ) 197 198 type slowLogFormatter struct{} 199 200 func (f *slowLogFormatter) Format(entry *log.Entry) ([]byte, error) { 201 var b *bytes.Buffer 202 if entry.Buffer != nil { 203 b = entry.Buffer 204 } else { 205 b = &bytes.Buffer{} 206 } 207 208 fmt.Fprintf(b, "# Time: %s\n", entry.Time.Format(SlowLogTimeFormat)) 209 fmt.Fprintf(b, "%s\n", entry.Message) 210 return b.Bytes(), nil 211 } 212 213 func stringToLogFormatter(format string, disableTimestamp bool) log.Formatter { 214 switch strings.ToLower(format) { 215 case "text": 216 return &textFormatter{ 217 DisableTimestamp: disableTimestamp, 218 } 219 default: 220 return &textFormatter{} 221 } 222 } 223 224 // initFileLog initializes file based logging options. 225 func initFileLog(cfg *zaplog.FileLogConfig, logger *log.Logger) error { 226 if st, err := os.Stat(cfg.Filename); err == nil { 227 if st.IsDir() { 228 return errors.New("can't use directory as log file name") 229 } 230 } 231 if cfg.MaxSize == 0 { 232 cfg.MaxSize = DefaultLogMaxSize 233 } 234 235 // use lumberjack to logrotate 236 output := &lumberjack.Logger{ 237 Filename: cfg.Filename, 238 MaxSize: cfg.MaxSize, 239 MaxBackups: cfg.MaxBackups, 240 MaxAge: cfg.MaxDays, 241 LocalTime: true, 242 } 243 244 if logger == nil { 245 log.SetOutput(output) 246 } else { 247 logger.Out = output 248 } 249 return nil 250 } 251 252 // SlowQueryLogger is used to log slow query, InitLogger will modify it according to config file. 253 var SlowQueryLogger = log.StandardLogger() 254 255 // SlowQueryZapLogger is used to log slow query, InitZapLogger will modify it according to config file. 256 var SlowQueryZapLogger = zaplog.L() 257 258 // InitLogger initializes FIDel's logger. 259 func InitLogger(cfg *LogConfig) error { 260 log.SetLevel(stringToLogLevel(cfg.Level)) 261 log.AddHook(&contextHook{}) 262 263 if cfg.Format == "" { 264 cfg.Format = DefaultLogFormat 265 } 266 formatter := stringToLogFormatter(cfg.Format, cfg.DisableTimestamp) 267 log.SetFormatter(formatter) 268 269 if len(cfg.File.Filename) != 0 { 270 if err := initFileLog(&cfg.File, nil); err != nil { 271 return errors.Trace(err) 272 } 273 } 274 275 if len(cfg.SlowQueryFile) != 0 { 276 SlowQueryLogger = log.New() 277 tmp := cfg.File 278 tmp.Filename = cfg.SlowQueryFile 279 if err := initFileLog(&tmp, SlowQueryLogger); err != nil { 280 return errors.Trace(err) 281 } 282 SlowQueryLogger.Formatter = &slowLogFormatter{} 283 } 284 285 return nil 286 } 287 288 // InitZapLogger initializes a zap logger with cfg. 289 func InitZapLogger(cfg *LogConfig) error { 290 gl, props, err := zaplog.InitLogger(&cfg.Config, zap.AddStacktrace(zapembedded.FatalLevel)) 291 if err != nil { 292 return errors.Trace(err) 293 } 294 zaplog.ReplaceGlobals(gl, props) 295 296 if len(cfg.SlowQueryFile) != 0 { 297 sqfCfg := zaplog.FileLogConfig{ 298 MaxSize: cfg.File.MaxSize, 299 Filename: cfg.SlowQueryFile, 300 } 301 sqCfg := &zaplog.Config{ 302 Level: cfg.Level, 303 Format: cfg.Format, 304 DisableTimestamp: cfg.DisableTimestamp, 305 File: sqfCfg, 306 } 307 sqLogger, _, err := zaplog.InitLogger(sqCfg) 308 if err != nil { 309 return errors.Trace(err) 310 } 311 SlowQueryZapLogger = sqLogger 312 } else { 313 SlowQueryZapLogger = gl 314 } 315 316 return nil 317 } 318 319 // SetLevel sets the zap logger's level. 320 func SetLevel(level string) error { 321 l := zap.NewAtomicLevel() 322 if err := l.UnmarshalText([]byte(level)); err != nil { 323 return errors.Trace(err) 324 } 325 zaplog.SetLevel(l.Level()) 326 return nil 327 } 328 329 type ctxLogKeyType struct{} 330 331 var ctxLogKey = ctxLogKeyType{} 332 333 // Logger gets a contextual logger from current context. 334 // contextual logger will output common fields from context. 335 func Logger(ctx context.Context) *zap.Logger { 336 if ctxlogger, ok := ctx.Value(ctxLogKey).(*zap.Logger); ok { 337 return ctxlogger 338 } 339 return zaplog.L() 340 } 341 342 // BgLogger is alias of `logutil.BgLogger()` 343 func BgLogger() *zap.Logger { 344 return zaplog.L() 345 } 346 347 // WithConnID attaches connId to context. 348 func WithConnID(ctx context.Context, connID uint32) context.Context { 349 var logger *zap.Logger 350 if ctxLogger, ok := ctx.Value(ctxLogKey).(*zap.Logger); ok { 351 logger = ctxLogger 352 } else { 353 logger = zaplog.L() 354 } 355 return context.WithValue(ctx, ctxLogKey, logger.With(zap.Uint32("conn", connID))) 356 } 357 358 // WithKeyValue attaches key/value to context. 359 func WithKeyValue(ctx context.Context, key, value string) context.Context { 360 var logger *zap.Logger 361 if ctxLogger, ok := ctx.Value(ctxLogKey).(*zap.Logger); ok { 362 logger = ctxLogger 363 } else { 364 logger = zaplog.L() 365 } 366 return context.WithValue(ctx, ctxLogKey, logger.With(zap.String(key, value))) 367 } 368 369 // TraceEventKey presents the TraceEventKey in span log. 370 const TraceEventKey = "event" 371 372 // Event records event in current tracing span. 373 func Event(ctx context.Context, event string) { 374 if span := opentracing.SpanFromContext(ctx); span != nil && span.Tracer() != nil { 375 span.LogFields(tlog.String(TraceEventKey, event)) 376 } 377 } 378 379 // Eventf records event in current tracing span with format support. 380 func Eventf(ctx context.Context, format string, args ...interface{}) { 381 if span := opentracing.SpanFromContext(ctx); span != nil && span.Tracer() != nil { 382 span.LogFields(tlog.String(TraceEventKey, fmt.Sprintf(format, args...))) 383 } 384 } 385 386 // SetTag sets tag ekv-pair in current tracing span 387 func SetTag(ctx context.Context, key string, value interface{}) { 388 if span := opentracing.SpanFromContext(ctx); span != nil && span.Tracer() != nil { 389 span.SetTag(key, value) 390 } 391 }