当前位置: 首页 > 工具软件 > gos-log > 使用案例 >

如何将记录器注入gos http处理程序

张鹏鹍
2023-12-01

Assigning loggers to package-level variables is an anti-pattern. When you declare var log = mylogger.New() in the package namespace, you create a tight compile-time dependency on the particular brand of logger you use. It makes your program brittle, hard to change.

将记录器分配给程序包级变量是一种反模式。 在包名称空间中声明var log = mylogger.New() ,将对所使用的特定记录器品牌产生紧密的编译时依赖性。 它使您的程序脆弱,难以更改。

The solution is to pass an interface that describes the capabilities of a compatible logger but ignores its identity until runtime. But what does that look like? Specifically, what does it look like when you want to pass a logger to a function whose signature is not under your control?

解决方案是传递一个描述兼容记录器功能的接口,但忽略其身份,直到运行时。 但是那是什么样子? 具体来说,当您要将记录器传递给签名不受您控制的功能时,它看起来像什么?

The http.HandlerFunc is a type where logging is essential but unintuitive:

http.HandlerFunc是日志记录必不可少但不直观的一种类型:

type HandlerFunc(ResponseWriter, *Request)

Confronted with this signature, a package-level logger that you can access from inside the HandlerFunc seems like an easy solution, but it’ll come back to bite you as your program evolves. Our mission: to inject a logging interface into something that looks and behaves like the mighty HandlerFunc.

面对此签名,您可以从HandlerFunc内部访问的程序包级记录器似乎是一个简单的解决方案,但随着程序的发展,它会再次对您造成影响。 我们的任务是:向外观和行为类似于强大的HandlerFunc注入日志记录接口。

建立自己的HandlerFactory (Build Your Own HandlerFactory)

First up, the type declarations:

首先,类型声明:

package routing


import (
    "errors"
    "net/http"


    "github.com/gorilla/mux"
)


type logger interface {
    Printf(format string, v ...interface{})
}


type loggingHandlerFunc = func(w http.ResponseWriter, r *http.Request, l logger)


type loggingHandler struct {
    logger
    handlerFunc loggingHandlerFunc
}

The interface logger defines what any logger we choose to inject must be capable of, in this case, Printf. The concrete type of the logger is unimportant; we only care about what it can do.

接口logger定义了我们选择注入的任何记录器必须具备的功能,在这种情况下,该功能是Printf 。 记录器的具体类型并不重要; 我们只关心它能做什么。

loggingHandlerFunc is the centrepiece of this solution — a type that looks a lot like http.HandlerFunc but which also accepts an argument that implements our logger interface.

loggingHandlerFunc是此解决方案的核心-一种看起来很像http.HandlerFunc的类型,但它也接受实现我们的logger接口的参数。

But how do we “forward” an incoming *http.Request to a loggingHandlerFunc? That’s what loggingHandler does. loggingHandler embeds both a logger and a loggingHandlerFunc, and defines a familiar method:

但是,我们如何将传入的*http.RequestloggingHandlerFunc ? 那就是loggingHandler所做的。 loggingHandler嵌入了loggerloggingHandlerFunc ,并定义了一个熟悉的方法:

func (lh *loggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    lh.handlerFunc(w, r, lh.logger)
}

Thus, our loggingHandler implements http.Handler, which requires the single method ServeHTTP(w http.ResponseWriter, r *http.Request). We can now pass a *loggingHandler to an http.ServeMux (or any mux package of your choice), and traffic to the route you specify will be passed through the *loggingHandler to the embedded loggingHandlerFunc, picking up a logger along the way.

因此,我们的loggingHandler实现了http.Handler ,它需要单个方法ServeHTTP(w http.ResponseWriter, r *http.Request) 。 现在,我们可以将*loggingHandler传递给http.ServeMux (或您选择的任何mux包),并且指定路由的流量将通过*loggingHandler传递给嵌入式loggingHandlerFunc ,并在沿途拾起logger

func homepageHandler(w http.ResponseWriter, r *http.Request, log logger) {
    err := errors.New("This will be logged")
    log.Printf("%-8s %s: %v", "INFO", "homepageHandler", err)
    http.Error(w, http.StatusText(http.StatusInternalServerError),
        http.StatusInternalServerError)
}

However, this approach leads to a lot of manual *loggingHandler creation, and in the likely event that you want to use the same logger across a number of routes, you’ll find yourself repeatedly constructing *loggingHandlers with similar inputs. Let’s streamline this with a factory.

但是,这种方法会导致大量手动*loggingHandler创建,并且在可能要跨多个路径使用同一logger的可能情况下,您会发现自己反复构造具有相似输入的*loggingHandler 。 让我们用工厂简化它。

func loggingHandlerFactory(l logger) func(loggingHandlerFunc) *loggingHandler {
    return func(hf loggingHandlerFunc) *loggingHandler {
        return &loggingHandler{l, hf}
    }
}


