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

1. 标准库 log

1.1 基础使用

 1package main
 2
 3import (
 4    "log"
 5)
 6
 7func main() {
 8    log.Println("This is a log message")
 9    log.Printf("User %s logged in", "admin")
10
11    // log.Fatal 会调用 os.Exit(1)
12    // log.Fatal("Fatal error")
13
14    // log.Panic 会触发 panic
15    // log.Panic("Panic error")
16}

1.2 自定义 Logger

 1import (
 2    "log"
 3    "os"
 4)
 5
 6func main() {
 7    // 创建自定义 Logger
 8    logger := log.New(
 9        os.Stdout,                      // 输出目标
10        "[MyApp] ",                     // 前缀
11        log.Ldate|log.Ltime|log.Lshortfile,  // 标志
12    )
13
14    logger.Println("Custom logger message")
15    // 输出:[MyApp] 2025/12/18 10:00:00 main.go:15: Custom logger message
16}

1.3 写入文件

 1func main() {
 2    file, err := os.OpenFile("app.log",
 3        os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
 4    if err != nil {
 5        panic(err)
 6    }
 7    defer file.Close()
 8
 9    log.SetOutput(file)
10    log.Println("This goes to file")
11}

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

2.1 基础使用

 1import (
 2    "log/slog"
 3    "os"
 4)
 5
 6func main() {
 7    // 创建 JSON 格式的 Logger
 8    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
 9
10    logger.Info("User logged in",
11        "user_id", 123,
12        "username", "admin",
13        "ip", "192.168.1.1")
14
15    // 输出:
16    // {"time":"2025-12-18T10:00:00Z","level":"INFO","msg":"User logged in","user_id":123,"username":"admin","ip":"192.168.1.1"}
17}

2.2 日志级别

 1func main() {
 2    logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
 3        Level: slog.LevelInfo,  // 只输出 Info 及以上级别
 4    }))
 5
 6    logger.Debug("Debug message")  // 不会输出
 7    logger.Info("Info message")    // 会输出
 8    logger.Warn("Warning message") // 会输出
 9    logger.Error("Error message")  // 会输出
10}

2.3 上下文日志

 1func main() {
 2    logger := slog.Default()
 3
 4    // 创建带有固定字段的子 Logger
 5    requestLogger := logger.With(
 6        "request_id", "abc123",
 7        "user_id", 456,
 8    )
 9
10    requestLogger.Info("Processing request")
11    requestLogger.Info("Request completed")
12
13    // 两条日志都会自动包含 request_id 和 user_id
14}

3. zap:高性能日志库

3.1 安装

1go get -u go.uber.org/zap

3.2 快速开始

 1import (
 2    "go.uber.org/zap"
 3)
 4
 5func main() {
 6    // 开发环境:易读的格式
 7    logger, _ := zap.NewDevelopment()
 8    defer logger.Sync()  // 刷新缓冲区
 9
10    logger.Info("User logged in",
11        zap.String("username", "admin"),
12        zap.Int("user_id", 123))
13
14    // 生产环境:JSON 格式
15    prodLogger, _ := zap.NewProduction()
16    defer prodLogger.Sync()
17
18    prodLogger.Info("Server started",
19        zap.String("port", "8080"))
20}

3.3 自定义配置

 1func main() {
 2    config := zap.NewProductionConfig()
 3
 4    // 设置日志级别
 5    config.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
 6
 7    // 设置输出路径
 8    config.OutputPaths = []string{
 9        "stdout",
10        "./logs/app.log",
11    }
12
13    // 设置错误日志路径
14    config.ErrorOutputPaths = []string{
15        "stderr",
16        "./logs/error.log",
17    }
18
19    logger, _ := config.Build()
20    defer logger.Sync()
21
22    logger.Info("Application started")
23}

3.4 性能对比

 1// zap 提供了 SugaredLogger,牺牲一点性能换取更简洁的 API
 2logger, _ := zap.NewProduction()
 3sugar := logger.Sugar()
 4
 5// 结构化日志(最快)
 6logger.Info("User logged in",
 7    zap.String("username", "admin"))
 8
 9// 格式化日志(稍慢,但更方便)
10sugar.Infof("User %s logged in", "admin")

4. 日志分级

4.1 日志级别

从低到高:

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

4.2 动态调整日志级别

 1import (
 2    "go.uber.org/zap"
 3    "go.uber.org/zap/zapcore"
 4)
 5
 6func main() {
 7    atom := zap.NewAtomicLevel()
 8
 9    config := zap.NewProductionConfig()
10    config.Level = atom
11
12    logger, _ := config.Build()
13
14    logger.Info("This will be logged")
15    logger.Debug("This won't be logged")
16
17    // 动态调整为 Debug 级别
18    atom.SetLevel(zapcore.DebugLevel)
19
20    logger.Debug("Now this will be logged")
21}

5. 日志轮转

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

1go get -u gopkg.in/natefinch/lumberjack.v2
 1import (
 2    "go.uber.org/zap"
 3    "go.uber.org/zap/zapcore"
 4    "gopkg.in/natefinch/lumberjack.v2"
 5)
 6
 7func main() {
 8    // 配置日志轮转
 9    w := zapcore.AddSync(&lumberjack.Logger{
10        Filename:   "./logs/app.log",
11        MaxSize:    100,  // MB
12        MaxBackups: 3,    // 保留旧文件数量
13        MaxAge:     28,   // 天
14        Compress:   true, // 压缩
15    })
16
17    core := zapcore.NewCore(
18        zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
19        w,
20        zap.InfoLevel,
21    )
22
23    logger := zap.New(core)
24    defer logger.Sync()
25
26    logger.Info("Log with rotation")
27}

6. 最佳实践

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

7. 总结

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

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


相关阅读