Files
tyapi-server/internal/shared/logger/logger.go

292 lines
7.1 KiB
Go
Raw Normal View History

package logger
import (
"context"
"fmt"
"os"
2025-07-28 13:47:58 +08:00
"path/filepath"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
2025-07-28 13:47:58 +08:00
"gopkg.in/natefinch/lumberjack.v2"
)
// 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 // 是否按日分包
}
// 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)
if err != nil {
2025-07-28 13:47:58 +08:00
return nil, fmt.Errorf("创建文件输出失败: %w", err)
}
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
}
// 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
}
// 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)
}