func Router(l logger) *mux.Router {
    router := mux.NewRouter()
    withLogger := loggingHandlerFactory(l)
    router.Handle("/", withLogger(homepageHandler)).Methods(http.MethodGet)
    return router
}

loggingHandlerFactory takes in a logger and returns a closure that, when called with a loggingHandlerFunc, returns a new *loggingHandler with the correct logger baked in.

loggingHandlerFactory接受一个logger并返回一个闭包,当使用loggingHandlerFunc调用该闭包时,将返回一个新的*loggingHandler其中包含正确的logger。

Using a loggingHandlerFunc in a route becomes as simple as passing it to the factory. Better yet, the loggingHandlerFuncs you write are embeddable in whatever struct you like, not just the loggingHandler defined here. This configuration is easy to change and has no knock-on effects outside of the package.

在路由中使用loggingHandlerFunc就像将其传递给工厂一样简单。 更好的是,您编写的loggingHandlerFunc可以嵌入到您喜欢的任何结构中,而不仅仅是loggingHandler定义的loggingHandler 。 此配置易于更改,在包装外部没有任何连锁效应。

That contrasts somewhat with an approach that’s more straightforward but may restrict your freedom to change:

这与更简单但可能会限制您进行更改的自由的方法形成鲜明对比:

type logger interface {
    Printf(format string, v ...interface{})
}


type loggingHandler struct {
    logger
}


func Router(l logger) *mux.Router {
    router := mux.NewRouter()
    lh := &loggingHandler{l}
    router.HandleFunc("/", lh.homepageHandler).Methods(http.MethodGet)
    return router
}


func (lh *loggingHandler) homepageHandler(w http.ResponseWriter, r *http.Request) {
    err := errors.New("This will be logged")
    lh.Printf("%-8s %s: %v", "INFO", "homepageHandler", err)
    http.Error(w, http.StatusText(http.StatusInternalServerError),
        http.StatusInternalServerError)
}

Here, the loggingHandler embeds only a logger, and plain old http.HandlerFuncs are declared as methods on *loggingHandler, giving them access to its logger field.

在这里, loggingHandler仅嵌入一个logger,而普通的旧http.HandlerFunc声明为*loggingHandler上的方法,从而使他们可以访问其logger字段。

I find this easier to intuit, but it binds your http.HandlerFuncs to a single implementation of loggingHandler. If the loggingHandler has to change, it could impact every HandlerFunc attached to it. Contrast this with the first approach, where a loggingHandlerFunc doesn’t care where its logger comes from, only that it’s passed as an argument. It ensures that our package is not just loosely coupled to the outside world, it’s loosely coupled inside too.

我觉得这是比较容易忒,但它结合你的http.HandlerFunc s到单个实现loggingHandler 。 如果loggingHandler必须更改,则可能会影响附加到它的每个HandlerFunc 。 与第一种方法,其中一个对比这loggingHandlerFunc不关心那里logger从何而来,只是它作为参数传递。 它确保我们的包装不仅与外界松散耦合,而且也与内部松散耦合。

Here’s the example in full.

这是完整的示例

荣誉(?)提及 (An Honourable(?) Mention)

There’s one school of thought that suggests you could stash error events in the Values of a handlerFunc's request Context. When your http.HandlerFuncs return, the one at the top of the chain would be responsible for getting them out again and logging a unified report about everything that happened during the request.

有一种想法建议您可以将错误事件存储在handlerFunc的request ContextValue 。 当您的http.HandlerFunc返回时,位于链顶部的那个将负责再次将其释放,并记录有关请求期间发生的所有事件的统一报告。

There’s another school that would have me hunted for suggesting it to you.

还有另一所学校会想我向您推荐它。

The first contention is that Values is an amorphous sack of interface{}: interface{} pairs that invite a wealth of lurking bugs through the type assertions required to get data out again. The second is that having this bag of formless data persist for the lifetime of the request amounts to thread-local storage, which is inimical to the Go concurrency model.

第一个争论是Valuesinterface{}: interface{}的无定形麻袋interface{}: interface{}对通过再次获取数据所需的类型断言引发大量潜伏的bug。 第二个问题是,在请求的生命周期内保留这袋无格式数据将构成线程本地存储,这对Go并发模型不利

Nevertheless, if you’re curious about how such heresy would be implemented (I know, you’re asking for a friend), Peter Bourgon’s ctxdata is the code to see.

但是,如果您对如何实现这样的异端感到好奇(我知道,您是在找一个朋友),那么可以看一下Peter Bourgon的ctxdata

The debate surrounding logging in Go is fascinating (no, really). I highly recommend catching up on it.

有关登录Go的争论很有趣(不,真的)。 我强烈建议赶上它。

Thanks go to Dave Cheney and Peter Bourgon for their help in wrapping my head around this.

感谢Dave CheneyPeter Bourgon对我的帮助。

翻译自: https://medium.com/better-programming/how-to-inject-a-logger-into-gos-http-handlers-34481a4f2aad

 类似资料: