默认 log 包应停用,因其无级别控制、不支持结构化输出且无法动态调整目标,难以满足线上排查与监控需求;推荐 zap 或 zerolog,并通过 context 传递带上下文的 logger。
Go 标准库的 log 包没有级别控制、不支持结构化输出、无法动态调整输出目标,一旦项目需要排查线上问题或对接监控系统,它就会成为日志链路的断点。尤其在 HTTP 服务中,你没法用 log.Printf 区分 info / warn / error,也不能把请求 ID、耗时、状态码自动注入每条日志。
zap(性能高、结构化强)或 zerolog(零分配、API 简洁),二者都支持 With() 追加字段、LevelEnabler 动态开关级别context.WithValue(ctx, key, logger) 传递带请求上下文的 loggeruser_token、password)直接打到日志里——用 logger.With(zap.String("user_id", uid)).Info("login success") 替代拼接字符串手动在每个 handler 开头打 start、结尾打 end 日志极易遗漏且难以对齐字段。用中间件封装是唯一靠谱做法,关键在于:时间戳必须在 next.ServeHTTP 前后用 time.Now() 精确采集,且所有字段要一次性写入,避免多次 IO。
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
lw := &responseWriter{ResponseWriter: w, statusCode: 200}
next.ServeHTTP(l
w, r)
duration := time.Since(start)
logger.Info("http request",
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
zap.Int("status", lw.statusCode),
zap.Duration("duration", duration),
zap.String("ip", getClientIP(r)),
zap.String("user_agent", r.UserAgent()),
)
})
}
responseWriter 必须实现 WriteHeader(int) 方法才能捕获真实 status code,否则默认是 200gin.LoggerWithConfig 或 echo.MiddlewareLogger,避免重复造轮子用 prometheus.NewCounter 或 prometheus.NewHistogram 注册指标后,若不控制 label 维度,比如把 user_id 当作 label 值,会导致指标数量无限膨胀,进程 OOM。
handler("login" / "order_create")、status_code("200" / "404" / "500")HistogramVec,并设置合理 buckets:prometheus.ExponentialBuckets(0.01, 2, 10) 覆盖 10ms–5s 区间/metrics,且确保路径不被其他中间件拦截(例如 auth 中间件不该作用于该路径)var (
httpDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests.",
Buckets: prometheus.ExponentialBuckets(0.01, 2, 10),
},
[]string{"handler", "status_code"},
)
)
func init() {
prometheus.MustRegister(httpDuration)
}
开发时你要看到颜色、行号、函数名;上线后这些全是冗余开销,还会干扰日志聚合系统(如 Loki)的解析。硬编码判断 os.Getenv("ENV") == "prod" 不可靠,应由构建阶段或配置驱动。
zap.Config 分离配置:dev 模式启用 Development: true,prod 模式设 Encoding: "json" + Level: zapcore.ErrorLevel
os.Stdout —— 生产环境应重定向到文件,并配合 lumberjack.Logger 实现轮转Warn 或更高,避免被高频请求刷屏最常被忽略的一点:日志采样。高频低价值日志(如“cache miss”)不做采样,几秒内就能打满磁盘。用 zap.Sampling(&zap.SamplingConfig{Initial: 100, Thereafter: 100}) 控制单位时间内的最大输出条数。