package logger import ( "bytes" "encoding/json" "fmt" "gitea.baoapi.com/root/stu_uuos/config" rotatelogs "github.com/lestrrat-go/file-rotatelogs" "github.com/rifflock/lfshook" "github.com/sirupsen/logrus" "os" "path" "path/filepath" "regexp" "runtime" "strings" "time" ) const ( nocolor logColor = 0 red logColor = 31 green logColor = 32 yellow logColor = 33 blue logColor = 36 gray logColor = 37 ) type TextLogFormatter struct { Conf *config.Config } type logOutTarget string type logFormatter string type logLevel string type logColor int const ( Std logOutTarget = "std" File logOutTarget = "file" ) const ( Text logFormatter = "text" Json logFormatter = "json" ) const ( Unknown logLevel = "UNKNOWN" Trc logLevel = "TRC" Dbg logLevel = "DBG" Inf logLevel = "INF" Wrn logLevel = "WRN" Err logLevel = "ERR" Fat logLevel = "FAT" Pan logLevel = "PAN" ) func newLogrusLogger() *logrus.Logger { conf := config.GetConfig() logger := logrus.New() lvl, err := logrus.ParseLevel(conf.Log.MinLevel) if err != nil { panic(err) } logger.Level = lvl logger.ReportCaller = conf.Log.ReportCaller if conf.Log.Formatter == string(Text) { logger.Formatter = &TextLogFormatter{conf} } else { logger.Formatter = &logrus.JSONFormatter{ TimestampFormat: conf.Log.TimestampFormat, PrettyPrint: conf.Log.JsonPrettyPrint, CallerPrettyfier: func(f *runtime.Frame) (string, string) { filename, line, funcName := getRuntimeLocation() if len(filename) == 0 { _, filename = path.Split(f.File) } else { filename = fmt.Sprintf("%s:%d", filename, line) } if len(funcName) == 0 { s := strings.Split(f.Function, ".") funcName = s[len(s)-1] } return funcName, filename }, } } if conf.Log.Out == string(File) { hook := newLfsHook(conf, logger.Formatter) logger.AddHook(hook) } else { logger.SetOutput(os.Stdout) } return logger } func (f *TextLogFormatter) Format(entry *logrus.Entry) ([]byte, error) { var b *bytes.Buffer if entry.Buffer != nil { b = entry.Buffer } else { b = &bytes.Buffer{} } lt, lc := parseLevel(entry) fn, line, funcName := getEntryRuntimeLocation(entry) caller := "" if entry.HasCaller() && f.Conf.Log.ReportCaller { caller = fmt.Sprintf("%s:%d %s", fn, line, funcName) } if f.Conf.Log.Out == string(Std) && f.Conf.Log.OutColor { //只有控制台输出才需要输出颜色,文件输出不需要颜色。 _, err := fmt.Fprintf(b, "%s \x1b[%dm[%s] \x1b[0m%s - %s ", entry.Time.Format(f.Conf.Log.TimestampFormat), lc, lt, caller, entry.Message) if err != nil { panic(err) } } else { _, err := fmt.Fprintf(b, "%s [%s] %s | %-44s ", entry.Time.Format(f.Conf.Log.TimestampFormat), lt, caller, entry.Message) if err != nil { panic(err) } } if len(entry.Data) > 0 { outJsonEntry(b, entry) } b.WriteByte('\n') return b.Bytes(), nil } func parseLevel(entry *logrus.Entry) (logLevel, logColor) { lt := Unknown var lc logColor switch entry.Level { case logrus.TraceLevel: lt = Trc lc = gray case logrus.DebugLevel: lt = Dbg lc = gray case logrus.InfoLevel: lt = Inf lc = blue case logrus.WarnLevel: lt = Wrn lc = yellow case logrus.ErrorLevel: lt = Err lc = red case logrus.FatalLevel: lt = Fat lc = red case logrus.PanicLevel: lt = Pan lc = red } return lt, lc } func outJsonEntry(b *bytes.Buffer, entry *logrus.Entry) { jb, err := json.Marshal(entry.Data) if err != nil { panic(err) } if _, err = fmt.Fprint(b, string(jb)); err != nil { panic(err) } } func newLfsHook(conf *config.Config, formatter logrus.Formatter) logrus.Hook { filePathName := path.Join(conf.Log.FilePath, conf.Log.FileName) var opts []rotatelogs.Option // 生成软链,指向最新日志文件 opts = append(opts, rotatelogs.WithLinkName(filePathName)) // 日志切割时间间隔 du1, err := time.ParseDuration(conf.Log.RotationTime) if err != nil { panic(err) } if du1.Minutes() < 1 { panic(fmt.Errorf("日志切割时间 %v 太短", du1)) } opts = append(opts, rotatelogs.WithRotationTime(du1)) du2, err := time.ParseDuration(conf.Log.MaxAge) if err != nil { panic(err) } if du2.Minutes() > 0 && du2.Minutes() < 1 { panic(fmt.Errorf("日志保留时间 %v 太短", du1)) } // WithMaxAge 和 WithRotationCount 二者只能设置一个 if du2.Minutes() > 0 { // 文件最长保存时间 opts = append(opts, rotatelogs.WithMaxAge(du2)) } else if conf.Log.MaxRemainCount > 0 { //最多保存文件个数 opts = append(opts, rotatelogs.WithRotationCount(conf.Log.MaxRemainCount)) } else { // 默认设置为文件最长保存时间一个月 opts = append(opts, rotatelogs.WithMaxAge(time.Hour*24*30)) } writer, err := rotatelogs.New(filePathName+".%Y%m%d%H%M", opts...) if err != nil { panic(err) } lfsHook := lfshook.NewHook(lfshook.WriterMap{ logrus.TraceLevel: writer, logrus.DebugLevel: writer, logrus.InfoLevel: writer, logrus.WarnLevel: writer, logrus.ErrorLevel: writer, logrus.FatalLevel: writer, logrus.PanicLevel: writer, }, formatter) return lfsHook } func getEntryRuntimeLocation(entry *logrus.Entry) (filename string, line int, funcName string) { filename, line, funcName = getRuntimeLocation() if len(filename) == 0 && line == 0 && len(funcName) == 0 { filename = filepath.Base(entry.Caller.File) line = entry.Caller.Line funcName = filepath.Base(entry.Caller.Function) } return filename, line, funcName } var logRegexp = regexp.MustCompile(`config*|model*|entity*|db*|constant*|srv*|web*|handler*|router*|util*|micro*`) var excludeRegexp = regexp.MustCompile(`logger*|logrus*`) // 获取正在运行的文件名称、文件所在行、函数名 func getRuntimeLocation() (filename string, line int, funcName string) { var pc uintptr var ok bool var i int for { pc, filename, line, ok = runtime.Caller(i) if ok && (logRegexp.MatchString(filename) && !excludeRegexp.MatchString(filename)) { funcName = runtime.FuncForPC(pc).Name() fns := strings.Split(funcName, "/") if len(fns) > 1 { dir := strings.Join(fns[1:len(fns)-1], "/") directPkg := strings.Split(fns[len(fns)-1], ".")[0] filename = fmt.Sprintf("%s/%s/%s", dir, directPkg, filepath.Base(filename)) } funcName = filepath.Base(funcName) break } i++ } return filename, line, funcName }