日志是排查问题的第一手段。一个好的日志系统应该:结构化、高性能、可配置、易于检索。

1. 标准库 log

1.1 基础使用

package main

import (
    "log"
)

func main() {
    log.Println("This is a log message")
    log.Printf("User %s logged in", "admin")

    // log.Fatal 会调用 os.Exit(1)
    // log.Fatal("Fatal error")

    // log.Panic 会触发 panic
    // log.Panic("Panic error")
}

1.2 自定义 Logger

import (
    "log"
    "os"
)

func main() {
    // 创建自定义 Logger
    logger := log.New(
        os.Stdout,                      // 输出目标
        "[MyApp] ",                     // 前缀
        log.Ldate|log.Ltime|log.Lshortfile,  // 标志
    )

    logger.Println("Custom logger message")
    // 输出:[MyApp] 2025/12/18 10:00:00 main.go:15: Custom logger message
}

1.3 写入文件

func main() {
    file, err := os.OpenFile("app.log",
        os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        panic(err)
    }
    defer file.Close()

    log.SetOutput(file)
    log.Println("This goes to file")
}

2. slog:结构化日志 (Go 1.21+)

2.1 基础使用

import (
    "log/slog"
    "os"
)

func main() {
    // 创建 JSON 格式的 Logger
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

    logger.Info("User logged in",
        "user_id", 123,
        "username", "admin",
        "ip", "192.168.1.1")

    // 输出:
    // {"time":"2025-12-18T10:00:00Z","level":"INFO","msg":"User logged in","user_id":123,"username":"admin","ip":"192.168.1.1"}
}

2.2 日志级别

func main() {
    logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
        Level: slog.LevelInfo,  // 只输出 Info 及以上级别
    }))

    logger.Debug("Debug message")  // 不会输出
    logger.Info("Info message")    // 会输出
    logger.Warn("Warning message") // 会输出
    logger.Error("Error message")  // 会输出
}

2.3 上下文日志

func main() {
    logger := slog.Default()

    // 创建带有固定字段的子 Logger
    requestLogger := logger.With(
        "request_id", "abc123",
        "user_id", 456,
    )

    requestLogger.Info("Processing request")
    requestLogger.Info("Request completed")

    // 两条日志都会自动包含 request_id 和 user_id
}

3. zap:高性能日志库

3.1 安装

go get -u go.uber.org/zap

3.2 快速开始

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

func main() {
    // 开发环境:易读的格式
    logger, _ := zap.NewDevelopment()
    defer logger.Sync()  // 刷新缓冲区

    logger.Info("User logged in",
        zap.String("username", "admin"),
        zap.Int("user_id", 123))

    // 生产环境:JSON 格式
    prodLogger, _ := zap.NewProduction()
    defer prodLogger.Sync()

    prodLogger.Info("Server started",
        zap.String("port", "8080"))
}

3.3 自定义配置

func main() {
    config := zap.NewProductionConfig()

    // 设置日志级别
    config.Level = zap.NewAtomicLevelAt(zap.InfoLevel)

    // 设置输出路径
    config.OutputPaths = []string{
        "stdout",
        "./logs/app.log",
    }

    // 设置错误日志路径
    config.ErrorOutputPaths = []string{
        "stderr",
        "./logs/error.log",
    }

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

    logger.Info("Application started")
}

3.4 性能对比

// zap 提供了 SugaredLogger,牺牲一点性能换取更简洁的 API
logger, _ := zap.NewProduction()
sugar := logger.Sugar()

// 结构化日志(最快)
logger.Info("User logged in",
    zap.String("username", "admin"))

// 格式化日志(稍慢,但更方便)
sugar.Infof("User %s logged in", "admin")

4. 日志分级

4.1 日志级别

从低到高:

  • Debug:调试信息,生产环境关闭
  • Info:一般信息,如服务启动、请求处理
  • Warn:警告信息,如配置缺失但有默认值
  • Error:错误信息,如请求失败、数据库连接失败
  • Fatal:致命错误,程序无法继续运行

4.2 动态调整日志级别

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

func main() {
    atom := zap.NewAtomicLevel()

    config := zap.NewProductionConfig()
    config.Level = atom

    logger, _ := config.Build()

    logger.Info("This will be logged")
    logger.Debug("This won't be logged")

    // 动态调整为 Debug 级别
    atom.SetLevel(zapcore.DebugLevel)

    logger.Debug("Now this will be logged")
}

5. 日志轮转

使用 lumberjack 库实现日志文件自动轮转:

go get -u gopkg.in/natefinch/lumberjack.v2
import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "gopkg.in/natefinch/lumberjack.v2"
)

func main() {
    // 配置日志轮转
    w := zapcore.AddSync(&lumberjack.Logger{
        Filename:   "./logs/app.log",
        MaxSize:    100,  // MB
        MaxBackups: 3,    // 保留旧文件数量
        MaxAge:     28,   // 天
        Compress:   true, // 压缩
    })

    core := zapcore.NewCore(
        zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
        w,
        zap.InfoLevel,
    )

    logger := zap.New(core)
    defer logger.Sync()

    logger.Info("Log with rotation")
}

6. 最佳实践

  1. 使用结构化日志:便于检索和分析
  2. 合理设置日志级别:生产环境用 Info,开发环境用 Debug
  3. 避免敏感信息:不要记录密码、Token 等
  4. 添加上下文:request_id、user_id 等便于追踪
  5. 日志轮转:避免日志文件过大
  6. 性能考虑:高频日志使用 zap

7. 总结

特性logslogzap
性能
结构化
学习成本
适用场景简单脚本一般应用高性能服务

思考题: 为什么 zap 需要调用 Sync() 方法?如果不调用会有什么后果?


相关阅读