241 lines
6.3 KiB
Go
241 lines
6.3 KiB
Go
package logx
|
|
|
|
import (
|
|
"base-go-v2/internal/config"
|
|
"base-go-v2/internal/routinex"
|
|
"base-go-v2/internal/utils/idgen"
|
|
"base-go-v2/internal/utils/strutil"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
)
|
|
|
|
var (
|
|
Logger *zap.Logger
|
|
TrackID = "trackId"
|
|
once sync.Once
|
|
currentDate string
|
|
mu sync.Mutex
|
|
|
|
infoFile *os.File
|
|
warnFile *os.File
|
|
errorFile *os.File
|
|
|
|
infoCore zapcore.Core
|
|
warnCore zapcore.Core
|
|
errorCore zapcore.Core
|
|
)
|
|
|
|
const logTmFmtWithMS = "2006-01-02 15:04:05.000"
|
|
|
|
var levelColor = map[zapcore.Level]string{
|
|
zapcore.DebugLevel: "\033[36mDEBUG\033[0m",
|
|
zapcore.InfoLevel: "\033[32mINFO\033[0m",
|
|
zapcore.WarnLevel: "\033[33mWARN\033[0m",
|
|
zapcore.ErrorLevel: "\033[31mERROR\033[0m",
|
|
zapcore.DPanicLevel: "\033[35mDPANIC\033[0m",
|
|
zapcore.PanicLevel: "\033[35mPANIC\033[0m",
|
|
zapcore.FatalLevel: "\033[35mFATAL\033[0m",
|
|
}
|
|
|
|
// ----------------- trackCore -----------------
|
|
// 在写入日志时自动从 routinex 获取 log_id 并加入字段
|
|
type trackCore struct {
|
|
zapcore.Core
|
|
}
|
|
|
|
func (c *trackCore) With(fields []zapcore.Field) zapcore.Core {
|
|
return &trackCore{Core: c.Core.With(fields)}
|
|
}
|
|
|
|
func (c *trackCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
|
if c.Enabled(entry.Level) {
|
|
return ce.AddCore(entry, c)
|
|
}
|
|
return ce
|
|
}
|
|
|
|
func (c *trackCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
|
val := routinex.Get(TrackID)
|
|
if val == nil {
|
|
val = idgen.NextId()
|
|
routinex.Set(TrackID, val) // 保证同一请求复用同一 trackID
|
|
}
|
|
|
|
fields = append(fields, zap.String(TrackID, strutil.ToString(val)))
|
|
return c.Core.Write(entry, fields)
|
|
}
|
|
|
|
// InitLogger ----------------- 初始化日志 -----------------
|
|
func InitLogger() {
|
|
once.Do(func() {
|
|
createDirIfNotExist(config.App.Log.Dir)
|
|
switchDate(time.Now().Format("2006-01-02"))
|
|
|
|
fmt.Printf("✅ 日志初始化成功,日志目录:%s\n", config.App.Log.Dir)
|
|
})
|
|
}
|
|
|
|
// ----------------- 初始化核心 -----------------
|
|
func switchDate(today string) {
|
|
currentDate = today
|
|
|
|
if infoFile != nil {
|
|
_ = infoFile.Close()
|
|
}
|
|
if warnFile != nil {
|
|
_ = warnFile.Close()
|
|
}
|
|
if errorFile != nil {
|
|
_ = errorFile.Close()
|
|
}
|
|
|
|
infoPath, warnPath, errorPath := config.App.Log.Dir, config.App.Log.Dir, config.App.Log.Dir
|
|
if config.App.Log.LevelDirs {
|
|
infoPath = filepath.Join(config.App.Log.Dir, "info")
|
|
warnPath = filepath.Join(config.App.Log.Dir, "warn")
|
|
errorPath = filepath.Join(config.App.Log.Dir, "error")
|
|
createDirIfNotExist(infoPath)
|
|
createDirIfNotExist(warnPath)
|
|
createDirIfNotExist(errorPath)
|
|
} else {
|
|
createDirIfNotExist(config.App.Log.Dir)
|
|
}
|
|
|
|
infoFile = getOrCreateFile(filepath.Join(infoPath, fmt.Sprintf("info-%s.log", today)))
|
|
warnFile = getOrCreateFile(filepath.Join(warnPath, fmt.Sprintf("warn-%s.log", today)))
|
|
errorFile = getOrCreateFile(filepath.Join(errorPath, fmt.Sprintf("error-%s.log", today)))
|
|
|
|
var fileEncoder zapcore.Encoder
|
|
if config.App.Log.Format == "console" {
|
|
fileEncoder = newConsoleEncoder(false)
|
|
} else {
|
|
fileEncoder = newJSONEncoder()
|
|
}
|
|
|
|
infoCore = &trackCore{zapcore.NewCore(fileEncoder, zapcore.AddSync(infoFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
|
return lvl <= zapcore.InfoLevel
|
|
}))}
|
|
|
|
warnCore = &trackCore{zapcore.NewCore(fileEncoder, zapcore.AddSync(warnFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
|
return lvl == zapcore.WarnLevel
|
|
}))}
|
|
|
|
errorCore = &trackCore{zapcore.NewCore(fileEncoder, zapcore.AddSync(errorFile), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
|
return lvl >= zapcore.ErrorLevel
|
|
}))}
|
|
|
|
cores := []zapcore.Core{infoCore, warnCore, errorCore}
|
|
|
|
if config.App.Log.Console {
|
|
consoleCore := &trackCore{zapcore.NewCore(newConsoleEncoder(true), zapcore.AddSync(os.Stdout), zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
|
return lvl >= zapcore.DebugLevel
|
|
}))}
|
|
cores = append(cores, consoleCore)
|
|
}
|
|
|
|
Logger = zap.New(zapcore.NewTee(cores...))
|
|
|
|
if config.App.Log.Caller {
|
|
Logger = Logger.WithOptions(zap.AddCaller())
|
|
} else {
|
|
Logger = Logger.WithOptions(zap.AddCallerSkip(1))
|
|
}
|
|
|
|
Logger = Logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error {
|
|
if config.App.Log.Daily {
|
|
checkDateSwitch()
|
|
}
|
|
return nil
|
|
}))
|
|
}
|
|
|
|
func newConsoleEncoder(enableColor bool) zapcore.Encoder {
|
|
cfg := zapcore.EncoderConfig{
|
|
TimeKey: "ts",
|
|
LevelKey: "level",
|
|
CallerKey: "caller",
|
|
MessageKey: "msg",
|
|
LineEnding: zapcore.DefaultLineEnding,
|
|
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
|
enc.AppendString(t.Format(logTmFmtWithMS))
|
|
},
|
|
EncodeLevel: func(lvl zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
|
|
if enableColor {
|
|
color, ok := levelColor[lvl]
|
|
if !ok {
|
|
color = lvl.CapitalString()
|
|
}
|
|
enc.AppendString(color)
|
|
} else {
|
|
enc.AppendString(lvl.CapitalString())
|
|
}
|
|
},
|
|
EncodeCaller: func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
|
|
pid := os.Getpid()
|
|
enc.AppendString(fmt.Sprintf("[%d] [%d] %s", pid, routinex.CurGID(), caller.TrimmedPath()))
|
|
},
|
|
EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
|
|
ms := d.Milliseconds()
|
|
if ms < 1000 {
|
|
enc.AppendString(fmt.Sprintf("%dms", ms))
|
|
} else {
|
|
enc.AppendString(fmt.Sprintf("%.2fs", d.Seconds()))
|
|
}
|
|
},
|
|
}
|
|
return zapcore.NewConsoleEncoder(cfg)
|
|
}
|
|
|
|
func newJSONEncoder() zapcore.Encoder {
|
|
cfg := zap.NewProductionEncoderConfig()
|
|
cfg.TimeKey = "ts"
|
|
cfg.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
|
|
enc.AppendString(t.Format(logTmFmtWithMS))
|
|
}
|
|
cfg.EncodeLevel = zapcore.CapitalLevelEncoder
|
|
cfg.EncodeCaller = zapcore.ShortCallerEncoder
|
|
|
|
// 智能 duration 输出
|
|
cfg.EncodeDuration = func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
|
|
ms := d.Milliseconds()
|
|
if ms < 1000 {
|
|
enc.AppendString(fmt.Sprintf("%dms", ms))
|
|
} else {
|
|
enc.AppendString(fmt.Sprintf("%.2fs", d.Seconds()))
|
|
}
|
|
}
|
|
|
|
return zapcore.NewJSONEncoder(cfg)
|
|
}
|
|
|
|
func checkDateSwitch() {
|
|
today := time.Now().Format("2006-01-02")
|
|
if today != currentDate {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if today != currentDate {
|
|
switchDate(today)
|
|
}
|
|
}
|
|
}
|
|
|
|
func createDirIfNotExist(dir string) {
|
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
|
_ = os.MkdirAll(dir, 0755)
|
|
}
|
|
}
|
|
|
|
func getOrCreateFile(file string) *os.File {
|
|
f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return f
|
|
}
|