| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // +build go1.9 |
| |
| package log |
| |
| import ( |
| "fmt" |
| "log" |
| "os" |
| "runtime" |
| "runtime/debug" |
| "strings" |
| "time" |
| |
| "github.com/apache/servicecomb-service-center/pkg/util" |
| |
| "github.com/natefinch/lumberjack" |
| "go.uber.org/zap" |
| "go.uber.org/zap/zapcore" |
| ) |
| |
| const ( |
| defaultLogLevel = "DEBUG" |
| ) |
| |
| var ( |
| StdoutSyncer = zapcore.Lock(os.Stdout) |
| StderrSyncer = zapcore.Lock(os.Stderr) |
| |
| zapLevelMap = map[string]zapcore.Level{ |
| "DEBUG": zap.DebugLevel, |
| "INFO": zap.InfoLevel, |
| "WARN": zap.WarnLevel, |
| "ERROR": zap.ErrorLevel, |
| "FATAL": zap.FatalLevel, |
| } |
| ) |
| |
| // Config struct for lager and rotate parameters |
| type Config struct { |
| LoggerLevel string |
| LoggerFile string |
| // if false, log print with JSON format |
| LogFormatText bool |
| // M Bytes |
| LogRotateSize int |
| LogBackupCount int |
| // days |
| LogBackupAge int |
| CallerSkip int |
| NoTime bool // if true, not record time |
| NoLevel bool // if true, not record level |
| NoCaller bool // if true, not record caller |
| } |
| |
| func (cfg Config) WithCallerSkip(s int) Config { |
| cfg.CallerSkip = s |
| return cfg |
| } |
| |
| func (cfg Config) WithFile(path string) Config { |
| cfg.LoggerFile = path |
| return cfg |
| } |
| |
| func (cfg Config) WithNoTime(b bool) Config { |
| cfg.NoTime = b |
| return cfg |
| } |
| |
| func (cfg Config) WithNoLevel(b bool) Config { |
| cfg.NoLevel = b |
| return cfg |
| } |
| |
| func (cfg Config) WithNoCaller(b bool) Config { |
| cfg.NoCaller = b |
| return cfg |
| } |
| |
| func Configure() Config { |
| return Config{ |
| LoggerLevel: defaultLogLevel, |
| LogFormatText: true, |
| CallerSkip: globalCallerSkip, |
| } |
| } |
| |
| func toZapConfig(c Config) zapcore.Core { |
| // level config |
| l, ok := zapLevelMap[strings.ToUpper(c.LoggerLevel)] |
| if !ok { |
| l = zap.DebugLevel |
| } |
| var levelEnabler zap.LevelEnablerFunc = func(level zapcore.Level) bool { |
| return level >= l |
| } |
| |
| // log format |
| format := zapcore.EncoderConfig{ |
| MessageKey: "message", |
| LevelKey: "level", |
| TimeKey: "time", |
| NameKey: "logger", |
| CallerKey: "caller", |
| StacktraceKey: "stack", |
| LineEnding: zapcore.DefaultLineEnding, |
| EncodeLevel: zapcore.CapitalLevelEncoder, |
| EncodeTime: zapcore.ISO8601TimeEncoder, |
| EncodeDuration: zapcore.StringDurationEncoder, |
| EncodeCaller: zapcore.ShortCallerEncoder, |
| EncodeName: zapcore.FullNameEncoder, |
| } |
| if c.NoCaller { |
| format.CallerKey = "" |
| } |
| if c.NoLevel { |
| format.LevelKey = "" |
| levelEnabler = func(_ zapcore.Level) bool { return true } |
| } |
| if c.NoTime { |
| format.TimeKey = "" |
| } |
| var enc zapcore.Encoder |
| if c.LogFormatText { |
| enc = zapcore.NewConsoleEncoder(format) |
| } else { |
| enc = zapcore.NewJSONEncoder(format) |
| } |
| |
| // log rotate |
| var syncer zapcore.WriteSyncer |
| if len(c.LoggerFile) > 0 { |
| syncer = zapcore.AddSync(&lumberjack.Logger{ |
| Filename: c.LoggerFile, |
| MaxSize: c.LogRotateSize, |
| MaxBackups: c.LogBackupCount, |
| MaxAge: c.LogBackupAge, |
| LocalTime: true, |
| Compress: true, |
| }) |
| } else { |
| syncer = StdoutSyncer |
| } |
| |
| //zap.NewDevelopment() |
| return zapcore.NewCore(enc, syncer, levelEnabler) |
| } |
| |
| type Logger struct { |
| Config Config |
| |
| zapLogger *zap.Logger |
| zapSugar *zap.SugaredLogger |
| } |
| |
| func (l *Logger) Debug(msg string) { |
| l.zapLogger.Debug(msg) |
| } |
| |
| func (l *Logger) Debugf(format string, args ...interface{}) { |
| l.zapSugar.Debugf(format, args...) |
| } |
| |
| func (l *Logger) Info(msg string) { |
| l.zapLogger.Info(msg) |
| } |
| |
| func (l *Logger) Infof(format string, args ...interface{}) { |
| l.zapSugar.Infof(format, args...) |
| } |
| |
| func (l *Logger) Warn(msg string) { |
| l.zapLogger.Warn(msg) |
| } |
| |
| func (l *Logger) Warnf(format string, args ...interface{}) { |
| l.zapSugar.Warnf(format, args...) |
| } |
| |
| func (l *Logger) Error(msg string, err error) { |
| if err == nil { |
| l.zapLogger.Error(msg) |
| return |
| } |
| l.zapLogger.Error(msg, zap.String("error", err.Error())) |
| } |
| |
| func (l *Logger) Errorf(err error, format string, args ...interface{}) { |
| if err == nil { |
| l.zapSugar.Errorf(format, args...) |
| return |
| } |
| l.zapSugar.With("error", err.Error()).Errorf(format, args...) |
| } |
| |
| func (l *Logger) Fatal(msg string, err error) { |
| if err == nil { |
| l.zapLogger.Panic(msg) |
| return |
| } |
| l.zapLogger.Panic(msg, zap.String("error", err.Error())) |
| } |
| |
| func (l *Logger) Fatalf(err error, format string, args ...interface{}) { |
| if err == nil { |
| l.zapSugar.Panicf(format, args...) |
| return |
| } |
| l.zapSugar.With("error", err.Error()).Panicf(format, args...) |
| } |
| |
| // callSkip equals to 0 identify the caller of Recover() |
| func (l *Logger) Recover(r interface{}, callerSkip int) { |
| e := zapcore.Entry{ |
| Level: zap.PanicLevel, // zapcore sync automatically when larger than ErrorLevel |
| Time: time.Now(), |
| Caller: zapcore.NewEntryCaller(runtime.Caller(callerSkip + 1)), |
| Stack: zap.Stack("stack").String, |
| } |
| // recover logs also output to stderr |
| fmt.Fprintf(StderrSyncer, "%s\tPANIC\t%s\t%s\n%v\n", |
| e.Time.Format("2006-01-02T15:04:05.000Z0700"), |
| e.Caller.TrimmedPath(), |
| r, |
| e.Stack) |
| err := StderrSyncer.Sync() // sync immediately, for server may exit abnormally |
| if err != nil { |
| log.Println(err) |
| } |
| if err := l.zapLogger.Core().With([]zap.Field{zap.Reflect("recover", r)}).Write(e, nil); err != nil { |
| fmt.Fprintf(StderrSyncer, "%s\tERROR\t%v\n", time.Now().Format("2006-01-02T15:04:05.000Z0700"), err) |
| fmt.Fprintln(StderrSyncer, util.BytesToStringWithNoCopy(debug.Stack())) |
| err = StderrSyncer.Sync() |
| if err != nil { |
| log.Println(err) |
| } |
| return |
| } |
| } |
| |
| func (l *Logger) Sync() { |
| err := l.zapLogger.Sync() |
| if err != nil { |
| log.Println(err) |
| } |
| err = StderrSyncer.Sync() |
| if err != nil { |
| log.Println(err) |
| } |
| err = StdoutSyncer.Sync() |
| if err != nil { |
| log.Println(err) |
| } |
| } |
| |
| func NewLogger(cfg Config) *Logger { |
| opts := make([]zap.Option, 1) |
| opts[0] = zap.ErrorOutput(StderrSyncer) |
| if !cfg.NoCaller { |
| opts = append(opts, zap.AddCaller(), zap.AddCallerSkip(cfg.CallerSkip)) |
| } |
| l := zap.New(toZapConfig(cfg), opts...) |
| return &Logger{ |
| Config: cfg, |
| zapLogger: l, |
| zapSugar: l.Sugar(), |
| } |
| } |