- 新增模块化日志器(pkg/logger/module.go) - 新增 GORM 日志适配器 - 统一日志入口,移除所有 zap.L() 全局 logger 调用 - 字段标准化 - 启动阶段使用结构化日志 - 更新所有相关测试
126 lines
3.6 KiB
Go
126 lines
3.6 KiB
Go
package logger
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"time"
|
||
|
||
"go.uber.org/zap"
|
||
"go.uber.org/zap/zapcore"
|
||
)
|
||
|
||
// stdoutWriter 包装 os.Stdout,忽略 Sync() 错误。
|
||
// 在非 TTY 环境(如 go test)中,os.Stdout 被重定向为 pipe,
|
||
// 底层 fsync 会返回 "bad file descriptor"。zap 社区标准做法。
|
||
type stdoutWriter struct{}
|
||
|
||
func (stdoutWriter) Write(p []byte) (int, error) { return os.Stdout.Write(p) }
|
||
func (stdoutWriter) Sync() error { return nil }
|
||
|
||
// Config 日志配置
|
||
type Config struct {
|
||
Level string // 日志级别: debug, info, warn, error
|
||
Path string // 日志文件目录,为空则仅输出到 stdout
|
||
MaxSize int // 单个日志文件最大尺寸 (MB)
|
||
MaxBackups int // 保留的旧日志文件最大数量
|
||
MaxAge int // 保留旧日志文件的最大天数
|
||
Compress bool // 是否压缩旧日志文件
|
||
}
|
||
|
||
// New 根据配置创建 zap.Logger
|
||
// 如果 Path 为空,仅输出到 stdout;
|
||
// 如果 Path 已设置,同时输出到 stdout 和文件(文件使用 JSON 格式,stdout 使用 console 格式)
|
||
func New(cfg Config) (*zap.Logger, error) {
|
||
level, err := parseLevel(cfg.Level)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// stdout encoder — console 格式
|
||
stdoutEncoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
|
||
TimeKey: "ts",
|
||
LevelKey: "level",
|
||
NameKey: "logger",
|
||
CallerKey: "caller",
|
||
FunctionKey: zapcore.OmitKey,
|
||
MessageKey: "msg",
|
||
StacktraceKey: "stacktrace",
|
||
LineEnding: zapcore.DefaultLineEnding,
|
||
EncodeLevel: zapcore.CapitalColorLevelEncoder,
|
||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||
EncodeDuration: zapcore.StringDurationEncoder,
|
||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||
EncodeName: encodeLoggerName,
|
||
})
|
||
|
||
stdoutCore := zapcore.NewCore(
|
||
stdoutEncoder,
|
||
zapcore.AddSync(stdoutWriter{}),
|
||
level,
|
||
)
|
||
|
||
// 仅 stdout 模式
|
||
if cfg.Path == "" {
|
||
return zap.New(stdoutCore, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel)), nil
|
||
}
|
||
|
||
// 文件 encoder — JSON 格式
|
||
fileEncoder := zapcore.NewJSONEncoder(zapcore.EncoderConfig{
|
||
TimeKey: "ts",
|
||
LevelKey: "level",
|
||
NameKey: "logger",
|
||
CallerKey: "caller",
|
||
FunctionKey: zapcore.OmitKey,
|
||
MessageKey: "msg",
|
||
StacktraceKey: "stacktrace",
|
||
LineEnding: zapcore.DefaultLineEnding,
|
||
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||
EncodeDuration: zapcore.StringDurationEncoder,
|
||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||
})
|
||
|
||
rotateWriter := newRotateWriter(cfg)
|
||
fileCore := zapcore.NewCore(
|
||
fileEncoder,
|
||
zapcore.AddSync(rotateWriter),
|
||
level,
|
||
)
|
||
|
||
core := zapcore.NewTee(stdoutCore, fileCore)
|
||
return zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel)), nil
|
||
}
|
||
|
||
// parseLevel 将字符串解析为 zapcore.Level
|
||
func parseLevel(s string) (zapcore.Level, error) {
|
||
switch s {
|
||
case "debug":
|
||
return zapcore.DebugLevel, nil
|
||
case "info":
|
||
return zapcore.InfoLevel, nil
|
||
case "warn":
|
||
return zapcore.WarnLevel, nil
|
||
case "error":
|
||
return zapcore.ErrorLevel, nil
|
||
default:
|
||
return zapcore.InfoLevel, nil
|
||
}
|
||
}
|
||
|
||
// logFileName 生成当日日志文件名: nex-YYYY-MM-DD.log
|
||
func logFileName() string {
|
||
return "nex-" + time.Now().Format("2006-01-02") + ".log"
|
||
}
|
||
|
||
// logFilePath 拼接完整日志文件路径
|
||
func logFilePath(dir string) string {
|
||
return filepath.Join(dir, logFileName())
|
||
}
|
||
|
||
// encodeLoggerName 自定义 logger 名称编码器,输出 [name] 格式
|
||
func encodeLoggerName(name string, enc zapcore.PrimitiveArrayEncoder) {
|
||
if name != "" {
|
||
enc.AppendString("[" + name + "]")
|
||
}
|
||
}
|