Skip to content

feat: allow to customize log message#74

Open
sad-fat-cat wants to merge 1 commit intogo-chi:masterfrom
sad-fat-cat:feature/custom-log-message
Open

feat: allow to customize log message#74
sad-fat-cat wants to merge 1 commit intogo-chi:masterfrom
sad-fat-cat:feature/custom-log-message

Conversation

@sad-fat-cat
Copy link

New option LogFormat, that lets you control the format of the log messages

Copy link
Contributor

@VojtechVitek VojtechVitek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

},

LogFormat: func(r *http.Request, statusCode int, d time.Duration) string {
return fmt.Sprintf("%s %s => HTTP %v", r.Method, r.URL, statusCode)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of curiosity, what's the format you're thinking of for yourself?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@sad-fat-cat
Copy link
Author

Anything news? Can we merge this?

Copy link
Collaborator

@david-littlefarmer david-littlefarmer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +38 to +41
logFormat := o.LogFormat
if logFormat == nil {
logFormat = defaultLogFormat
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you see people needing more than statusCode and duration in the future? Should we pass the arguments via an extendable struct?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
}
Suggested change
LogFormat func(*http.Request, int, time.Duration) string
LogFormat func(req *http.Request, parameters *LogFormatParameters) string

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this @david-littlefarmer. Perhaps we can call it "attrs"?

How feasible would it be to pass the raw logAttrs?

Suggested change
LogFormat func(*http.Request, int, time.Duration) string
LogFormat func(req *http.Request, attrs []slog.Attr) string

Would that be developer friendly?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this would be the best approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants