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