【发布时间】:2021-06-18 07:37:03
【问题描述】:
想法:我想用唯一的请求 ID 将传入和传出请求记录到我的 Gin 服务器。此外,我想使用与路由相同的请求 ID 在我的 Gin 路由中记录所有 HTTP 客户端的请求。
所有这些都应该使用中间件在后台工作。
将请求记录到我的服务器(和响应)
为了将每个请求记录到我的服务器,我编写了这个中间件:
import (
"bytes"
"context"
"github.com/gin-contrib/requestid"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"io/ioutil"
"net/http"
"time"
)
type responseBodyWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (r responseBodyWriter) Write(b []byte) (int, error) {
r.body.Write(b)
return r.ResponseWriter.Write(b)
}
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
w := &responseBodyWriter{body: &bytes.Buffer{}, ResponseWriter: c.Writer}
c.Writer = w
msg := "Input:"
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
if raw != "" {
path = path + "?" + raw
}
// Read from body and write here again.
var bodyBytes []byte
if c.Request.Body != nil {
bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
}
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
inputLogger := log.With().
Str("method", c.Request.Method).
Str("path", path).
Str("requestId", requestid.Get(c)).
Logger()
if len(bodyBytes) > 0 {
inputLogger.Info().RawJSON("body", bodyBytes).Msg(msg)
} else {
inputLogger.Info().Msg(msg)
}
c.Next()
end := time.Now()
latency := end.Sub(start)
msg = "Output:"
outputLogger := log.With().
Str("method", c.Request.Method).
Str("path", path).
Str("requestId", requestid.Get(c)).
RawJSON("body", w.body.Bytes()).
Int("status", c.Writer.Status()).
Dur("latency", latency).
Logger()
switch {
case c.Writer.Status() >= http.StatusBadRequest && c.Writer.Status() < http.StatusInternalServerError:
{
outputLogger.Warn().Msg(msg)
}
case c.Writer.Status() >= http.StatusInternalServerError:
{
outputLogger.Error().Msg(msg)
}
default:
outputLogger.Info().Msg(msg)
}
}
}
记录在我的服务器路由中发出的请求
问题是:我不知道如何将由 Gin 的中间件创建的请求 ID(或 Gin 的上下文)传递给 RoundTrip 函数:
type Transport struct {
Transport http.RoundTripper
}
var defaultTransport = Transport{
Transport: http.DefaultTransport,
}
func init() {
http.DefaultTransport = &defaultTransport
}
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
ctx := context.WithValue(req.Context(), ContextKeyRequestStart, time.Now())
req = req.WithContext(ctx)
t.logRequest(req)
resp, err := t.transport().RoundTrip(req)
if err != nil {
return resp, err
}
t.logResponse(resp)
return resp, err
}
func (t *Transport) logRequest(req *http.Request) {
log.Info().
Str("method", req.Method).
Str("path", req.URL.String()).
Str("requestId", "how can I get request id here???").
Msg("Api request: ")
}
func (t *Transport) logResponse(resp *http.Response) {
var bodyBytes []byte
if resp.Body != nil {
bodyBytes, _ = ioutil.ReadAll(resp.Body)
}
resp.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
ctx := resp.Request.Context()
log.Info().
Str("method", resp.Request.Method).
Str("path", resp.Request.URL.String()).
Str("requestId", "how can I get request id here???").
RawJSON("body", bodyBytes).
Int("status", resp.StatusCode).
Dur("latency", time.Now().Sub(ctx.Value(ContextKeyRequestStart).(time.Time))).
Msg("API response: ")
}
func (t *Transport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
【问题讨论】: