Golang微服务日志包开发:响应对象日志记录方法求助
Go微服务日志包:完整记录HTTP请求/响应的实现方案
一、HTTP Handler场景:记录响应状态码、耗时、响应体
默认http.ResponseWriter不暴露响应体和状态码,需要自定义封装一个实现该接口的结构体,来捕获这些信息。结合gorilla/mux的中间件,可以实现全局日志记录,无需关心具体请求URL。
1. 自定义LoggingResponseWriter
import ( "bytes" "net/http" ) type LoggingResponseWriter struct { http.ResponseWriter statusCode int body bytes.Buffer } func NewLoggingResponseWriter(w http.ResponseWriter) *LoggingResponseWriter { return &LoggingResponseWriter{w, http.StatusOK, bytes.Buffer{}} } // 重写WriteHeader,记录状态码 func (lrw *LoggingResponseWriter) WriteHeader(code int) { lrw.statusCode = code lrw.ResponseWriter.WriteHeader(code) } // 重写Write,记录响应体 func (lrw *LoggingResponseWriter) Write(b []byte) (int, error) { lrw.body.Write(b) return lrw.ResponseWriter.Write(b) }
2. 实现日志中间件
import ( "encoding/json" "io" "log" "net/http" "time" "github.com/gorilla/mux" ) func LoggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() lrw := NewLoggingResponseWriter(w) // 执行后续业务handler next.ServeHTTP(lrw, r) // 读取请求体(需重新放回,避免后续handler无法读取) reqBody := readRequestBody(r) // 组装日志数据 logData := map[string]interface{}{ "request_method": r.Method, "request_url": r.URL.String(), "status_code": lrw.statusCode, "duration_ms": time.Since(start).Milliseconds(), "request_body": reqBody, "response_body": lrw.body.String(), } // 转为JSON并发送到Elasticsearch jsonBytes, err := json.Marshal(logData) if err != nil { log.Printf("日志序列化失败: %v", err) return } // 这里替换为你的ES发送逻辑,比如用ES客户端或HTTP POST // sendToES(jsonBytes) }) } // 读取请求体并重置Body func readRequestBody(r *http.Request) string { if r.Body == nil { return "" } bodyBytes, err := io.ReadAll(r.Body) if err != nil { log.Printf("读取请求体失败: %v", err) return "" } r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) return string(bodyBytes) }
3. 接入gorilla/mux
func main() { r := mux.NewRouter() // 全局注册日志中间件 r.Use(LoggingMiddleware) // 注册业务handler r.HandleFunc("/api/user", userHandler) http.ListenAndServe(":8080", r) }
这个中间件会自动处理所有经过路由的请求,无需提前知晓具体URL即可记录完整响应信息。
二、主动发起HTTP请求场景(http.Get等):记录响应信息
使用http.Get/http.Post等方法时,返回的*http.Response包含了状态码、响应体等所有信息,只需读取响应体并备份即可。
import ( "bytes" "encoding/json" "io" "log" "net/http" "time" ) func logOutgoingRequest(url string) { start := time.Now() resp, err := http.Get(url) if err != nil { log.Printf("请求失败: %v", err) return } defer resp.Body.Close() // 读取响应体并备份 respBody, err := io.ReadAll(resp.Body) if err != nil { log.Printf("读取响应体失败: %v", err) return } // 重置Body,方便后续复用 resp.Body = io.NopCloser(bytes.NewBuffer(respBody)) // 组装日志数据 logData := map[string]interface{}{ "request_url": url, "request_method": "GET", "status_code": resp.StatusCode, "duration_ms": time.Since(start).Milliseconds(), "response_body": string(respBody), } // 转为JSON发送到ES jsonBytes, err := json.Marshal(logData) if err != nil { log.Printf("日志序列化失败: %v", err) return } // sendToES(jsonBytes) }
如果是POST/PUT等带请求体的请求,需要在发起请求前提前备份请求体内容,避免发送后无法读取。
关键注意事项
- 自定义
LoggingResponseWriter时,若业务中用到http.ResponseWriter的其他方法(如Header()),需确保结构体正确转发调用。 - 读取请求/响应体后必须重置
Body,否则后续处理会因流已关闭而失败。 - 发送日志到Elasticsearch建议用异步方式(如goroutine+缓冲channel),避免阻塞HTTP请求处理。
内容的提问来源于stack exchange,提问作者Kumarsatyam4324
相关产品推荐
相关产品推荐

