Go 语言日志处理全面指南:方法与最佳实践

人这东西真是不可思议,一瞬间就长大了好多岁,过去我还以为人,是一年一年按部就班地增长岁数的。但其实不是,人是一瞬间长大变老的

Posted by yishuifengxiao on 2024-03-09

标准库 log 包

基础日志功能

package main

import (
"log"
"os"
)

func main() {
// 基本日志输出
log.Print("这是一条普通日志")
log.Printf("格式化日志: %s", "Hello, World!")
log.Println("带换行的日志")

// 带前缀的日志
log.SetPrefix("INFO: ")
log.Print("这是一条信息日志")

// 配置日志格式
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Print("带时间戳和文件信息的日志")

// 致命错误日志(会调用 os.Exit(1))
// log.Fatal("这是一条致命错误日志")
// log.Fatalf("格式化致命错误: %s", "错误详情")
// log.Fatalln("致命错误带换行")

// 恐慌日志(会调用 panic())
// log.Panic("这是一条恐慌日志")
// log.Panicf("格式化恐慌: %s", "错误详情")
// log.Panicln("恐慌日志带换行")
}

适用场景

  • 简单的命令行工具
  • 快速原型开发
  • 不需要复杂日志功能的场景

自定义日志输出

func customLogger() {
// 创建自定义日志器
file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal("无法打开日志文件:", err)
}
defer file.Close()

// 创建多输出日志器
multiWriter := io.MultiWriter(os.Stdout, file)

logger := log.New(multiWriter, "APP: ", log.Ldate|log.Ltime|log.Lshortfile)

logger.Println("这条日志会同时输出到控制台和文件")
logger.Printf("用户 %s 登录成功", "alice")
}

适用场景

  • 需要同时输出到多个目标(控制台+文件)
  • 需要为不同组件创建独立日志器
  • 简单的文件日志记录

结构化日志处理

使用标准库实现简单结构化日志

type StructuredLogger struct {
*log.Logger
}

func NewStructuredLogger(out io.Writer, prefix string) *StructuredLogger {
return &StructuredLogger{
Logger: log.New(out, prefix, log.Ldate|log.Ltime),
}
}

func (l *StructuredLogger) Info(message string, fields map[string]interface{}) {
logData := fmt.Sprintf("INFO: %s", message)
for k, v := range fields {
logData += fmt.Sprintf(" %s=%v", k, v)
}
l.Println(logData)
}

func (l *StructuredLogger) Error(message string, err error, fields map[string]interface{}) {
logData := fmt.Sprintf("ERROR: %s - %v", message, err)
for k, v := range fields {
logData += fmt.Sprintf(" %s=%v", k, v)
}
l.Println(logData)
}

func structuredLoggingExample() {
logger := NewStructuredLogger(os.Stdout, "")

logger.Info("用户操作", map[string]interface{}{
"user_id": 123,
"action": "login",
"ip": "192.168.1.1",
})

logger.Error("数据库连接失败",
errors.New("connection refused"),
map[string]interface{}{
"db_host": "localhost",
"db_port": 5432,
})
}

适用场景

  • 需要基本结构化日志但不想引入第三方库
  • 简单的键值对日志记录
  • 自定义日志格式需求

第三方日志库

logrus - 功能丰富的结构化日志库

import (
"github.com/sirupsen/logrus"
"os"
)

func logrusExample() {
// 基本配置
logrus.SetFormatter(&logrus.JSONFormatter{})
logrus.SetOutput(os.Stdout)
logrus.SetLevel(logrus.InfoLevel)

// 基本日志
logrus.Info("这是一条信息日志")
logrus.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("一群海豚出现了")

// 上下文日志
contextLogger := logrus.WithFields(logrus.Fields{
"request_id": "req-123",
"user_ip": "192.168.1.1",
})

contextLogger.Info("处理用户请求")
contextLogger.Warn("请求处理较慢")

// 错误日志
err := errors.New("数据库连接失败")
logrus.WithError(err).Error("操作失败")

// 文件钩子
file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err == nil {
logrus.AddHook(&fileHook{file})
} else {
logrus.Error("无法打开日志文件")
}
}

// 自定义文件钩子
type fileHook struct {
file *os.File
}

func (hook *fileHook) Fire(entry *logrus.Entry) error {
line, err := entry.String()
if err != nil {
return err
}
_, err = hook.file.WriteString(line)
return err
}

func (hook *fileHook) Levels() []logrus.Level {
return logrus.AllLevels
}

适用场景

  • 需要丰富的结构化日志功能
  • JSON 格式日志输出
  • 复杂的日志级别控制
  • 需要日志钩子扩展功能

