23 - 日志:log/slog、zap、zerolog、结构化日志
23 - 日志
23.1 标准 log 包
package main
import (
"log"
"os"
)
func main() {
// 基本日志
log.Println("这是一条日志")
log.Printf("用户 %s 登录, ID: %d", "Alice", 1)
// 带前缀
log.SetPrefix("[APP] ")
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Println("带前缀的日志")
// Fatal(记录后 os.Exit(1))
// log.Fatal("致命错误")
// Panic(记录后 panic)
// log.Panic("panic 错误")
// 输出到文件
f, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
log.SetOutput(f)
log.Println("写入文件的日志")
}
23.2 log/slog(Go 1.21+)
slog 是 Go 1.21 引入的官方结构化日志包。
package main
import (
"log/slog"
"os"
)
func main() {
// 文本格式(默认)
slog.Info("服务器启动", "port", 8080, "env", "production")
// 2024/01/15 10:00:00 INFO 服务器启动 port=8080 env=production
// JSON 格式
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("用户登录",
"user_id", 123,
"username", "alice",
"ip", "192.168.1.100",
)
// {"time":"2024-01-15T10:00:00Z","level":"INFO","msg":"用户登录","user_id":123,"username":"alice","ip":"192.168.1.100"}
// 日志级别
slog.Debug("调试信息") // 默认不输出
slog.Info("信息")
slog.Warn("警告")
slog.Error("错误")
// 带上下文的 logger
logger2 := slog.With(
"service", "user-service",
"version", "1.0.0",
)
logger2.Info("处理请求", "method", "GET", "path", "/api/users")
// 设置日志级别
opts := &slog.HandlerOptions{
Level: slog.LevelDebug,
}
logger3 := slog.New(slog.NewTextHandler(os.Stdout, opts))
logger3.Debug("现在可以看到调试信息了")
// WithGroup
logger4 := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger4.WithGroup("request").Info("请求信息",
"method", "GET",
"path", "/api/users",
"status", 200,
)
// {"time":"...","level":"INFO","msg":"请求信息","request":{"method":"GET","path":"/api/users","status":200}}
}
slog 自定义 Handler
type CustomHandler struct {
slog.Handler
}
func (h *CustomHandler) Handle(ctx context.Context, r slog.Record) error {
// 添加 trace_id
if traceID := ctx.Value("trace_id"); traceID != nil {
r.AddAttrs(slog.String("trace_id", traceID.(string)))
}
return h.Handler.Handle(ctx, r)
}
func main() {
base := slog.NewJSONHandler(os.Stdout, nil)
handler := &CustomHandler{Handler: base}
logger := slog.New(handler)
ctx := context.WithValue(context.Background(), "trace_id", "abc-123")
logger.InfoContext(ctx, "处理请求")
}
23.3 zap
Uber 开发的高性能日志库。
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
// 开发环境(可读格式)
logger, _ := zap.NewDevelopment()
defer logger.Sync()
logger.Info("服务器启动",
zap.Int("port", 8080),
zap.String("env", "dev"),
)
// 生产环境(JSON 格式)
prodLogger, _ := zap.NewProduction()
defer prodLogger.Sync()
prodLogger.Info("用户登录",
zap.Int("user_id", 123),
zap.String("username", "alice"),
)
// 自定义配置
config := zap.Config{
Level: zap.NewAtomicLevelAt(zap.InfoLevel),
Encoding: "json",
OutputPaths: []string{"stdout", "app.log"},
EncoderConfig: zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
},
}
customLogger, _ := config.Build()
defer customLogger.Sync()
customLogger.Info("自定义配置日志")
// Sugar Logger(printf 风格)
sugar := prodLogger.Sugar()
sugar.Infof("用户 %s 登录, ID: %d", "alice", 123)
sugar.Warnw("慢查询",
"duration", "2.5s",
"query", "SELECT * FROM users",
)
}
23.4 zerolog
零分配的高性能日志库。
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"os"
"time"
)
func main() {
// 全局配置
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
// 基本使用
log.Info().Str("service", "api").Int("port", 8080).Msg("服务器启动")
log.Error().Err(fmt.Errorf("连接失败")).Msg("数据库错误")
// 链式调用
log.Debug().
Str("method", "GET").
Str("path", "/api/users").
Dur("duration", 150*time.Millisecond).
Int("status", 200).
Msg("请求完成")
// 子 logger
subLogger := log.With().
Str("service", "user-service").
Str("version", "1.0.0").
Logger()
subLogger.Info().Msg("处理请求")
// 全局上下文
zerolog.DefaultContextLogger = &subLogger
}
日志库对比
| 特性 | log/slog | zap | zerolog |
|---|---|---|---|
| 标准库 | ✅ | ❌ | ❌ |
| 性能 | 高 | 最高 | 最高 |
| 零分配 | ❌ | 部分 | ✅ |
| API 风格 | 函数式 | 链式 | 链式 |
| 学习曲线 | 低 | 中 | 低 |
| 功能丰富度 | 中 | 高 | 中 |
23.5 结构化日志最佳实践
// 1. 使用 context 传递 trace_id
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
traceID := r.Header.Get("X-Request-ID")
ctx = context.WithValue(ctx, "trace_id", traceID)
slog.InfoContext(ctx, "处理请求",
"method", r.Method,
"path", r.URL.Path,
)
}
// 2. 日志中间件
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
slog.Info("请求完成",
"method", r.Method,
"path", r.URL.Path,
"duration", time.Since(start),
)
})
}
// 3. 错误日志
func processRequest(ctx context.Context) error {
if err := doSomething(); err != nil {
slog.ErrorContext(ctx, "处理失败",
"error", err,
"operation", "doSomething",
)
return err
}
return nil
}
🏢 业务场景
- 请求链路追踪:结构化日志 + trace_id 串联请求
- 错误监控:Error 级别日志发送到 Sentry/ELK
- 性能分析:记录慢请求的 duration
- 审计日志:记录用户操作(登录、修改、删除)