英文:
What's the proper way to use logger along with context?
问题
我正在使用Golang构建一个Web服务器。你知道Golang有一个context
包,并且官方推荐始终将上下文作为第一个参数传递。context.Context
有一个Value
方法,可以在上下文中保存变量。
然而,大多数日志库也提供了一种继承日志记录器的方式,例如,使用父记录器的字段创建子记录器。在logrus(或其他日志记录器,不重要)中,你可以创建一个logrus.Entry
并应用WithFields
来获取一个子记录器。
例如,当每个HTTP请求到达时,会附加一个request-id
。我希望它被放入上下文中,并且在每个日志中作为一个字段进行记录。
那么,如何以正确的方式实现这一点呢?
谢谢!
英文:
I am building a web server in Golang. You know Golang has an context
package and it's offically recommended to always pass a context as first argument. context.Context
has a Value
method to keep variables under a context.
However, most logging libraries also privide a way to inherit logger, say, creating child loggers with fields from its parent. In logrus (or some other logger, doesn't matter), you may create an logrus.Entry
and apply WithFields
to get a child logger.
For example, when each HTTP request coming an request-id
is attached. I hope it being put in context, and also logged as a field in every log.
So, how to do this in a proper way?
THANKS!!
答案1
得分: 1
你可以使用HTTP中间件在上下文中设置和获取requestID。
package requestid
import (
"context"
"net/http"
)
type requestIDKey struct{}
func ContextWithRequestID(ctx context.Context, requestID string) context.Context {
return context.WithValue(ctx, requestIDKey{}, requestID)
}
func FromContext(ctx context.Context) string {
val := ctx.Value(requestIDKey{})
if val == nil {
return ""
}
return val.(string)
}
func HTTPMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
}
ctx := ContextWithRequestID(r.Context(), requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
然后使用导出的包装函数创建一个全局日志记录器,该函数接收上下文作为参数(在此示例中,我使用zap,但也可以是其他任何日志记录器)。
package logger
import (
"context"
"go.uber.org/zap"
)
var globalLogger *zap.SugaredLogger
func init() {
globalLogger = zap.NewExample().Sugar()
}
func loggerWithRequestID(ctx context.Context) *zap.SugaredLogger {
return globalLogger.With("request_id", requestid.FromContext(ctx))
}
func Errorf(ctx context.Context, template string, args ...interface{}) {
loggerWithRequestID(ctx).Errorf(template, args...)
}
//Warn,Warnf...
这样可以在代码的任何地方使用它,例如:
logger.Error(ctx, "遇到错误")
英文:
You can set and get requestID in context using http middleware
package requestid
type requestIDKey struct{}
func ContextWithRequestID(ctx context.Context, requestID string) context.Context {
return context.WithValue(ctx, requestIDKey{}, requestID)
}
func FromContext(ctx context.Context) string {
val := ctx.Value(requestIDKey{})
if val == nil {
return ""
}
return val.(string)
}
func HTTPMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
}
ctx := ContextWithRequestID(r.Context(), requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
And then create a global logger with exported wrapper functions that receive context as an argument (in this example i am using zap, but it can be any other logger)
package logger
var globalLogger *zap.SugaredLogger
func init() {
globalLogger = zap.NewExample().Sugar()
}
func loggerWithRequestID(ctx context.Context) *zap.SugaredLogger {
return globalLogger.With("request_id", requestid.FromContext(ctx))
}
func Errorf(ctx context.Context, template string, args ...interface{}) {
loggerWithRequestID(ctx).Errorf(template, args...)
}
//Warn,Warnf...
So it can be used anywhere in code like this:
logger.Error(ctx, "encountered error")
答案2
得分: 0
你可以从传入的请求中获取request-id
,然后使用上下文将request-id
传递给你调用的函数。以下是一个示例:https://medium.com/@cep21/how-to-correctly-use-context-context-in-go-1-7-8f2c0fafdf39。
关于日志记录:使用标准日志库定义自己的日志记录器。
func CreateCustomLogger(requestId string) *log.Logger {
defaultLoggingFlags := log.Ldate|log.Ltime|log.LUTC|log.Lmicroseconds|log.Lshortfile
return log.New(os.Stderr, requestId + ": ", defaultLoggingFlags)
}
在你的函数中:
customLogger := CreateCustomLogger(requestId)
customLogger.Printf("Custom log message")
英文:
You can get the request-id
from the incoming request and then use the context to pass the request-id
to the functions you are calling.
Here is an example of that : https://medium.com/@cep21/how-to-correctly-use-context-context-in-go-1-7-8f2c0fafdf39.
For logging: Define your own logger using the std log library
func CreateCustomLogger(requestId string) *log.Logger {
defaultLoggingFlags :=
log.Ldate|log.Ltime|log.LUTC|log.Lmicroseconds|log.Lshortfile
return log.New(os.Stderr, requestId + ": ", defaultLoggingFlags)
}
In your function:
customLogger := CreateCustomLogger(requestId)
customLogger.Printf("Custom log message")
答案3
得分: 0
你可以创建自定义的格式化程序并进行设置。在这里查看链接。
英文:
You can create custom formatter and set it. Have a look here.
答案4
得分: 0
每个HTTP请求都有自己的上下文,我只是在中间件中将请求ID合并到上下文中,像这样:
Request = Request.WithContext(context.WithValue(Request.Context(), "request_uuid", uid))
英文:
Each Http request has it's own context, i just merge request id in it like this in middleware.
Request = Request.WithContext(context.WithValue(Request.Context(), "request_uuid", uid))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论