-
Notifications
You must be signed in to change notification settings - Fork 107
Logger middleware: support gzipped responses from graphite-web #1693
Conversation
This sounds like the order the gziper and log middlerware handlers are being loaded is wrong. the gzipper middleware sets the content-type header only when w.WriteHeader(int) is called. Then when w.Write([]byte) the data is compressed and passed through to the underlying responseWriter. So it sounds like the following is happening. Gzip middlerware wraps ResponseWriter as gzipResponseWriter When the response results in an error:
to fix this, i think that Line 21 in 43d0f40
This will ensure that logs are last thin to happen before response data is pushed to the client. |
might make more sense to make awoods the reviewer of this. I'm gonna be OOO for about a week and he knows this code much better. |
see my comments in PR #1694 #1694 (review) |
api/middleware/logger.go
Outdated
func (rw *LoggingResponseWriter) Write(b []byte) (int, error) { | ||
if rw.ResponseWriter.Status() >= 400 { | ||
rw.errBody = make([]byte, len(b)) | ||
copy(rw.errBody, b) | ||
if rw.Header().Get("Content-Encoding") == "gzip" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldnt be done here. We should just store the compressed payload in rw.errBody and do the decompress when we write the log message.
The client wont get their response until we call the rw.responseWriter(b), so we should call it as soon as possible.
The log message is not written until after the request has been completely sent to the client, so spending the time to decompress the data their wont impact request latency (yes, failed responses are likely only going to have small payloads and so it probably wont matter where we do the decompress)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in ff4b0e5
api/middleware/logger.go
Outdated
// This happens when the gziper middleware is enabled because it | ||
// sets the 'Content-Encoding' header to 'gzip' before it actually | ||
// compresses the body. | ||
rw.errBody = make([]byte, len(b)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rw.Write(b)
might be called multiple times. This will result in the errBody only containing the end of the data.
If response resulted in an Error and the payload is really large, we probably dont want all of it, but we will want the first part of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed with ff4b0e5 by buffering up all the entire response. For now I think we can store it all. Having it all might be necessary in the gzip
case.
Reordered the middlewares to follow your advice in 8356c6f. |
I think we should make a few more improvements here, though they are slightly out of scope.
to address these i suggest
|
Fair points. Can I do that in a separate PR? |
Yes |
graphite-web can (and in my testing does) gzip compress its responses. When it errors and we want to log that error, metrictank needs to unzip the body.
Caveat: detecting gzip compression is done based on the
Content-Encoding
header which sometimes is set too early by thegziper
middleware before compression has happened. We handle that case by logging the uncompressed error when decompression fails.