日志是排查问题的第一手段。一个好的日志系统应该:结构化、高性能、可配置、易于检索。
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. 最佳实践
- 使用结构化日志:便于检索和分析
- 合理设置日志级别:生产环境用 Info,开发环境用 Debug
- 避免敏感信息:不要记录密码、Token 等
- 添加上下文:request_id、user_id 等便于追踪
- 日志轮转:避免日志文件过大
- 性能考虑:高频日志使用 zap
7. 总结
| 特性 | log | slog | zap |
|---|---|---|---|
| 性能 | 中 | 中 | 高 |
| 结构化 | ❌ | ✅ | ✅ |
| 学习成本 | 低 | 低 | 中 |
| 适用场景 | 简单脚本 | 一般应用 | 高性能服务 |
思考题:
为什么 zap 需要调用 Sync() 方法?如果不调用会有什么后果?