使用结构化日志、中间件记录请求、文件轮转、上下文跟踪和错误分离,可构建高效Golang Web日志系统。1. 用logrus输出JSON格式日志;2. 通过中间件自动记录请求详情及响应时间;3. 结合lumberjack实现日志切割;4. 在中间件中生成并传递traceID,便于链路追踪;5. 将错误日志输出至独立文件。完整示例包含字段包括方法、URL、状态码、客户端IP、耗时和traceID,提升问题排查与监控效率。

在构建 Golang Web 服务时,日志是排查问题、监控系统行为和保障服务稳定的核心工具。良好的日志收集与管理机制能显著提升系统的可观测性。以下是实现高效 Web 服务日志收集的实用技巧和代码示例。
1. 使用结构化日志替代标准输出
Go 标准库的 log 包功能有限,输出的是纯文本,不利于后续分析。推荐使用结构化日志库如 logrus 或 zap,它们支持 JSON 格式输出,便于日志系统(如 ELK、Loki)解析。
以 logrus 为例:
package main
import (
"github.com/sirupsen/logrus"
"net/http"
)
var logger = logrus.New()
func init() {
logger.SetFormatter(&logrus.JSONFormatter{}) // 输出为 JSON
logger.SetLevel(logrus.InfoLevel)
}
func handler(w http.ResponseWriter, r *http.Request) {
logger.WithFields(logrus.Fields{
"method": r.Method,
"url": r.URL.String(),
"clientIP": r.RemoteAddr,
"userAgent": r.UserAgent(),
}).Info("HTTP request received")
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello"))
}
2. 中间件统一记录请求日志
通过 HTTP 中间件自动记录每个请求的进入和响应信息,避免在每个处理函数中重复写日志。
立即学习“go语言免费学习笔记(深入)”;
示例中间件:
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 包装 ResponseWriter 以捕获状态码
rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(rw, r)
logger.WithFields(logrus.Fields{
"method": r.Method,
"url": r.URL.String(),
"status": rw.statusCode,
"durationMs": time.Since(start).Milliseconds(),
"clientIP": r.RemoteAddr,
"userAgent": r.UserAgent(),
}).Info("request completed")
})
}
// 包装 ResponseWriter
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
使用方式:
http.Handle("/", loggingMiddleware(http.HandlerFunc(handler)))
http.ListenAndServe(":8080", nil)
3. 将日志输出到文件并轮转
生产环境不应只输出到控制台。可使用 lumberjack 配合 logrus 实现日志文件切割。
安装:
go get gopkg.in/natefinch/lumberjack.v2
配置日志输出到文件:
logger.SetOutput(&lumberjack.Logger{
Filename: "/var/log/myapp/access.log",
MaxSize: 10, // MB
MaxBackups: 5,
MaxAge: 7, // 天
Compress: true, // 启用压缩
})
4. 添加上下文跟踪 ID(Trace ID)
在分布式系统中,为每个请求生成唯一 Trace ID,有助于跨服务追踪请求链路。
修改中间件添加 Trace ID:
func tracingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
traceID := r.Header.Get("X-Trace-ID")
if traceID == "" {
traceID = generateTraceID() // 可用 uuid 或 nanoid
}
ctx := context.WithValue(r.Context(), "traceID", traceID)
r = r.WithContext(ctx)
w.Header().Set("X-Trace-ID", traceID)
next.ServeHTTP(w, r)
})
}
日志中记录 traceID:
logger.WithField("traceID", r.Context().Value("traceID")).Info("processing request")
5. 错误日志单独处理
将错误日志输出到独立文件,便于快速定位问题。
可创建两个 logger 实例:
accessLogger := logrus.New()
errorLogger := logrus.New()
accessLogger.SetOutput(accessLogWriter)
errorLogger.SetOutput(errorLogWriter)
// 在处理中根据级别选择 logger
if level >= logrus.ErrorLevel {
errorLogger.WithFields(...).Error(msg)
} else {
accessLogger.WithFields(...).Info(msg)
}
基本上就这些。结合结构化日志、中间件自动化、文件轮转和上下文跟踪,就能搭建一个清晰、可维护的 Golang Web 日志系统。不复杂但容易忽略细节,比如状态码捕获和 traceID 传递,做好这些,日志才真正有用。










