Golang中如何记录请求ID
Golang中如何记录请求ID
我们有一个用Go编写的Web服务器,正在使用gorilla/mux包。
我们希望在处理程序端为所有日志添加请求ID,以便后续能够区分哪些日志与哪个请求相关(我们可能会同时接收大量请求并进行处理)。
有没有简单的方法可以实现这一点?这种方法不需要将上下文或请求ID沿着调用链传递给所有函数,也不需要手动将它们添加到日志中。
(我理解使用goroutine ID并不好/不推荐,所以如果存在其他简单的解决方案,我们正在寻找这样的方法。)
谢谢!
2 回复
更多关于Golang中如何记录请求ID的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go Web应用中,为每个请求分配唯一的请求ID并自动记录到日志中,可以通过中间件和上下文(context)来实现。以下是使用gorilla/mux的解决方案:
package main
import (
"context"
"fmt"
"log"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/google/uuid"
)
// 定义上下文键类型
type key string
const requestIDKey key = "requestID"
// 请求ID中间件
func requestIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 生成唯一请求ID
requestID := uuid.New().String()
// 将请求ID添加到响应头(可选)
w.Header().Set("X-Request-ID", requestID)
// 将请求ID存入上下文
ctx := context.WithValue(r.Context(), requestIDKey, requestID)
// 创建带有自定义字段的日志记录器
logger := log.New(log.Writer(), fmt.Sprintf("[%s] ", requestID), log.LstdFlags)
// 将日志记录器也存入上下文(可选)
ctx = context.WithValue(ctx, "logger", logger)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// 从上下文中获取请求ID的辅助函数
func getRequestID(ctx context.Context) string {
if id, ok := ctx.Value(requestIDKey).(string); ok {
return id
}
return "unknown"
}
// 示例处理程序
func helloHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
requestID := getRequestID(ctx)
// 方式1:手动记录带请求ID的日志
log.Printf("[%s] Processing request to /hello", requestID)
// 方式2:使用上下文中的日志记录器(如果添加了)
if logger, ok := ctx.Value("logger").(*log.Logger); ok {
logger.Println("Processing request to /hello")
}
// 模拟处理时间
time.Sleep(100 * time.Millisecond)
w.WriteHeader(http.StatusOK)
w.Write([]byte(fmt.Sprintf("Hello! Request ID: %s", requestID)))
}
// 另一个处理程序示例
func apiHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
requestID := getRequestID(ctx)
log.Printf("[%s] API request received", requestID)
// 业务逻辑...
time.Sleep(50 * time.Millisecond)
w.WriteHeader(http.StatusOK)
w.Write([]byte(fmt.Sprintf(`{"request_id": "%s", "status": "success"}`, requestID)))
}
func main() {
r := mux.NewRouter()
// 应用请求ID中间件到所有路由
r.Use(requestIDMiddleware)
r.HandleFunc("/hello", helloHandler)
r.HandleFunc("/api", apiHandler)
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
如果需要更高级的日志集成,可以使用结构化的日志库如logrus或zap:
import (
"github.com/sirupsen/logrus"
)
// 使用logrus的中间件示例
func logrusRequestIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := uuid.New().String()
w.Header().Set("X-Request-ID", requestID)
ctx := context.WithValue(r.Context(), requestIDKey, requestID)
// 创建带请求ID字段的日志条目
logger := logrus.WithField("request_id", requestID)
ctx = context.WithValue(ctx, "logrusLogger", logger)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// 使用logrus的处理程序示例
func logrusHandler(w http.ResponseWriter, r *http.Request) {
if logger, ok := r.Context().Value("logrusLogger").(*logrus.Entry); ok {
logger.Info("Processing request with logrus")
logger.WithField("user_agent", r.UserAgent()).Debug("Request details")
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("Logrus example"))
}
这种方法的好处:
- 自动为每个请求生成唯一ID
- 通过中间件自动处理,无需修改现有处理程序逻辑
- 请求ID可通过上下文在所有处理函数中访问
- 支持结构化日志记录
- 线程安全,适用于并发环境
运行示例后,日志输出将包含请求ID:
[550e8400-e29b-41d4-a716-446655440000] 2024/01/01 10:00:00 Processing request to /hello

