如何使用 Gin 上下文创建的请求 ID 记录 HTTP 客户端的请求
How to log HTTP client's requests with request ID that was created by Gin context
想法:我想使用唯一请求 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
}
Transport.RoundTrip
函数接受一个 *http.Request
参数,因此您应该能够通过在处理程序中使用它创建请求来传递 Gin 上下文:
func MyHandler(c *gin.Context) {
// passing context to the request
req := http.NewRequestWithContext(c, "GET", "http://localhost:8080", nil)
resp, err := http.DefaultClient.Do(req)
}
请注意,为了能够使用您覆盖的默认 RoundTripper
而无需额外初始化,您应该使用 http.DefaultClient
.
你可以使用这个:
https://github.com/sumit-tembe/gin-requestid
package main
import (
"net/http"
"github.com/gin-gonic/gin"
requestid "github.com/sumit-tembe/gin-requestid"
)
func main() {
// without any middlewares
router := gin.New()
// Middlewares
{
//recovery middleware
router.Use(gin.Recovery())
//middleware which injects a 'RequestID' into the context and header of each request.
router.Use(requestid.RequestID(nil))
//middleware which enhance Gin request logger to include 'RequestID'
router.Use(gin.LoggerWithConfig(requestid.GetLoggerConfig(nil, nil, nil)))
}
router.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello world!")
})
router.Run(":8080")
}
输出:
[GIN-debug] 2019-12-16T18:50:49+05:30 [bzQg6wTpL4cdZ9bM] - "GET /"
[GIN-debug] 2019-12-16T18:50:49+05:30 [bzQg6wTpL4cdZ9bM] - [::1] "GET / HTTP/1.1 200 22.415µs" Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
还支持自定义请求id生成器,您可以根据需要设计。
想法:我想使用唯一请求 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
}
Transport.RoundTrip
函数接受一个 *http.Request
参数,因此您应该能够通过在处理程序中使用它创建请求来传递 Gin 上下文:
func MyHandler(c *gin.Context) {
// passing context to the request
req := http.NewRequestWithContext(c, "GET", "http://localhost:8080", nil)
resp, err := http.DefaultClient.Do(req)
}
请注意,为了能够使用您覆盖的默认 RoundTripper
而无需额外初始化,您应该使用 http.DefaultClient
.
你可以使用这个:
https://github.com/sumit-tembe/gin-requestid
package main
import (
"net/http"
"github.com/gin-gonic/gin"
requestid "github.com/sumit-tembe/gin-requestid"
)
func main() {
// without any middlewares
router := gin.New()
// Middlewares
{
//recovery middleware
router.Use(gin.Recovery())
//middleware which injects a 'RequestID' into the context and header of each request.
router.Use(requestid.RequestID(nil))
//middleware which enhance Gin request logger to include 'RequestID'
router.Use(gin.LoggerWithConfig(requestid.GetLoggerConfig(nil, nil, nil)))
}
router.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello world!")
})
router.Run(":8080")
}
输出:
[GIN-debug] 2019-12-16T18:50:49+05:30 [bzQg6wTpL4cdZ9bM] - "GET /"
[GIN-debug] 2019-12-16T18:50:49+05:30 [bzQg6wTpL4cdZ9bM] - [::1] "GET / HTTP/1.1 200 22.415µs" Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
还支持自定义请求id生成器,您可以根据需要设计。