zap - 高性能日志库

import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

func zapExample() {
// 生产环境配置(高性能)
logger, err := zap.NewProduction()
if err != nil {
panic(err)
}
defer logger.Sync()

logger.Info("这是一条信息日志",
zap.String("key", "value"),
zap.Int("number", 42),
)

// 开发环境配置(更人性化)
devLogger, _ := zap.NewDevelopment()
defer devLogger.Sync()

devLogger.Debug("调试信息",
zap.String("module", "auth"),
zap.Duration("latency", 150*time.Millisecond),
)

// 自定义配置
config := zap.Config{
Level: zap.NewAtomicLevelAt(zap.InfoLevel),
Development: false,
Sampling: &zap.SamplingConfig{
Initial: 100,
Thereafter: 100,
},
Encoding: "json",
EncoderConfig: zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
},
OutputPaths: []string{"stdout", "/tmp/zap.log"},
ErrorOutputPaths: []string{"stderr"},
}

customLogger, _ := config.Build()
defer customLogger.Sync()

customLogger.Warn("自定义配置警告",
zap.String("service", "api"),
zap.Int("port", 8080),
)

// 性能敏感场景使用 SugaredLogger
sugar := customLogger.Sugar()
sugar.Infow("糖语法日志",
"url", "https://example.com",
"attempt", 3,
"backoff", time.Second,
)
}

适用场景

  • 高性能要求的应用
  • 微服务和分布式系统
  • 需要极低延迟的日志记录
  • 大规模日志生产环境

zerolog - 零分配 JSON 日志库

import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"os"
"time"
)

func zerologExample() {
// 基本配置
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Info().Msg("这是一条信息日志")

// 结构化日志
log.Info().
Str("animal", "walrus").
Int("size", 10).
Msg("一群海豚出现了")

// 错误日志
err := errors.New("连接失败")
log.Error().
Err(err).
Str("service", "mysql").
Msg("无法连接数据库")

// 上下文日志
sublogger := log.With().
Str("component", "auth").
Logger()

sublogger.Info().Msg("用户认证成功")

// 控制台友好输出
consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}
consoleLogger := zerolog.New(consoleWriter).With().Timestamp().Logger()

consoleLogger.Info().
Str("path", "/api/v1/users").
Int("status", 200).
Msg("请求处理完成")

// 采样日志(限制日志速率)
sampledLogger := log.Sample(&zerolog.BasicSampler{N: 10}) // 每10条记录1条
for i := 0; i < 100; i++ {
sampledLogger.Info().Int("i", i).Msg("采样日志")
}
}

适用场景

  • 需要极致性能的应用
  • 零内存分配要求的场景
  • 纯粹的 JSON 日志输出
  • 资源受限环境

日志最佳实践

日志级别管理

func logLevelManagement() {
// 动态日志级别
logLevel := os.Getenv("LOG_LEVEL")
var level zerolog.Level

switch logLevel {
case "debug":
level = zerolog.DebugLevel
case "info":
level = zerolog.InfoLevel
case "warn":
level = zerolog.WarnLevel
case "error":
level = zerolog.ErrorLevel
default:
level = zerolog.InfoLevel
}

zerolog.SetGlobalLevel(level)

// 不同级别日志
log.Debug().Msg("调试信息") // 只在DEBUG级别显示
log.Info().Msg("普通信息")
log.Warn().Msg("警告信息")
log.Error().Msg("错误信息")
}

请求上下文日志

func requestContextLogging() {
// HTTP中间件添加请求ID
http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
// 生成或获取请求ID
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
}

// 创建请求上下文日志器
logger := log.With().
Str("request_id", requestID).
Str("method", r.Method).
Str("path", r.URL.Path).
Logger()

// 设置响应头
w.Header().Set("X-Request-ID", requestID)

// 记录请求开始
logger.Info().Msg("请求开始")

// 处理请求...
time.Sleep(100 * time.Millisecond)

// 记录请求完成
logger.Info().
Int("status", 200).
Dur("duration_ms", 100).
Msg("请求完成")
})
}

性能敏感场景的日志优化

func performanceSensitiveLogging() {
// 使用条件日志避免不必要的计算
if log.Debug().Enabled() {
// 只在DEBUG级别时计算昂贵操作
expensiveData := calculateExpensiveData()
log.Debug().Interface("data", expensiveData).Msg("调试数据")
}

// 使用采样减少日志量
sampler := &zerolog.BasicSampler{N: 100} // 每100条记录1条
sampledLog := log.Sample(sampler)

for i := 0; i < 1000; i++ {
sampledLog.Info().Int("iteration", i).Msg("处理中")
}
}

