package log import ( "errors" "fmt" "log" "os" "path/filepath" "sync" "time" ) // TODO chef: // - 性能优化,目前是基于系统库log实现的 // - 和系统库中的log跑个benchmark对比 var logErr = errors.New("log:fxxk") type Logger interface { Debugf(format string, v ...interface{}) Infof(format string, v ...interface{}) Warnf(format string, v ...interface{}) Errorf(format string, v ...interface{}) Debug(v ...interface{}) Info(v ...interface{}) Warn(v ...interface{}) Error(v ...interface{}) Outputf(level Level, calldepth int, format string, v ...interface{}) Output(level Level, calldepth int, v ...interface{}) } type Level uint8 const ( LevelDebug = iota LevelInfo LevelWarn LevelError ) type Config struct { Level Level `json:"level"` // 日志级别,大于等于该级别的日志才会被输出 Filename string `json:"filename"` // 输出日志文件名,如果为空,则不写日志文件。可包含路径,路径不存在时,将自动创建 IsToStdout bool `json:"is_to_stdout"` // 是否以stdout输出到控制台 // 文件输出和控制台输出可同时打开,控制台输出主要用做开发时调试,支持level彩色输出,以及源码文件、行号 RotateMByte int `json:"rotate_mbyte"` // 日志大小达到多少兆后翻滚,如果为0,则不翻滚 } func New(c Config) (Logger, error) { var ( fl *log.Logger sl *log.Logger dir string fp *os.File err error ) if c.Level < LevelDebug || c.Level > LevelError { return nil, logErr } if c.Filename != "" { dir = filepath.Dir(c.Filename) if err := os.MkdirAll(dir, 0777); err != nil { return nil, err } fp, err = os.Create(c.Filename) if err != nil { return nil, err } fl = log.New(fp, "", log.Ldate|log.Lmicroseconds) } if c.IsToStdout { sl = log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds|log.Lshortfile) } l := &logger{ fileLogger: fl, stdoutLogger: sl, c: c, dir: dir, fp: fp, } return l, nil } const ( levelDebugString = "DEBUG " levelInfoString = "INFO " levelWarnString = "WARN " levelErrorString = "ERROR " levelDebugColorString = "\033[22;37mDEBUG\033[0m " levelInfoColorString = "\033[22;36mINFO\033[0m " levelWarnColorString = "\033[22;33mWARN\033[0m " levelErrorColorString = "\033[22;31mERROR\033[0m " ) var ( levelToString = map[Level]string{ LevelDebug: levelDebugString, LevelInfo: levelInfoString, LevelWarn: levelWarnString, LevelError: levelErrorString, } levelToColorString = map[Level]string{ LevelDebug: levelDebugColorString, LevelInfo: levelInfoColorString, LevelWarn: levelWarnColorString, LevelError: levelErrorColorString, } ) type logger struct { fileLogger *log.Logger stdoutLogger *log.Logger c Config dir string m sync.Mutex fp *os.File } func (l *logger) Debugf(format string, v ...interface{}) { l.Outputf(LevelDebug, 3, format, v...) } func (l *logger) Infof(format string, v ...interface{}) { l.Outputf(LevelInfo, 3, format, v...) } func (l *logger) Warnf(format string, v ...interface{}) { l.Outputf(LevelWarn, 3, format, v...) } func (l *logger) Errorf(format string, v ...interface{}) { l.Outputf(LevelError, 3, format, v...) } func (l *logger) Debug(v ...interface{}) { l.Output(LevelDebug, 3, v...) } func (l *logger) Info(v ...interface{}) { l.Output(LevelInfo, 3, v...) } func (l *logger) Warn(v ...interface{}) { l.Output(LevelWarn, 3, v...) } func (l *logger) Error(v ...interface{}) { l.Output(LevelError, 3, v...) } // TODO chef: Outputf 和 Output 代码重复 func (l *logger) Outputf(level Level, calldepth int, format string, v ...interface{}) { if l.c.Level > level { return } msg := fmt.Sprintf(format, v...) if l.stdoutLogger != nil { l.stdoutLogger.Output(calldepth, levelToColorString[level]+msg) } if l.fileLogger != nil { if l.c.RotateMByte > 0 { l.m.Lock() // 把写日志的操作也锁住,避免日志移走后,其他协程继续写老日志文件 // TODO chef: 性能比较差,系统库内部也有锁 defer l.m.Unlock() if fi, err := os.Stat(l.c.Filename); err == nil { if fi.Size() > int64(l.c.RotateMByte)*1024*1024 { newFileName := l.c.Filename + "." + time.Now().Format("20060102150405") if err := os.Rename(l.c.Filename, newFileName); err == nil { l.fp.Close() l.fp, _ = os.Create(l.c.Filename) l.fileLogger.SetOutput(l.fp) } } } } l.fileLogger.Output(calldepth, levelToString[level]+msg) } } func (l *logger) Output(level Level, calldepth int, v ...interface{}) { if l.c.Level > level { return } msg := fmt.Sprint(v...) if l.stdoutLogger != nil { l.stdoutLogger.Output(calldepth, levelToColorString[level]+msg) } if l.fileLogger != nil { if l.c.RotateMByte > 0 { l.m.Lock() // 把写日志的操作也锁住,避免日志移走后,其他协程继续写老日志文件 // TODO chef: 性能比较差,系统库内部也有锁 defer l.m.Unlock() if fi, err := os.Stat(l.c.Filename); err == nil { if fi.Size() > int64(l.c.RotateMByte)*1024*1024 { newFileName := l.c.Filename + "." + time.Now().Format("20060102150405") if err := os.Rename(l.c.Filename, newFileName); err == nil { l.fp.Close() l.fp, _ = os.Create(l.c.Filename) l.fileLogger.SetOutput(l.fp) } } } } l.fileLogger.Output(calldepth, levelToString[level]+msg) } } var global Logger func Debugf(format string, v ...interface{}) { global.Outputf(LevelDebug, 3, format, v...) } func Infof(format string, v ...interface{}) { global.Outputf(LevelInfo, 3, format, v...) } func Warnf(format string, v ...interface{}) { global.Outputf(LevelWarn, 3, format, v...) } func Errorf(format string, v ...interface{}) { global.Outputf(LevelError, 3, format, v...) } func Debug(v ...interface{}) { global.Output(LevelDebug, 3, v...) } func Info(v ...interface{}) { global.Output(LevelInfo, 3, v...) } func Warn(v ...interface{}) { global.Output(LevelWarn, 3, v...) } func Error(v ...interface{}) { global.Output(LevelError, 3, v...) } func Outputf(level Level, calldepth int, format string, v ...interface{}) { global.Outputf(level, calldepth, format, v...) } func Output(level Level, calldepth int, v ...interface{}) { global.Output(level, calldepth, v...) } // 这里不加锁保护,如果要调用Init函数初始化全局的Logger,那么由调用方保证调用Init函数时不会并发调用全局Logger的其他方法 func Init(c Config) error { var err error global, err = New(c) return err } func init() { global, _ = New(Config{ Level: LevelDebug, IsToStdout: true, }) }