使用logger和context的正确方式是什么?

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

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))

huangapple
  • 本文由 发表于 2016年11月29日 10:03:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/40856637.html
匿名

发表评论

匿名网友

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

确定