From 200a358933d38e854dfb7ab5ad08a680422831ce Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Tue, 18 Nov 2025 17:21:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/infra/logs/encoder.go | 45 ++------ internal/infra/logs/logger_methods.go | 153 ++++++++++++++++++++++++++ internal/infra/logs/logs.go | 31 ++---- 3 files changed, 170 insertions(+), 59 deletions(-) create mode 100644 internal/infra/logs/logger_methods.go diff --git a/internal/infra/logs/encoder.go b/internal/infra/logs/encoder.go index 317db18..2682e05 100644 --- a/internal/infra/logs/encoder.go +++ b/internal/infra/logs/encoder.go @@ -1,8 +1,6 @@ package logs import ( - "bytes" - "go.uber.org/zap/buffer" "go.uber.org/zap/zapcore" ) @@ -24,43 +22,16 @@ func NewColoredConsoleEncoder(cfg zapcore.EncoderConfig) zapcore.Encoder { } } +// EncodeEntry 重写了核心的编码方法。 // EncodeEntry 重写了核心的编码方法。 func (c *coloredConsoleEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) { - // 1. 从所有字段中分离出“调用链”字段和其他字段 - var traceField zapcore.Field - otherFields := make([]zapcore.Field, 0, len(fields)) - for _, field := range fields { - if field.Key == traceKey { - traceField = field - } else { - otherFields = append(otherFields, field) - } - } - - // 2. 使用内嵌的 ConsoleEncoder 先编码日志条目和“其他”字段 - // 这会生成不包含调用链的日志主体部分。 - line, err := c.Encoder.EncodeEntry(entry, otherFields) + // 1. 直接使用内嵌的 Encoder 编码日志条目和所有字段。 + line, err := c.Encoder.EncodeEntry(entry, fields) if err != nil { return nil, err } - // 3. 如果存在“调用链”字段,则将其手动编码并追加到末尾 - if traceField.Key != "" { - // 为了保持格式一致,我们创建一个临时的 Encoder 来只编码这一个字段 - // 注意:这里我们不能直接使用 c.Encoder,因为它会添加不必要的前缀(如时间、级别等) - tempEncoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{}) - traceLine, err := tempEncoder.EncodeEntry(zapcore.Entry{}, []zapcore.Field{traceField}) - if err != nil { - return nil, err // 理论上不应失败 - } - - // 将编码后的调用链字段(例如 "逻辑调用链=xxx->yyy")追加到日志主体后 - // TrimSpace 用于移除 tempEncoder 可能产生的额外换行符 - line.AppendString("\t") // 使用制表符分隔 - line.AppendString(string(bytes.TrimSpace(traceLine.Bytes()))) - } - - // 4. 为最终的日志行(不包括调用链)添加颜色 + // 2. 为最终的日志行添加颜色。 var color string switch entry.Level { case zapcore.DebugLevel: @@ -77,14 +48,14 @@ func (c *coloredConsoleEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcor color = reset } - // 创建一个新的 buffer,将颜色、日志内容和重置代码包裹起来 + // 3. 创建一个新的 buffer,将颜色、日志内容和重置代码包裹起来。 finalBuf := buffer.NewPool().Get() finalBuf.AppendString(color) - finalBuf.Write(line.Bytes()) // 写入已包含调用链的日志行 + finalBuf.Write(line.Bytes()) finalBuf.AppendString(reset) - // 如果原始日志行末尾没有换行符,则添加一个 - if line.Bytes()[line.Len()-1] != '\n' { + // 4. 如果原始日志行末尾没有换行符,则添加一个。 + if line.Len() > 0 && line.Bytes()[line.Len()-1] != '\n' { finalBuf.AppendByte('\n') } diff --git a/internal/infra/logs/logger_methods.go b/internal/infra/logs/logger_methods.go new file mode 100644 index 0000000..5c3b97a --- /dev/null +++ b/internal/infra/logs/logger_methods.go @@ -0,0 +1,153 @@ +package logs + +import ( + "fmt" + + "go.uber.org/zap/zapcore" +) + +// logWithTrace 是一个私有的辅助函数,用于统一处理日志记录。 +// 它会检查 logger 实例中是否包含 traceValue,如果存在,则将其作为最后一个结构化字段追加。 +// 然后,它根据传入的日志级别,调用底层 SugaredLogger 对应级别的 '...w' 方法。 +func (l *Logger) logWithTrace(level zapcore.Level, msg string, keysAndValues ...interface{}) { + // 如果存在调用链信息,则将其追加到键值对列表的末尾 + if l.traceValue != "" { + keysAndValues = append(keysAndValues, traceKey, l.traceValue) + } + + // 根据级别调用相应的 '...w' 方法 + switch level { + case zapcore.DebugLevel: + l.sl.Debugw(msg, keysAndValues...) + case zapcore.InfoLevel: + l.sl.Infow(msg, keysAndValues...) + case zapcore.WarnLevel: + l.sl.Warnw(msg, keysAndValues...) + case zapcore.ErrorLevel: + l.sl.Errorw(msg, keysAndValues...) + case zapcore.DPanicLevel: + l.sl.DPanicw(msg, keysAndValues...) + case zapcore.PanicLevel: + l.sl.Panicw(msg, keysAndValues...) + case zapcore.FatalLevel: + l.sl.Fatalw(msg, keysAndValues...) + } +} + +// --- 重写所有日志记录方法 --- + +// Debug 使用 fmt.Sprint 格式化参数并记录 Debug 级别的日志。 +func (l *Logger) Debug(args ...interface{}) { + l.logWithTrace(zapcore.DebugLevel, fmt.Sprint(args...)) +} + +// Info 使用 fmt.Sprint 格式化参数并记录 Info 级别的日志。 +func (l *Logger) Info(args ...interface{}) { + l.logWithTrace(zapcore.InfoLevel, fmt.Sprint(args...)) +} + +// Warn 使用 fmt.Sprint 格式化参数并记录 Warn 级别的日志。 +func (l *Logger) Warn(args ...interface{}) { + l.logWithTrace(zapcore.WarnLevel, fmt.Sprint(args...)) +} + +// Error 使用 fmt.Sprint 格式化参数并记录 Error 级别的日志。 +func (l *Logger) Error(args ...interface{}) { + l.logWithTrace(zapcore.ErrorLevel, fmt.Sprint(args...)) +} + +// DPanic 使用 fmt.Sprint 格式化参数并记录 DPanic 级别的日志。 +func (l *Logger) DPanic(args ...interface{}) { + l.logWithTrace(zapcore.DPanicLevel, fmt.Sprint(args...)) +} + +// Panic 使用 fmt.Sprint 格式化参数并记录 Panic 级别的日志。 +func (l *Logger) Panic(args ...interface{}) { + l.logWithTrace(zapcore.PanicLevel, fmt.Sprint(args...)) +} + +// Fatal 使用 fmt.Sprint 格式化参数并记录 Fatal 级别的日志。 +func (l *Logger) Fatal(args ...interface{}) { + l.logWithTrace(zapcore.FatalLevel, fmt.Sprint(args...)) +} + +// Debugf 使用 fmt.Sprintf 格式化模板和参数,并记录 Debug 级别的日志。 +func (l *Logger) Debugf(template string, args ...interface{}) { + l.logWithTrace(zapcore.DebugLevel, fmt.Sprintf(template, args...)) +} + +// Infof 使用 fmt.Sprintf 格式化模板和参数,并记录 Info 级别的日志。 +func (l *Logger) Infof(template string, args ...interface{}) { + l.logWithTrace(zapcore.InfoLevel, fmt.Sprintf(template, args...)) +} + +// Warnf 使用 fmt.Sprintf 格式化模板和参数,并记录 Warn 级别的日志。 +func (l *Logger) Warnf(template string, args ...interface{}) { + l.logWithTrace(zapcore.WarnLevel, fmt.Sprintf(template, args...)) +} + +// Errorf 使用 fmt.Sprintf 格式化模板和参数,并记录 Error 级别的日志。 +func (l *Logger) Errorf(template string, args ...interface{}) { + l.logWithTrace(zapcore.ErrorLevel, fmt.Sprintf(template, args...)) +} + +// DPanicf 使用 fmt.Sprintf 格式化模板和参数,并记录 DPanic 级别的日志。 +func (l *Logger) DPanicf(template string, args ...interface{}) { + l.logWithTrace(zapcore.DPanicLevel, fmt.Sprintf(template, args...)) +} + +// Panicf 使用 fmt.Sprintf 格式化模板和参数,并记录 Panic 级别的日志。 +func (l *Logger) Panicf(template string, args ...interface{}) { + l.logWithTrace(zapcore.PanicLevel, fmt.Sprintf(template, args...)) +} + +// Fatalf 使用 fmt.Sprintf 格式化模板和参数,并记录 Fatal 级别的日志。 +func (l *Logger) Fatalf(template string, args ...interface{}) { + l.logWithTrace(zapcore.FatalLevel, fmt.Sprintf(template, args...)) +} + +// Debugw 记录 Debug 级别的结构化日志。 +func (l *Logger) Debugw(msg string, keysAndValues ...interface{}) { + l.logWithTrace(zapcore.DebugLevel, msg, keysAndValues...) +} + +// Infow 记录 Info 级别的结构化日志。 +func (l *Logger) Infow(msg string, keysAndValues ...interface{}) { + l.logWithTrace(zapcore.InfoLevel, msg, keysAndValues...) +} + +// Warnw 记录 Warn 级别的结构化日志。 +func (l *Logger) Warnw(msg string, keysAndValues ...interface{}) { + l.logWithTrace(zapcore.WarnLevel, msg, keysAndValues...) +} + +// Errorw 记录 Error 级别的结构化日志。 +func (l *Logger) Errorw(msg string, keysAndValues ...interface{}) { + l.logWithTrace(zapcore.ErrorLevel, msg, keysAndValues...) +} + +// DPanicw 记录 DPanic 级别的结构化日志。 +func (l *Logger) DPanicw(msg string, keysAndValues ...interface{}) { + l.logWithTrace(zapcore.DPanicLevel, msg, keysAndValues...) +} + +// Panicw 记录 Panic 级别的结构化日志。 +func (l *Logger) Panicw(msg string, keysAndValues ...interface{}) { + l.logWithTrace(zapcore.PanicLevel, msg, keysAndValues...) +} + +// Fatalw 记录 Fatal 级别的结构化日志。 +func (l *Logger) Fatalw(msg string, keysAndValues ...interface{}) { + l.logWithTrace(zapcore.FatalLevel, msg, keysAndValues...) +} + +// With 在 zap 中,With 方法返回的是一个新的 SugaredLogger。 +// 为了保持兼容性并继续传递 traceValue,我们需要在这里也返回一个新的 Logger 实例。 +func (l *Logger) With(args ...interface{}) *Logger { + newSl := l.sl.With(args...) + return &Logger{sl: newSl, traceValue: l.traceValue} +} + +func (l *Logger) Sync() error { + return l.sl.Sync() +} diff --git a/internal/infra/logs/logs.go b/internal/infra/logs/logs.go index 186ad4a..a83456d 100644 --- a/internal/infra/logs/logs.go +++ b/internal/infra/logs/logs.go @@ -41,7 +41,8 @@ var ( // Logger 是一个封装了 zap.SugaredLogger 的日志记录器。 // 它提供了结构化日志记录的各种方法,并实现了 io.Writer 接口以兼容 Gin。 type Logger struct { - *zap.SugaredLogger + sl *zap.SugaredLogger + traceValue string } // NewLogger 根据提供的配置创建一个新的 Logger 实例。 @@ -67,7 +68,7 @@ func NewLogger(cfg config.LogConfig) *Logger { // zap.AddCallerSkip(1) 可以向上跳一层调用栈,如果我们将 logger.Info 等方法再封装一层,这个选项会很有用 zapLogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1)) - return &Logger{zapLogger.Sugar()} + return &Logger{sl: zapLogger.Sugar()} } // InitDefaultLogger 初始化包级单例的 defaultLogger。 @@ -93,19 +94,13 @@ func GetLogger(ctx context.Context) *Logger { return defaultLogger } - val := ctx.Value(chainKey) - if val == nil { - return defaultLogger - } - chain := GetTraceStr(ctx) if chain == "" { return defaultLogger } - // 使用 With 方法创建带有 traceKey 字段的 Logger 副本 - newSugaredLogger := defaultLogger.With(traceKey, chain) - return &Logger{newSugaredLogger} + // 返回一个新的 Logger 实例,它携带了调用链信息 + return &Logger{sl: defaultLogger.sl, traceValue: chain} } // Trace 是构建和记录调用链的核心函数。 @@ -218,32 +213,24 @@ func (g *GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql "elapsed", fmt.Sprintf("%.3fms", float32(elapsed.Nanoseconds())/1e6), } - // 仅在启用调用链时附加信息 - if logConfig.EnableTrace { - chain := GetTraceStr(ctx) - if chain != "" { - fields = append(fields, traceKey, chain) - } - } - if err != nil { // 如果是 "record not found" 错误且我们配置了跳过,则直接返回 if g.SkipErrRecordNotFound && strings.Contains(err.Error(), "record not found") { return } // 否则,记录为错误日志 - defaultLogger.With(fields...).Errorf("[GORM] error: %s", err) + GetLogger(ctx).Errorw(fmt.Sprintf("[GORM] error: %s", err), fields...) return } // 如果查询时间超过慢查询阈值,则记录警告 if g.SlowThreshold != 0 && elapsed > g.SlowThreshold { - defaultLogger.With(fields...).Warnf("[GORM] slow query") + GetLogger(ctx).Warnw("[GORM] slow query", fields...) return } // 正常情况,记录 Debug 级别的 SQL 查询 - defaultLogger.With(fields...).Debugf("[GORM] trace") + GetLogger(ctx).Debugw("[GORM] trace", fields...) } // NewSilentLogger 创建一个不输出任何日志的 Logger 实例, 用于测试中屏蔽日志 @@ -255,5 +242,5 @@ func NewSilentLogger() *Logger { core := zapcore.NewCore(encoder, discardSyncer, zap.DebugLevel) // 设置为 DebugLevel 以确保所有日志都被处理(并丢弃) zapLogger := zap.New(core) sugaredLogger := zapLogger.Sugar() - return &Logger{SugaredLogger: sugaredLogger} + return &Logger{sl: sugaredLogger} }