分布式系统日志

func distributedSystemLogging() {
// 添加追踪信息
traceID := "trace-123456"
spanID := "span-789"

logger := log.With().
Str("trace_id", traceID).
Str("span_id", spanID).
Logger()

// 跨服务日志
logger.Info().Msg("服务调用开始")

// 模拟调用下游服务
err := callDownstreamService(traceID)
if err != nil {
logger.Error().
Err(err).
Str("downstream_service", "user-service").
Msg("下游服务调用失败")
}

logger.Info().Msg("服务调用完成")
}

日志输出与聚合

多输出目标

func multiOutputLogging() {
// 创建多个输出
consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
fileWriter, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)

// 多路复用输出
multiWriter := zerolog.MultiLevelWriter(consoleWriter, fileWriter)
logger := zerolog.New(multiWriter).With().Timestamp().Logger()

logger.Info().Msg("这条日志会输出到控制台和文件")

// 不同级别输出到不同位置
errorFile, _ := os.OpenFile("error.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
errorWriter := zerolog.LevelWriterAdapter{
Writer: errorFile,
Level: zerolog.ErrorLevel,
}

multiLevelWriter := zerolog.MultiLevelWriter(consoleWriter, errorWriter)
levelLogger := zerolog.New(multiLevelWriter).With().Timestamp().Logger()

levelLogger.Info().Msg("信息日志") // 只输出到控制台
levelLogger.Error().Msg("错误日志") // 输出到控制台和错误文件
}

日志旋转

func logRotation() {
// 使用lumberjack实现日志旋转
lumberjackLogger := &lumberjack.Logger{
Filename: "/var/log/myapp/app.log",
MaxSize: 100, // MB
MaxBackups: 5,
MaxAge: 28, // days
Compress: true,
}

defer lumberjackLogger.Close()

// 创建日志器
logger := zerolog.New(lumberjackLogger).With().Timestamp().Logger()

// 模拟大量日志写入
for i := 0; i < 100000; i++ {
logger.Info().Int("index", i).Msg("测试日志")
}
}

云原生日志

func cloudNativeLogging() {
// 结构化日志适合云环境
logger := log.With().
Str("service", "user-service").
Str("pod", os.Getenv("HOSTNAME")).
Str("environment", os.Getenv("ENV")).
Logger()

// 标准字段便于聚合
logger.Info().
Str("event", "user_login").
Str("user_id", "123").
Msg("用户登录")

// 错误日志包含堆栈
err := errors.New("数据库错误")
logger.Error().
Err(err).
Stack().
Msg("数据库操作失败")
}

日志监控与告警

错误率监控

func errorRateMonitoring() {
errorCount := 0
totalRequests := 0
var mu sync.Mutex

// 记录错误和请求
http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
totalRequests++
mu.Unlock()

// 处理请求
err := processRequest(r)
if err != nil {
mu.Lock()
errorCount++
mu.Unlock()

log.Error().
Err(err).
Str("path", r.URL.Path).
Msg("请求处理失败")

// 检查错误率
errorRate := float64(errorCount) / float64(totalRequests)
if errorRate > 0.1 { // 10%错误率阈值
log.Warn().
Float64("error_rate", errorRate).
Msg("错误率超过阈值")
}
}
})
}

性能指标日志

func performanceMetricsLogging() {
// 记录性能指标
start := time.Now()

// 执行操作
result, err := expensiveOperation()
duration := time.Since(start)

log.Info().
Dur("duration_ms", duration).
Bool("success", err == nil).
Msg("操作完成")

// 指标分类
metrics := map[string]interface{}{
"operation_duration_ms": duration.Milliseconds(),
"operation_success": err == nil,
"result_size": len(result),
}

log.Info().
Fields(metrics).
Msg("性能指标")
}

测试中的日志处理

测试日志捕获

func TestHandler(t *testing.T) {
// 创建内存缓冲区捕获日志
var buf bytes.Buffer
logger := zerolog.New(&buf)

// 替换全局日志器
oldLogger := log.Logger
log.Logger = logger
defer func() { log.Logger = oldLogger }()

// 执行测试
handler()

// 检查日志
if !strings.Contains(buf.String(), "expected log message") {
t.Errorf("日志中未找到预期消息: %s", buf.String())
}
}

基准测试中的日志禁用

func BenchmarkOperation(b *testing.B) {
// 禁用日志以提高基准测试性能
log.Logger = zerolog.Nop()

b.ResetTimer()
for i := 0; i < b.N; i++ {
// 被测试的操作
operation()
}
}