没有http.Request的访问上下文

没有http.Request的访问上下文

问题描述:

I am setting X-Request-Id in context within a middleware (as shown below) so that I can use it where there is *http.Request struct - e.g. req.Context().Value(middleware.ReqIdKey). However, there are some places in my codebase where there is no way for me to access *http.Request struct hence reason I am not able to use context to fetch X-Request-Id. Is there a way in Go or am I trying to do something fundamentally wrong?

internal/middleware/requestid.go

This is middleware where I set the X-Request-Id in context. Currently called as http.ListenAndServe(":8080", middleware.RequestId(SomeHandler)) in my "server" package.

package middleware

import (
    "context"
    "github.com/google/uuid"
    "net/http"
)

type val string
const ReqIdKey val = "X-Request-Id"

func RequestId(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
        val := req.Header.Get("X-Request-Id")
        if val == "" {
            val = uuid.New().String()
        }

        ctx1 := context.WithValue(req.Context(), ReqIdKey, val)
        ctx2 := req.WithContext(ctx1)

        handler.ServeHTTP(res, ctx2)
    })
}

internal/logger/logger.go

This is another package where I need to access context or just X-Request-Id value. By the way, calling logger.Config takes place before starting the server.

package logger

import (
    "github.com/sirupsen/logrus"
    "os"
)

var Log *logrus.Entry

func Config() {
    logrus.SetLevel(logrus.InfoLevel)
    logrus.SetOutput(os.Stdout)
    logrus.SetFormatter(&logrus.JSONFormatter{})

    Log = logrus.WithFields(logrus.Fields{
        "request_id": ..., // I need X-Request-Id value to go here so that all logs have it
    })
}

First, you should pass your context everywhere it's needed. If you need that in your Config() function, pass it there:

func Config(ctx context.Context) {
  /* ... * /
}

But you probably call Config() once, on startup, not per request, which leads to my second point:

You should not be passing context, or request-scoped data in general, to a config function. This is completely backwards.

Rather, you should be passing your logger into your handler/middleware, and let it log with the request data:

func handleSomePath(logger *logrus.Entry) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        /* do something */
        logger.WithFields(logrus.Fields{
            "request_id": /* ... */
        })
    }
}

If you have a http.Request you can access the Context and Values in there. If you do not have a Request but need the Context: Get the Context and pass it down your calltree as an explicit parameter (by convention its the first parameter).

(There is no magic in Go and any thing not passed into a function, either directly or indirectly simply is not there.)