如何使用Gin上下文创建的请求ID记录HTTP客户端的请求

huangapple go评论88阅读模式
英文:

How to log HTTP client's requests with request ID that was created by Gin context

问题

思路:我想在我的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", "如何在这里获取请求ID???").
		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", "如何在这里获取请求ID???").
		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
}
英文:

Idea: I want to log incoming and outcoming requests to my Gin server with unique request ID. Also I want to log all HTTP client's requests inside my Gin's routes using the same request ID that route has.

All of that should to work under the hood using middleware.

Logging requests to my server (and responses)

To log each request to my server I wrote this middleware:

import (
	&quot;bytes&quot;
	&quot;context&quot;
	&quot;github.com/gin-contrib/requestid&quot;
	&quot;github.com/gin-gonic/gin&quot;
	&quot;github.com/rs/zerolog/log&quot;
	&quot;io/ioutil&quot;
	&quot;net/http&quot;
	&quot;time&quot;
)

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 := &amp;responseBodyWriter{body: &amp;bytes.Buffer{}, ResponseWriter: c.Writer}
		c.Writer = w

		msg := &quot;Input:&quot;
		path := c.Request.URL.Path
		raw := c.Request.URL.RawQuery
		if raw != &quot;&quot; {
			path = path + &quot;?&quot; + 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(&quot;method&quot;, c.Request.Method).
			Str(&quot;path&quot;, path).
			Str(&quot;requestId&quot;, requestid.Get(c)).
			Logger()

		if len(bodyBytes) &gt; 0 {
			inputLogger.Info().RawJSON(&quot;body&quot;, bodyBytes).Msg(msg)
		} else {
			inputLogger.Info().Msg(msg)
		}

		c.Next()

		end := time.Now()
		latency := end.Sub(start)

		msg = &quot;Output:&quot;
		outputLogger := log.With().
			Str(&quot;method&quot;, c.Request.Method).
			Str(&quot;path&quot;, path).
			Str(&quot;requestId&quot;, requestid.Get(c)).
			RawJSON(&quot;body&quot;, w.body.Bytes()).
			Int(&quot;status&quot;, c.Writer.Status()).
			Dur(&quot;latency&quot;, latency).
			Logger()

		switch {
		case c.Writer.Status() &gt;= http.StatusBadRequest &amp;&amp; c.Writer.Status() &lt; http.StatusInternalServerError:
			{
				outputLogger.Warn().Msg(msg)
			}
		case c.Writer.Status() &gt;= http.StatusInternalServerError:
			{
				outputLogger.Error().Msg(msg)
			}
		default:
			outputLogger.Info().Msg(msg)
		}

	}
}

Logging requests made inside my servers route

Here is the problem: I don't know how to pass request ID (or Gin's context), created by Gin's middleware to the RoundTrip function:

type Transport struct {
	Transport  http.RoundTripper
}

var defaultTransport = Transport{
	Transport: http.DefaultTransport,
}

func init() {
	http.DefaultTransport = &amp;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(&quot;method&quot;, req.Method).
		Str(&quot;path&quot;, req.URL.String()).
		Str(&quot;requestId&quot;, &quot;how can I get request id here???&quot;).
		Msg(&quot;Api request: &quot;)
}

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(&quot;method&quot;, resp.Request.Method).
		Str(&quot;path&quot;, resp.Request.URL.String()).
		Str(&quot;requestId&quot;, &quot;how can I get request id here???&quot;).
		RawJSON(&quot;body&quot;, bodyBytes).
		Int(&quot;status&quot;, resp.StatusCode).
		Dur(&quot;latency&quot;, time.Now().Sub(ctx.Value(ContextKeyRequestStart).(time.Time))).
		Msg(&quot;API response: &quot;)
}

func (t *Transport) transport() http.RoundTripper {
	if t.Transport != nil {
		return t.Transport
	}

	return http.DefaultTransport
}

答案1

得分: 3

Transport.RoundTrip函数接受一个*http.Request参数,所以你可以通过在处理程序中创建一个请求来传递Gin上下文:

func MyHandler(c *gin.Context) {
    // 将上下文传递给请求
    req := http.NewRequestWithContext(c, "GET", "http://localhost:8080", nil)
    resp, err := http.DefaultClient.Do(req)
}

请注意,为了能够使用你覆盖的默认RoundTripper而无需额外初始化,你应该使用http.DefaultClient

英文:

The Transport.RoundTrip function takes a *http.Request parameter, so you should be able to pass the Gin context by just creating a request in your handlers with it:

func MyHandler(c *gin.Context) {
// passing context to the request
req := http.NewRequestWithContext(c, &quot;GET&quot;, &quot;http://localhost:8080&quot;, nil)
resp, err := http.DefaultClient.Do(req)
}

Note that to be able to make use of the default RoundTripper that you overwrote without additional initialization, you should use the http.DefaultClient.

答案2

得分: 0

你可以使用这个链接: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生成器,您可以根据需要进行设计。

英文:

You can use this:
https://github.com/sumit-tembe/gin-requestid

package main
import (
&quot;net/http&quot;
&quot;github.com/gin-gonic/gin&quot;
requestid &quot;github.com/sumit-tembe/gin-requestid&quot;
)
func main() {
// without any middlewares
router := gin.New()
// Middlewares
{
//recovery middleware
router.Use(gin.Recovery())
//middleware which injects a &#39;RequestID&#39; into the context and header of each request.
router.Use(requestid.RequestID(nil))
//middleware which enhance Gin request logger to include &#39;RequestID&#39;
router.Use(gin.LoggerWithConfig(requestid.GetLoggerConfig(nil, nil, nil)))
}
router.GET(&quot;/&quot;, func(c *gin.Context) {
c.String(http.StatusOK, &quot;Hello world!&quot;)
})
router.Run(&quot;:8080&quot;)
}

Output:

[GIN-debug] 2019-12-16T18:50:49+05:30 [bzQg6wTpL4cdZ9bM] - &quot;GET /&quot;
[GIN-debug] 2019-12-16T18:50:49+05:30 [bzQg6wTpL4cdZ9bM] - [::1] &quot;GET / HTTP/1.1 200 22.415&#181;s&quot; Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36

It also supports custom request id generator which you can design according to need.

huangapple
  • 本文由 发表于 2021年6月18日 15:37:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/68031175.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定