|
|
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 }
|