- 新增 domain 层:model、provider、route、stats 实体 - 新增 service 层:models、providers、routing、stats 业务逻辑 - 新增 repository 层:models、providers、stats 数据访问 - 新增 pkg 工具包:errors、logger、validator - 新增中间件:CORS、logging、recovery、request ID - 新增数据库迁移:初始 schema 和索引 - 新增单元测试和集成测试 - 新增规范文档:config-management、database-migration、error-handling、layered-architecture、middleware-system、request-validation、structured-logging、test-coverage - 移除 config 子包和 model_router(已迁移至分层架构)
110 lines
3.0 KiB
Go
110 lines
3.0 KiB
Go
package logger
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"time"
|
||
|
||
"go.uber.org/zap"
|
||
"go.uber.org/zap/zapcore"
|
||
)
|
||
|
||
// 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,
|
||
})
|
||
|
||
stdoutCore := zapcore.NewCore(
|
||
stdoutEncoder,
|
||
zapcore.AddSync(os.Stdout),
|
||
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())
|
||
}
|