2025-06-30 19:21:56 +08:00
|
|
|
|
package logger
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"os"
|
2025-07-28 13:47:58 +08:00
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"time"
|
2025-06-30 19:21:56 +08:00
|
|
|
|
|
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
"go.uber.org/zap/zapcore"
|
2025-07-28 13:47:58 +08:00
|
|
|
|
"gopkg.in/natefinch/lumberjack.v2"
|
2025-06-30 19:21:56 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Logger 日志接口
|
|
|
|
|
|
type Logger interface {
|
|
|
|
|
|
Debug(msg string, fields ...zapcore.Field)
|
|
|
|
|
|
Info(msg string, fields ...zapcore.Field)
|
|
|
|
|
|
Warn(msg string, fields ...zapcore.Field)
|
|
|
|
|
|
Error(msg string, fields ...zapcore.Field)
|
|
|
|
|
|
Fatal(msg string, fields ...zapcore.Field)
|
|
|
|
|
|
Panic(msg string, fields ...zapcore.Field)
|
|
|
|
|
|
|
|
|
|
|
|
With(fields ...zapcore.Field) Logger
|
|
|
|
|
|
WithContext(ctx context.Context) Logger
|
|
|
|
|
|
Sync() error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ZapLogger Zap日志实现
|
|
|
|
|
|
type ZapLogger struct {
|
|
|
|
|
|
logger *zap.Logger
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Config 日志配置
|
|
|
|
|
|
type Config struct {
|
|
|
|
|
|
Level string
|
|
|
|
|
|
Format string
|
|
|
|
|
|
Output string
|
2025-07-28 13:47:58 +08:00
|
|
|
|
LogDir string // 日志目录
|
|
|
|
|
|
MaxSize int // 单个文件最大大小(MB)
|
|
|
|
|
|
MaxBackups int // 最大备份文件数
|
|
|
|
|
|
MaxAge int // 最大保留天数
|
|
|
|
|
|
Compress bool // 是否压缩
|
|
|
|
|
|
UseDaily bool // 是否按日分包
|
2025-06-30 19:21:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewLogger 创建新的日志实例
|
|
|
|
|
|
func NewLogger(config Config) (Logger, error) {
|
|
|
|
|
|
// 设置日志级别
|
|
|
|
|
|
level, err := zapcore.ParseLevel(config.Level)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("无效的日志级别: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 配置编码器
|
|
|
|
|
|
var encoder zapcore.Encoder
|
|
|
|
|
|
encoderConfig := getEncoderConfig()
|
|
|
|
|
|
|
|
|
|
|
|
switch config.Format {
|
|
|
|
|
|
case "json":
|
|
|
|
|
|
encoder = zapcore.NewJSONEncoder(encoderConfig)
|
|
|
|
|
|
case "console":
|
|
|
|
|
|
encoder = zapcore.NewConsoleEncoder(encoderConfig)
|
|
|
|
|
|
default:
|
|
|
|
|
|
encoder = zapcore.NewJSONEncoder(encoderConfig)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 配置输出
|
|
|
|
|
|
var writeSyncer zapcore.WriteSyncer
|
|
|
|
|
|
switch config.Output {
|
|
|
|
|
|
case "stdout":
|
|
|
|
|
|
writeSyncer = zapcore.AddSync(os.Stdout)
|
|
|
|
|
|
case "stderr":
|
|
|
|
|
|
writeSyncer = zapcore.AddSync(os.Stderr)
|
|
|
|
|
|
case "file":
|
2025-07-28 13:47:58 +08:00
|
|
|
|
writeSyncer, err = createFileWriteSyncer(config)
|
2025-06-30 19:21:56 +08:00
|
|
|
|
if err != nil {
|
2025-07-28 13:47:58 +08:00
|
|
|
|
return nil, fmt.Errorf("创建文件输出失败: %w", err)
|
2025-06-30 19:21:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
|
|
|
writeSyncer = zapcore.AddSync(os.Stdout)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建核心
|
|
|
|
|
|
core := zapcore.NewCore(encoder, writeSyncer, level)
|
|
|
|
|
|
|
|
|
|
|
|
// 创建logger
|
|
|
|
|
|
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
|
|
|
|
|
|
|
|
|
|
|
|
return &ZapLogger{
|
|
|
|
|
|
logger: logger,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 13:47:58 +08:00
|
|
|
|
// createFileWriteSyncer 创建文件输出同步器
|
|
|
|
|
|
func createFileWriteSyncer(config Config) (zapcore.WriteSyncer, error) {
|
|
|
|
|
|
// 设置默认日志目录
|
|
|
|
|
|
if config.LogDir == "" {
|
|
|
|
|
|
config.LogDir = "logs"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保日志目录存在
|
|
|
|
|
|
if err := os.MkdirAll(config.LogDir, 0755); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("创建日志目录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置默认值
|
|
|
|
|
|
if config.MaxSize == 0 {
|
|
|
|
|
|
config.MaxSize = 100 // 默认100MB
|
|
|
|
|
|
}
|
|
|
|
|
|
if config.MaxBackups == 0 {
|
|
|
|
|
|
config.MaxBackups = 3 // 默认3个备份
|
|
|
|
|
|
}
|
|
|
|
|
|
if config.MaxAge == 0 {
|
|
|
|
|
|
config.MaxAge = 7 // 默认7天
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建日志文件路径
|
|
|
|
|
|
var logFilePath string
|
|
|
|
|
|
if config.UseDaily {
|
|
|
|
|
|
// 按日分包:logs/2024-01-01/app.log
|
|
|
|
|
|
today := time.Now().Format("2006-01-02")
|
|
|
|
|
|
dailyDir := filepath.Join(config.LogDir, today)
|
|
|
|
|
|
if err := os.MkdirAll(dailyDir, 0755); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("创建日期目录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
logFilePath = filepath.Join(dailyDir, "app.log")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 传统方式:logs/app.log
|
|
|
|
|
|
logFilePath = filepath.Join(config.LogDir, "app.log")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建lumberjack日志轮转器
|
|
|
|
|
|
lumberJackLogger := &lumberjack.Logger{
|
|
|
|
|
|
Filename: logFilePath,
|
|
|
|
|
|
MaxSize: config.MaxSize, // 单个文件最大大小(MB)
|
|
|
|
|
|
MaxBackups: config.MaxBackups, // 最大备份文件数
|
|
|
|
|
|
MaxAge: config.MaxAge, // 最大保留天数
|
|
|
|
|
|
Compress: config.Compress, // 是否压缩
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return zapcore.AddSync(lumberJackLogger), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-30 19:21:56 +08:00
|
|
|
|
// getEncoderConfig 获取编码器配置
|
|
|
|
|
|
func getEncoderConfig() zapcore.EncoderConfig {
|
|
|
|
|
|
return zapcore.EncoderConfig{
|
|
|
|
|
|
TimeKey: "timestamp",
|
|
|
|
|
|
LevelKey: "level",
|
|
|
|
|
|
NameKey: "logger",
|
|
|
|
|
|
CallerKey: "caller",
|
|
|
|
|
|
FunctionKey: zapcore.OmitKey,
|
|
|
|
|
|
MessageKey: "message",
|
|
|
|
|
|
StacktraceKey: "stacktrace",
|
|
|
|
|
|
LineEnding: zapcore.DefaultLineEnding,
|
|
|
|
|
|
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
|
|
|
|
|
EncodeTime: zapcore.ISO8601TimeEncoder,
|
|
|
|
|
|
EncodeDuration: zapcore.StringDurationEncoder,
|
|
|
|
|
|
EncodeCaller: zapcore.ShortCallerEncoder,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Debug 调试日志
|
|
|
|
|
|
func (l *ZapLogger) Debug(msg string, fields ...zapcore.Field) {
|
|
|
|
|
|
l.logger.Debug(msg, fields...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Info 信息日志
|
|
|
|
|
|
func (l *ZapLogger) Info(msg string, fields ...zapcore.Field) {
|
|
|
|
|
|
l.logger.Info(msg, fields...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Warn 警告日志
|
|
|
|
|
|
func (l *ZapLogger) Warn(msg string, fields ...zapcore.Field) {
|
|
|
|
|
|
l.logger.Warn(msg, fields...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Error 错误日志
|
|
|
|
|
|
func (l *ZapLogger) Error(msg string, fields ...zapcore.Field) {
|
|
|
|
|
|
l.logger.Error(msg, fields...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fatal 致命错误日志
|
|
|
|
|
|
func (l *ZapLogger) Fatal(msg string, fields ...zapcore.Field) {
|
|
|
|
|
|
l.logger.Fatal(msg, fields...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Panic 恐慌日志
|
|
|
|
|
|
func (l *ZapLogger) Panic(msg string, fields ...zapcore.Field) {
|
|
|
|
|
|
l.logger.Panic(msg, fields...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// With 添加字段
|
|
|
|
|
|
func (l *ZapLogger) With(fields ...zapcore.Field) Logger {
|
|
|
|
|
|
return &ZapLogger{
|
|
|
|
|
|
logger: l.logger.With(fields...),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// WithContext 从上下文添加字段
|
|
|
|
|
|
func (l *ZapLogger) WithContext(ctx context.Context) Logger {
|
|
|
|
|
|
// 从上下文中提取常用字段
|
|
|
|
|
|
fields := []zapcore.Field{}
|
|
|
|
|
|
|
|
|
|
|
|
if traceID := getTraceIDFromContext(ctx); traceID != "" {
|
|
|
|
|
|
fields = append(fields, zap.String("trace_id", traceID))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if userID := getUserIDFromContext(ctx); userID != "" {
|
|
|
|
|
|
fields = append(fields, zap.String("user_id", userID))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if requestID := getRequestIDFromContext(ctx); requestID != "" {
|
|
|
|
|
|
fields = append(fields, zap.String("request_id", requestID))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return l.With(fields...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Sync 同步日志
|
|
|
|
|
|
func (l *ZapLogger) Sync() error {
|
|
|
|
|
|
return l.logger.Sync()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-13 16:36:20 +08:00
|
|
|
|
// GetZapLogger 获取内部的zap.Logger实例
|
|
|
|
|
|
func (l *ZapLogger) GetZapLogger() *zap.Logger {
|
|
|
|
|
|
return l.logger
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-30 19:21:56 +08:00
|
|
|
|
// getTraceIDFromContext 从上下文获取追踪ID
|
|
|
|
|
|
func getTraceIDFromContext(ctx context.Context) string {
|
|
|
|
|
|
if traceID := ctx.Value("trace_id"); traceID != nil {
|
|
|
|
|
|
if id, ok := traceID.(string); ok {
|
|
|
|
|
|
return id
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// getUserIDFromContext 从上下文获取用户ID
|
|
|
|
|
|
func getUserIDFromContext(ctx context.Context) string {
|
|
|
|
|
|
if userID := ctx.Value("user_id"); userID != nil {
|
|
|
|
|
|
if id, ok := userID.(string); ok {
|
|
|
|
|
|
return id
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// getRequestIDFromContext 从上下文获取请求ID
|
|
|
|
|
|
func getRequestIDFromContext(ctx context.Context) string {
|
|
|
|
|
|
if requestID := ctx.Value("request_id"); requestID != nil {
|
|
|
|
|
|
if id, ok := requestID.(string); ok {
|
|
|
|
|
|
return id
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Field 创建日志字段的便捷函数
|
|
|
|
|
|
func String(key, val string) zapcore.Field {
|
|
|
|
|
|
return zap.String(key, val)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Int(key string, val int) zapcore.Field {
|
|
|
|
|
|
return zap.Int(key, val)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Int64(key string, val int64) zapcore.Field {
|
|
|
|
|
|
return zap.Int64(key, val)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Float64(key string, val float64) zapcore.Field {
|
|
|
|
|
|
return zap.Float64(key, val)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Bool(key string, val bool) zapcore.Field {
|
|
|
|
|
|
return zap.Bool(key, val)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Error(err error) zapcore.Field {
|
|
|
|
|
|
return zap.Error(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Any(key string, val interface{}) zapcore.Field {
|
|
|
|
|
|
return zap.Any(key, val)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func Duration(key string, val interface{}) zapcore.Field {
|
|
|
|
|
|
return zap.Any(key, val)
|
|
|
|
|
|
}
|