-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Logging with context values #1019
base: master
Are you sure you want to change the base?
Conversation
upvoting this.. logging with context is highly desirable.. if you can prioritize this .. would be awesome.. thanks |
2d76282
to
0e5b37a
Compare
0e5b37a
to
8ff7891
Compare
d66040c
to
f8ea1ae
Compare
f8ea1ae
to
fbb4d38
Compare
Why not? What is the cost of creating a new instance? To me it seems like the middleware approach is perfectly fine here. |
Hi @cideM, Is it the right approach to create a new object on every request? I'm talking about a service that receives millions of requests |
@ziyaozclk I'd start with a benchmark which would also make for a great case in this issue and show other people what impact they can expect for this. I'm not sure if the current zap README mentions the cost of sub-logger creation and if not it could also be amended with a benchmark. |
@cideM You're right. Thank you for your suggestion. I'll share the performance benchmark as soon as possible. |
@cideM Imagine you have a middleware structure like the one below. You're adding to context RequestPath value. app.Use(func(c *fiber.Ctx) error {
path := c.Path()
if len(c.Request().URI().QueryString()) > 0 {
path = c.Path() + "?" + string(c.Request().URI().QueryString())
}
c.Context().SetUserValue("RequestPath", path)
err := c.Next()
return err
}) There are two ways RequestPath value is used in each logging process. First way, var fields []zap.Field
if ctxRequestPath, ok := c.Context().Value("RequestPath").(string); ok {
fields = append(fields, zap.String("RequestPath", ctxRequestPath))
}
logger.Info("Static log message", fields...) The second way, we don't have to pull RequestPath and other values from context as above everywhere. This gives us both code readability and performance. logger.InfoCtx(c.Context(), "Static log message") Performance Benchmark
|
@ziyaozclk thanks for the explanation and benchmarks, that's really great! There's something I don't understand though. In your commit, specifically in the test, you're doing reflection inside the If I now do the traditional thing and use app.Use(func(c *fiber.Ctx) error {
path := c.Path()
if len(c.Request().URI().QueryString()) > 0 {
path = c.Path() + "?" + string(c.Request().URI().QueryString())
}
// assuming the helper functions are defined somewhere in `logging`
logger := logging.FromContext(c.Context())
logger = logger.With(zap.String("RequestPath", path))
c = logging.InjectLogger(logger)
err := c.Next()
return err
}) this should only use reflection twice, once in this middleware to get the logger from context and once in the actual request handler to get the logger from context. But when we're doing the actual logging, so Maybe I'm misreading and/or misunderstanding something here though. But I'd expect the |
@cideM You're right, I'm using reflection. But Reflection is also used in methods such as |
No global logger is involved, you get the logger that was previously injected into context, you modify that logger and re-insert it into context. Then handlers just get the modified logger from context and call logging methods. The usual thing.
The way I see it, using Compare:
One advantage of your approach is that it avoids the middleware for modifying the logger. You can configure the logger once at app start and pass it to your route handlers. No need to add the logger to context (which is quite common and for example zerolog/hlog even has helpers for it). I guess that this would make your solution faster in very specific cases where you only log once in a handler and with very few fields. Then you avoid the additional middleware cost but don't do much reflection. But that seems like a very narrow use case. TL;DR: Thanks for bearing with me so far 🙏🏻 |
Hi, I did benchmark tests for many options. I hope the following comparison is useful for you.
NOT: ZapWithContext prefix is represent PR and ZapWithTraditional prefix is represent that only moves logger instance in context. |
Instead of hardcoded
and traceable
to have that we just need to have type Entry struct {
Level Level
Time time.Time
LoggerName string
Message string
Caller EntryCaller
Stack string
Context *context.Context <-- New field
} so now I can create traceable |
Not to just be another comment, but this would be incredibly helpful. Having the context available to Core's and Hooks would greatly simply integrations like otel or sentry. Without having the context passed through, we have to resort to all kinds of nasty workarounds (e.g. Also, as can be seen with zerolog and slog, having context-enabled log methods has started to become a standard that Zap is sorely missing. |
We want to use the values such as CorrelationId, RequestPath, etc. of each incoming request in web service projects, when we trigger log logEvent anywhere in the application. Because we need these values to observe in what case the log event is thrown. For this, we do not want to use zap.WithField in the middleware and create a new logger to use values such as CorrelationId, RequestPath etc. and move them within the application with the context. This both corrects the cost of creating a new instance and we will need to do reflection in the places we will use for the logger object we carry in the context.
1-) We can add the like following method in the options file.
2-) We should add contextFunc to logger file. This function will control how the values added to the context in the middleware are transformed.
Finally, we must create methods such as Info, Error, Warn, Panic etc. so that they can take context parameters.
For Example;
Use Example;
Console Result;
It works very well. 🚀🚀
I'm waiting for a reply as soon as possible.