// Package lfshook is hook for sirupsen/logrus that used for writing the logs to local files. package lfshook import ( "fmt" "io" "reflect" "sync" "github.com/sirupsen/logrus" "gopkg.in/natefinch/lumberjack.v2" ) // We are logging to file, strip colors to make the output more readable. var defaultFormatter = &logrus.TextFormatter{DisableColors: true} // PathMap is map for mapping a log level to a file's path. // Multiple levels may share a file, but multiple files may not be used for one level. // type PathMap map[logrus.Level]string // 一个 level 对应 一个 logger type LoggerMap map[logrus.Level]*lumberjack.Logger // WriterMap is map for mapping a log level to an io.Writer. // Multiple levels may share a writer, but multiple writers may not be used for one level. type WriterMap map[logrus.Level]io.Writer type RotateFileConfig struct { Filename string MaxSize int MaxBackups int MaxAge int Level logrus.Level Formatter logrus.Formatter } // LfsHook is a hook to handle writing to local log files. type LfsHook struct { loggers LoggerMap writers WriterMap levels []logrus.Level lock *sync.Mutex formatter logrus.Formatter Config RotateFileConfig defaultPath string defaultWriter io.Writer hasDefaultPath bool hasDefaultWriter bool } // NewHook returns new LFS hook. // Output can be a string, io.Writer, WriterMap or PathMap. // If using io.Writer or WriterMap, user is responsible for closing the used io.Writer. func NewHook(output interface{}, formatter logrus.Formatter) *LfsHook { hook := &LfsHook{ lock: new(sync.Mutex), } hook.SetFormatter(formatter) switch output.(type) { case string: hook.SetDefaultPath(output.(string)) break case io.Writer: hook.SetDefaultWriter(output.(io.Writer)) break case LoggerMap: hook.loggers = output.(LoggerMap) for level := range output.(LoggerMap) { hook.levels = append(hook.levels, level) } break case WriterMap: hook.writers = output.(WriterMap) for level := range output.(WriterMap) { hook.levels = append(hook.levels, level) } break default: panic(fmt.Sprintf("unsupported level map type: %v", reflect.TypeOf(output))) } return hook } // SetFormatter sets the format that will be used by hook. // If using text formatter, this method will disable color output to make the log file more readable. func (hook *LfsHook) SetFormatter(formatter logrus.Formatter) { hook.lock.Lock() defer hook.lock.Unlock() if formatter == nil { formatter = defaultFormatter } else { switch formatter.(type) { case *logrus.TextFormatter: textFormatter := formatter.(*logrus.TextFormatter) textFormatter.DisableColors = true } } hook.formatter = formatter } // SetDefaultPath sets default path for levels that don't have any defined output path. func (hook *LfsHook) SetDefaultPath(defaultPath string) { hook.lock.Lock() defer hook.lock.Unlock() hook.defaultPath = defaultPath hook.hasDefaultPath = true } // SetDefaultWriter sets default writer for levels that don't have any defined writer. func (hook *LfsHook) SetDefaultWriter(defaultWriter io.Writer) { hook.lock.Lock() defer hook.lock.Unlock() hook.defaultWriter = defaultWriter hook.hasDefaultWriter = true } // Fire writes the log file to defined path or using the defined writer. // User who run this function needs write permissions to the file or directory if the file does not yet exist. func (hook *LfsHook) Fire(entry *logrus.Entry) error { hook.lock.Lock() defer hook.lock.Unlock() if hook.writers != nil || hook.hasDefaultWriter { return hook.ioWrite(entry) } else if hook.loggers != nil || hook.hasDefaultPath { return hook.fileWrite(entry) } return nil } // Write a log line to an io.Writer. func (hook *LfsHook) ioWrite(entry *logrus.Entry) error { var ( writer io.Writer msg []byte err error ok bool ) if writer, ok = hook.writers[entry.Level]; !ok { if hook.hasDefaultWriter { writer = hook.defaultWriter } else { return nil } } // use our formatter instead of entry.String() msg, err = hook.formatter.Format(entry) if err != nil { log.Println("failed to generate string for entry:", err) return err } _, err = writer.Write(msg) return err } // Write a log line directly to a file. func (hook *LfsHook) fileWrite(entry *logrus.Entry) error { var ( logger *lumberjack.Logger msg []byte err error ok bool ) if logger, ok = hook.loggers[entry.Level]; !ok { if hook.hasDefaultPath { logger = &lumberjack.Logger{ Filename: hook.defaultPath, MaxSize: 10, // maxSize M MaxBackups: 5, // keep 5 file MaxAge: 7, // 7 day } } else { return nil } } // use our formatter instead of entry.String() msg, err = hook.formatter.Format(entry) if err != nil { log.Println("failed to generate string for entry:", err) return err } defer logger.Close() logger.Write(msg) return nil } // Levels returns configured log levels. func (hook *LfsHook) Levels() []logrus.Level { return logrus.AllLevels } var log *logrus.Entry var once sync.Once //NewLogger 初始化 logger func NewLogger() *logrus.Entry { once.Do(func() { log = logrus.New().WithFields( logrus.Fields{ "appname": "pbx-api-v4", }, ) }) return log }