feat: allow to customize log message#74
Conversation
| }, | ||
|
|
||
| LogFormat: func(r *http.Request, statusCode int, d time.Duration) string { | ||
| return fmt.Sprintf("%s %s => HTTP %v", r.Method, r.URL, statusCode) |
There was a problem hiding this comment.
out of curiosity, what's the format you're thinking of for yourself?
There was a problem hiding this comment.
Just constant string, like "request processed".
Because all info about request already stored in slog attrs, and variance part in message field sort of difficult to work with aggregations (for example, % of uniq clients were affected by a particular error in this operation).
I thought about just putting the template string in the params, but the function seems more flexible, in general.
|
Anything news? Can we merge this? |
david-littlefarmer
left a comment
There was a problem hiding this comment.
Hi, thanks for PR, this change makes sense to me.
I have just one comment, so we have same pattern for options in whole middleware.
| logFormat := o.LogFormat | ||
| if logFormat == nil { | ||
| logFormat = defaultLogFormat | ||
| } |
There was a problem hiding this comment.
| logFormat := o.LogFormat | |
| if logFormat == nil { | |
| logFormat = defaultLogFormat | |
| } | |
| if o.LogFormat == nil { | |
| o.LogFormat = defaultLogFormat | |
| } |
| } | ||
|
|
||
| msg := fmt.Sprintf("%s %s => HTTP %v (%v)", r.Method, r.URL, statusCode, duration) | ||
| msg := logFormat(r, statusCode, duration) |
There was a problem hiding this comment.
| msg := logFormat(r, statusCode, duration) | |
| msg := o.LogFormat(r, statusCode, duration) |
|
|
||
| // LogFormat is a optional function that lets you control the format of the log message | ||
| // If not provided, default format will be used | ||
| LogFormat func(*http.Request, int, time.Duration) string |
There was a problem hiding this comment.
Do you see people needing more than statusCode and duration in the future? Should we pass the arguments via an extendable struct?
There was a problem hiding this comment.
Yes, let's do it via struct, so we don't make breaking changes in future.
Also add parameter names in signature of function please.
type LogFormatParameters struct {
StatusCode int
Duration time.Duration
}| LogFormat func(*http.Request, int, time.Duration) string | |
| LogFormat func(req *http.Request, parameters *LogFormatParameters) string |
There was a problem hiding this comment.
I like this @david-littlefarmer. Perhaps we can call it "attrs"?
How feasible would it be to pass the raw logAttrs?
| LogFormat func(*http.Request, int, time.Duration) string | |
| LogFormat func(req *http.Request, attrs []slog.Attr) string |
Would that be developer friendly?
There was a problem hiding this comment.
We are already populating attributes of the log. This should just build the message for log. So it don't makes sense to me to pass same parameter again to message.
Also every log we would have to iterate through slice of attributes and find the wanted one.
But the flexibility is nice, no argues against it.
So what about this? We can pass the most wanted parameters directly, so they can be used without any unnecessary iterations and also provide the slice of attributes.
type LogFormatParameters struct {
StatusCode int
Duration time.Duration
Attributes []slog.Attr
}There was a problem hiding this comment.
I think I'm fine with this.
But remember that we're in the hotpath and every extra attr copy counts (N * number of HTTP requests).
So better if we can avoid the extra copying just to alter the format.
There was a problem hiding this comment.
And as I mentioned the above, I realized it'd be probably better to print the default message without calling the defaultLogFormatter function. IDK if Go can inline a function that is dynamic, I guess not? Meaning we'd copy the attrs on function stack for no reason.
var msg string
if opts.LogFormat != nil {
msg = logFormat(r, LogFormatArgs{...})
} else {
msg = fmt.Sprintf("%s %s => HTTP %v (%v)", r.Method, r.URL, statusCode, duration)
}How bad would it be for users to go though []log.Attrs? It should be a straight forward loop; with no allocations, right?
TL;DR: I'm trying to avoid extra allocations per each request.
There was a problem hiding this comment.
I agree that this would be the best approach.
New option
LogFormat, that lets you control the format of the log messages