-
Notifications
You must be signed in to change notification settings - Fork 68
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
httpgrpc: correct handling of non-loggable errors #421
Conversation
f09f3b4
to
7057a5d
Compare
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.
looks good overall, left some minor comments.
httpgrpc/server/server.go
Outdated
@@ -234,3 +239,8 @@ func fromHeader(hs http.Header) []*httpgrpc.Header { | |||
} | |||
return result | |||
} | |||
|
|||
func containsDoNotLogErrorKey(hs http.Header) bool { |
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 is too shallow. I understand it's that way for testing, but this functionality can be tested together with the rest of server handler too.
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.
I have removed this method, as well as the corresponding test.
httpgrpc/server/server_test.go
Outdated
func TestContainsDoNotLogErrorKey(t *testing.T) { | ||
testCases := map[string]struct { | ||
header http.Header | ||
expectedOutcome bool | ||
}{ | ||
"if headers do not contain X-DoNotLogError, return false": { | ||
header: http.Header{ | ||
"X-First": []string{"a", "b", "c"}, | ||
"X-Second": []string{"1", "2"}, | ||
}, | ||
expectedOutcome: false, | ||
}, | ||
"if headers contain X-DoNotLogError with a value, return true": { | ||
header: http.Header{ | ||
"X-First": []string{"a", "b", "c"}, | ||
"X-DoNotLogError": []string{"true"}, | ||
}, | ||
expectedOutcome: true, | ||
}, | ||
"if headers contain X-DoNotLogError without a value, return true": { | ||
header: http.Header{ | ||
"X-First": []string{"a", "b", "c"}, | ||
"X-DoNotLogError": nil, | ||
}, | ||
expectedOutcome: true, | ||
}, | ||
} | ||
for testName, testData := range testCases { | ||
t.Run(testName, func(t *testing.T) { | ||
require.Equal(t, testData.expectedOutcome, containsDoNotLogErrorKey(testData.header)) | ||
}) | ||
} | ||
} |
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.
Honestly, this test seems bit too much for testing what's essentially a simple map key-check. I'd suggest removing this test, and including checks for DoNotLog error into already existing tests that handle 500 errors.
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.
I have applied the required fix.
httpgrpc/server/server.go
Outdated
return nil, httpgrpc.ErrorFromHTTPResponse(resp) | ||
err := httpgrpc.ErrorFromHTTPResponse(resp) | ||
if containsDoNotLogErrorKey(header) { | ||
return nil, middleware.DoNotLogError{Err: err} |
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.
return nil, middleware.DoNotLogError{Err: err} | |
err = middleware.DoNotLogError{Err: err} |
middleware/grpc_logging.go
Outdated
var ( | ||
DoNotLogErrorHeader = http.CanonicalHeaderKey("X-DoNotLogError") | ||
) |
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.
I think this belongs to httpgrpc package, where server is using it. grpc_logging.go
has nothing to do with HTTP.
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.
I have moved this header to httpgrpc/server.go
.
// If the given error, or any error from its tree are a status.Status, | ||
// that status.Status and the outcome true are returned. | ||
// Otherwise, nil and the outcome false are returned. | ||
// This implementation differs from status.FromError() because the |
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.
It does check that. The implementation is nearly the same as this method:
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.
@56quarters you linked the grpc’s status, and not gogo’s status.
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.
OK, but that's the one we should be using? That's the one that the circuit breakers in Mimir use to extract gRPC error codes. The gogo behavior sounds like something we absolutely should avoid.
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.
Hi @56quarters,
Curently gogo is used both in dskit
and in mimir
. It is used even for the generation of Go structs out of .proto
sources (e.g., here).
I agree that we should replace usages of gogo's status
package with grpc's status
package, but this should be done in a separate PR, because that change would be very big.
WDYT?
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.
Update: apparently we already have an issue for removing the deprecated gogo
dependency. But this would be a breaking change, plus we don't know which package to use instead. The grpc
package has some big performance issues, that's why the gogo
project was created.
That's the one that the circuit breakers in Mimir use to extract gRPC error codes
This is not a problem, since gRPC errors created by both grpc
and gogo
can be recognized by each other.
Signed-off-by: Yuri Nikolic <[email protected]>
Signed-off-by: Yuri Nikolic <[email protected]>
b2488cc
to
00bb08e
Compare
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.
lgtm overall, but would like to see a better test for httpgrpc.Server.
Signed-off-by: Yuri Nikolic <[email protected]>
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.
Thank you.
httpgrpc/server/server.go
Outdated
return nil, httpgrpc.ErrorFromHTTPResponse(resp) | ||
err := httpgrpc.ErrorFromHTTPResponse(resp) | ||
if _, ok := header[DoNotLogErrorHeaderKey]; ok { | ||
recorder.Header().Del(DoNotLogErrorHeaderKey) |
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.
We don't need to remove header here, only headers in resp
are important, and fromHeader
is covering that already.
Signed-off-by: Yuri Nikolic <[email protected]>
* httpgrpc: correct handling of non-loggable errors Signed-off-by: Yuri Nikolic <[email protected]> * Fixing review findings Signed-off-by: Yuri Nikolic <[email protected]> * Fixing review findings 2 Signed-off-by: Yuri Nikolic <[email protected]> * Fixing review findings 3 Signed-off-by: Yuri Nikolic <[email protected]> --------- Signed-off-by: Yuri Nikolic <[email protected]>
What this PR does:
This PR allows correct handling of non-loggable errors.
First of all, it introduces
middleware.DoNotLogError
type, representing the errors that should not be logged. Additionally, it introduceshttpgrpc.DoNotLogErrorHeaderKey
, an HTTP header key with valueX-DoNotLogError
. In case of an erroneousHTTPResponse
, the presence of this header means that the corresponding error should not be logged.When
httpgrpc
'sServer.Handle()
receives a response from the HTTP server, anHTTPResponse
object is created.HTTPResponse
s with a status5xx
are translated into gRPC errors, by using thehttpgrpc
package. Before this PR, all these errors were loggable. This PR allowsServer.Handle()
to return errors that should not be logged. More precisely, the presence of a header with keyhttpgrpc.DoNotLogHeader
and any value in an erroneousHTTPResponse
indicates toServier.Handle()
to wrap the gRPC error (created by thehttpgrpc
package) with ahttpgrpc.DoNotLogError
.One more change introduced by this PR concerns
httpgrpc.HTTPResponseFromError()
. Namely, before this PR, errors built byhttpgrpc
package and wrapped with another error, were not recognized byhttpgrpc.HTTPResponseFromError()
. This PR fixes that behavior.Which issue(s) this PR fixes:
Part of grafana/mimir#6008.
Checklist
CHANGELOG.md
updated - the order of entries should be[CHANGE]
,[FEATURE]
,[ENHANCEMENT]
,[BUGFIX]