Files
tyapi-server/internal/shared/logger/logger.go
2025-08-25 15:44:06 +08:00

323 lines
8.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package logger
import (
"context"
"path/filepath"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
// Logger 日志器接口 - 基于 Zap 官方推荐
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
Named(name string) Logger
// 同步和清理
Sync() error
Core() zapcore.Core
// 获取原生 Zap Logger用于高级功能
GetZapLogger() *zap.Logger
}
// Config 日志配置 - 基于 Zap 官方配置结构
type Config struct {
// 基础配置
Level string `mapstructure:"level"` // 日志级别
Format string `mapstructure:"format"` // 输出格式 (json/console)
Output string `mapstructure:"output"` // 输出方式 (stdout/stderr/file)
LogDir string `mapstructure:"log_dir"` // 日志目录
UseDaily bool `mapstructure:"use_daily"` // 是否按日分包
UseColor bool `mapstructure:"use_color"` // 是否使用彩色输出仅console格式
// 文件配置
MaxSize int `mapstructure:"max_size"` // 单个文件最大大小(MB)
MaxBackups int `mapstructure:"max_backups"` // 最大备份文件数
MaxAge int `mapstructure:"max_age"` // 最大保留天数
Compress bool `mapstructure:"compress"` // 是否压缩
// 高级功能
EnableLevelSeparation bool `mapstructure:"enable_level_separation"` // 是否启用按级别分文件
LevelConfigs map[string]interface{} `mapstructure:"level_configs"` // 各级别配置(使用 interface{} 避免循环依赖)
EnableRequestLogging bool `mapstructure:"enable_request_logging"` // 是否启用请求日志
EnablePerformanceLog bool `mapstructure:"enable_performance_log"` // 是否启用性能日志
// 开发环境配置
Development bool `mapstructure:"development"` // 是否为开发环境
Sampling bool `mapstructure:"sampling"` // 是否启用采样
}
// ZapLogger Zap日志实现 - 基于官方推荐
type ZapLogger struct {
logger *zap.Logger
}
// NewLogger 创建新的日志实例 - 使用 Zap 官方推荐的方式
func NewLogger(config Config) (Logger, error) {
var logger *zap.Logger
var err error
// 根据环境创建合适的日志器
if config.Development {
logger, err = zap.NewDevelopment(
zap.AddCaller(),
zap.AddCallerSkip(1),
zap.AddStacktrace(zapcore.ErrorLevel),
)
} else {
logger, err = zap.NewProduction(
zap.AddCaller(),
zap.AddCallerSkip(1),
zap.AddStacktrace(zapcore.ErrorLevel),
)
}
if err != nil {
return nil, err
}
// 如果配置为文件输出,需要手动设置 Core
if config.Output == "file" {
writeSyncer, err := createFileWriteSyncer(config)
if err != nil {
return nil, err
}
// 创建新的 Core 并替换
encoder := getEncoder(config.Format, config)
level := getLogLevel(config.Level)
core := zapcore.NewCore(encoder, writeSyncer, level)
logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1), zap.AddStacktrace(zapcore.ErrorLevel))
}
return &ZapLogger{
logger: logger,
}, nil
}
// 实现 Logger 接口
func (z *ZapLogger) Debug(msg string, fields ...zapcore.Field) {
z.logger.Debug(msg, fields...)
}
func (z *ZapLogger) Info(msg string, fields ...zapcore.Field) {
z.logger.Info(msg, fields...)
}
func (z *ZapLogger) Warn(msg string, fields ...zapcore.Field) {
z.logger.Warn(msg, fields...)
}
func (z *ZapLogger) Error(msg string, fields ...zapcore.Field) {
z.logger.Error(msg, fields...)
}
func (z *ZapLogger) Fatal(msg string, fields ...zapcore.Field) {
z.logger.Fatal(msg, fields...)
}
func (z *ZapLogger) Panic(msg string, fields ...zapcore.Field) {
z.logger.Panic(msg, fields...)
}
func (z *ZapLogger) With(fields ...zapcore.Field) Logger {
return &ZapLogger{logger: z.logger.With(fields...)}
}
func (z *ZapLogger) WithContext(ctx context.Context) Logger {
// 从上下文提取字段
fields := extractFieldsFromContext(ctx)
return &ZapLogger{logger: z.logger.With(fields...)}
}
func (z *ZapLogger) Named(name string) Logger {
return &ZapLogger{logger: z.logger.Named(name)}
}
func (z *ZapLogger) Sync() error {
return z.logger.Sync()
}
func (z *ZapLogger) Core() zapcore.Core {
return z.logger.Core()
}
func (z *ZapLogger) GetZapLogger() *zap.Logger {
return z.logger
}
// 全局日志器 - 基于 Zap 官方推荐
var globalLogger *zap.Logger
// InitGlobalLogger 初始化全局日志器
func InitGlobalLogger(config Config) error {
var logger *zap.Logger
var err error
// 根据环境创建合适的日志器
if config.Development {
logger, err = zap.NewDevelopment(
zap.AddCaller(),
zap.AddCallerSkip(1),
zap.AddStacktrace(zapcore.ErrorLevel),
)
} else {
logger, err = zap.NewProduction(
zap.AddCaller(),
zap.AddCallerSkip(1),
zap.AddStacktrace(zapcore.ErrorLevel),
)
}
if err != nil {
return err
}
// 如果配置为文件输出,需要手动设置 Core
if config.Output == "file" {
writeSyncer, err := createFileWriteSyncer(config)
if err != nil {
return err
}
// 创建新的 Core 并替换
encoder := getEncoder(config.Format, config)
level := getLogLevel(config.Level)
core := zapcore.NewCore(encoder, writeSyncer, level)
logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1), zap.AddStacktrace(zapcore.ErrorLevel))
}
// 替换全局日志器
zap.ReplaceGlobals(logger)
globalLogger = logger
return nil
}
// GetGlobalLogger 获取全局日志器
func GetGlobalLogger() *zap.Logger {
if globalLogger == nil {
// 如果没有初始化,使用默认的生产环境配置
globalLogger = zap.Must(zap.NewProduction())
}
return globalLogger
}
// L 获取全局日志器Zap 官方推荐的方式)
func L() *zap.Logger {
return zap.L()
}
// 辅助函数
func getLogLevel(level string) zapcore.Level {
switch level {
case "debug":
return zapcore.DebugLevel
case "info":
return zapcore.InfoLevel
case "warn":
return zapcore.WarnLevel
case "error":
return zapcore.ErrorLevel
case "fatal":
return zapcore.FatalLevel
case "panic":
return zapcore.PanicLevel
default:
return zapcore.InfoLevel
}
}
func getEncoder(format string, config Config) zapcore.Encoder {
encoderConfig := getEncoderConfig(config)
if format == "console" {
return zapcore.NewConsoleEncoder(encoderConfig)
}
return zapcore.NewJSONEncoder(encoderConfig)
}
func getEncoderConfig(config Config) zapcore.EncoderConfig {
encoderConfig := zap.NewProductionEncoderConfig()
if config.Development {
encoderConfig = zap.NewDevelopmentEncoderConfig()
}
// 自定义时间格式
encoderConfig.TimeKey = "timestamp"
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// 自定义级别格式
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
// 自定义调用者格式
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
return encoderConfig
}
func createFileWriteSyncer(config Config) (zapcore.WriteSyncer, error) {
// 使用 lumberjack 进行日志轮转
rotator := &lumberjack.Logger{
Filename: getLogFilePath(config),
MaxSize: config.MaxSize,
MaxBackups: config.MaxBackups,
MaxAge: config.MaxAge,
Compress: config.Compress,
}
return zapcore.AddSync(rotator), nil
}
func getLogFilePath(config Config) string {
if config.UseDaily {
// 按日期分包
date := time.Now().Format("2006-01-02")
return filepath.Join(config.LogDir, date, "app.log")
}
return filepath.Join(config.LogDir, "app.log")
}
func extractFieldsFromContext(ctx context.Context) []zapcore.Field {
var fields []zapcore.Field
// 提取请求ID
if requestID := ctx.Value("request_id"); requestID != nil {
if id, ok := requestID.(string); ok {
fields = append(fields, zap.String("request_id", id))
}
}
// 提取用户ID
if userID := ctx.Value("user_id"); userID != nil {
if id, ok := userID.(string); ok {
fields = append(fields, zap.String("user_id", id))
}
}
// 提取跟踪ID
if traceID := ctx.Value("trace_id"); traceID != nil {
if id, ok := traceID.(string); ok {
fields = append(fields, zap.String("trace_id", id))
}
}
return fields
}