Golang中如何记录请求ID

Golang中如何记录请求ID 我们有一个用Go编写的Web服务器,正在使用gorilla/mux包。
我们希望在处理程序端为所有日志添加请求ID,以便后续能够区分哪些日志与哪个请求相关(我们可能会同时接收大量请求并进行处理)。

有没有简单的方法可以实现这一点?这种方法不需要将上下文或请求ID沿着调用链传递给所有函数,也不需要手动将它们添加到日志中。

(我理解使用goroutine ID并不好/不推荐,所以如果存在其他简单的解决方案,我们正在寻找这样的方法。)

谢谢!

2 回复

请查看此实现,通常的做法是生成一个ID并将其注入到请求上下文中,这样您就可以从任何中间件访问它。有些项目使用UUID

更多关于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
回到顶部