You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
285 lines
6.3 KiB
285 lines
6.3 KiB
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
|
|
}
|