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

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
}