feat: 实现分层架构,包含 domain、service、repository 和 pkg 层
- 新增 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(已迁移至分层架构)
This commit is contained in:
109
backend/pkg/logger/logger.go
Normal file
109
backend/pkg/logger/logger.go
Normal file
@@ -0,0 +1,109 @@
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user