Files
workspace/code/base-project/base-go/pkg/logx/log.go
T
2025-08-25 17:22:54 +08:00

238 lines
6.0 KiB
Go

package logx
import (
"base-framework/pkg/routinex"
"base-framework/pkg/utils/strutil"
"base-framework/pkg/utils/uid"
"fmt"
"os"
"path/filepath"
"sync"
"time"
"base-framework/pkg/config"
"base-framework/pkg/types"
"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 = uid.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() {
if config.Log == nil {
config.Log = defaultLogConfig()
}
createDirIfNotExist(config.Log.Dir)
switchDate(time.Now().Format("2006-01-02"))
})
}
func defaultLogConfig() *types.Log {
return &types.Log{
Dir: "logs",
Level: "debug",
Format: "json",
Daily: true,
MaxDays: 30,
Caller: true,
LevelDirs: true,
Console: true,
}
}
// ----------------- 初始化核心 -----------------
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.Log.Dir, config.Log.Dir, config.Log.Dir
if config.Log.LevelDirs {
infoPath = filepath.Join(config.Log.Dir, "info")
warnPath = filepath.Join(config.Log.Dir, "warn")
errorPath = filepath.Join(config.Log.Dir, "error")
createDirIfNotExist(infoPath)
createDirIfNotExist(warnPath)
createDirIfNotExist(errorPath)
} else {
createDirIfNotExist(config.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.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.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.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.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()))
},
}
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
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
}