add JRZQ09J8、FLXGDEA8、FLXGDEA9、JRZQ1D09
add external_services log
This commit is contained in:
@@ -2,8 +2,6 @@ package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
@@ -12,8 +10,9 @@ import (
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
// Logger 日志接口
|
||||
// Logger 日志器接口 - 基于 Zap 官方推荐
|
||||
type Logger interface {
|
||||
// 基础日志方法
|
||||
Debug(msg string, fields ...zapcore.Field)
|
||||
Info(msg string, fields ...zapcore.Field)
|
||||
Warn(msg string, fields ...zapcore.Field)
|
||||
@@ -21,271 +20,303 @@ type Logger interface {
|
||||
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
|
||||
}
|
||||
|
||||
// ZapLogger Zap日志实现
|
||||
// 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
|
||||
}
|
||||
|
||||
// Config 日志配置
|
||||
type Config struct {
|
||||
Level string
|
||||
Format string
|
||||
Output string
|
||||
LogDir string // 日志目录
|
||||
MaxSize int // 单个文件最大大小(MB)
|
||||
MaxBackups int // 最大备份文件数
|
||||
MaxAge int // 最大保留天数
|
||||
Compress bool // 是否压缩
|
||||
UseDaily bool // 是否按日分包
|
||||
}
|
||||
|
||||
// NewLogger 创建新的日志实例
|
||||
// NewLogger 创建新的日志实例 - 使用 Zap 官方推荐的方式
|
||||
func NewLogger(config Config) (Logger, error) {
|
||||
// 设置日志级别
|
||||
level, err := zapcore.ParseLevel(config.Level)
|
||||
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, fmt.Errorf("无效的日志级别: %w", err)
|
||||
return nil, 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":
|
||||
writeSyncer, err = createFileWriteSyncer(config)
|
||||
// 如果配置为文件输出,需要手动设置 Core
|
||||
if config.Output == "file" {
|
||||
writeSyncer, err := createFileWriteSyncer(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建文件输出失败: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
writeSyncer = zapcore.AddSync(os.Stdout)
|
||||
|
||||
// 创建新的 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))
|
||||
}
|
||||
|
||||
// 创建核心
|
||||
core := zapcore.NewCore(encoder, writeSyncer, level)
|
||||
|
||||
// 创建logger
|
||||
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
|
||||
|
||||
return &ZapLogger{
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// createFileWriteSyncer 创建文件输出同步器
|
||||
func createFileWriteSyncer(config Config) (zapcore.WriteSyncer, error) {
|
||||
// 设置默认日志目录
|
||||
if config.LogDir == "" {
|
||||
config.LogDir = "logs"
|
||||
}
|
||||
// 实现 Logger 接口
|
||||
func (z *ZapLogger) Debug(msg string, fields ...zapcore.Field) {
|
||||
z.logger.Debug(msg, fields...)
|
||||
}
|
||||
|
||||
// 确保日志目录存在
|
||||
if err := os.MkdirAll(config.LogDir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("创建日志目录失败: %w", err)
|
||||
}
|
||||
func (z *ZapLogger) Info(msg string, fields ...zapcore.Field) {
|
||||
z.logger.Info(msg, fields...)
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if config.MaxSize == 0 {
|
||||
config.MaxSize = 100 // 默认100MB
|
||||
}
|
||||
if config.MaxBackups == 0 {
|
||||
config.MaxBackups = 3 // 默认3个备份
|
||||
}
|
||||
if config.MaxAge == 0 {
|
||||
config.MaxAge = 7 // 默认7天
|
||||
}
|
||||
func (z *ZapLogger) Warn(msg string, fields ...zapcore.Field) {
|
||||
z.logger.Warn(msg, fields...)
|
||||
}
|
||||
|
||||
// 构建日志文件路径
|
||||
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")
|
||||
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 {
|
||||
// 传统方式:logs/app.log
|
||||
logFilePath = filepath.Join(config.LogDir, "app.log")
|
||||
logger, err = zap.NewProduction(
|
||||
zap.AddCaller(),
|
||||
zap.AddCallerSkip(1),
|
||||
zap.AddStacktrace(zapcore.ErrorLevel),
|
||||
)
|
||||
}
|
||||
|
||||
// 创建lumberjack日志轮转器
|
||||
lumberJackLogger := &lumberjack.Logger{
|
||||
Filename: logFilePath,
|
||||
MaxSize: config.MaxSize, // 单个文件最大大小(MB)
|
||||
MaxBackups: config.MaxBackups, // 最大备份文件数
|
||||
MaxAge: config.MaxAge, // 最大保留天数
|
||||
Compress: config.Compress, // 是否压缩
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// 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
|
||||
// 如果配置为文件输出,需要手动设置 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))
|
||||
}
|
||||
return ""
|
||||
|
||||
// 替换全局日志器
|
||||
zap.ReplaceGlobals(logger)
|
||||
globalLogger = logger
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getUserIDFromContext 从上下文获取用户ID
|
||||
func getUserIDFromContext(ctx context.Context) string {
|
||||
if userID := ctx.Value("user_id"); userID != nil {
|
||||
if id, ok := userID.(string); ok {
|
||||
return id
|
||||
}
|
||||
// GetGlobalLogger 获取全局日志器
|
||||
func GetGlobalLogger() *zap.Logger {
|
||||
if globalLogger == nil {
|
||||
// 如果没有初始化,使用默认的生产环境配置
|
||||
globalLogger = zap.Must(zap.NewProduction())
|
||||
}
|
||||
return ""
|
||||
return globalLogger
|
||||
}
|
||||
|
||||
// getRequestIDFromContext 从上下文获取请求ID
|
||||
func getRequestIDFromContext(ctx context.Context) string {
|
||||
// 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 {
|
||||
return id
|
||||
fields = append(fields, zap.String("request_id", 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)
|
||||
|
||||
// 提取用户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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user