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.Request
给loggingHandlerFunc
? 那就是loggingHandler
所做的。 loggingHandler
嵌入了logger
和loggingHandlerFunc
,并定义了一个熟悉的方法:
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 *loggingHandler
s 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 loggingHandlerFunc
s 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.HandlerFunc
s 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.HandlerFunc
s 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
从何而来,只是它作为参数传递。 它确保我们的包装不仅与外界松散耦合,而且也与内部松散耦合。
荣誉(?)提及 (An Honourable(?) Mention)
There’s one school of thought that suggests you could stash error events in the Value
s of a handlerFunc
's request Context
. When your http.HandlerFunc
s 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 Context
的Value
。 当您的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.
第一个争论是Values
是interface{}: 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 Cheney和Peter Bourgon对我的帮助。
翻译自: https://medium.com/better-programming/how-to-inject-a-logger-into-gos-http-handlers-34481a4f2aad