Skip to content
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

contrib/google.golang.org/grpc: add WithErrorCheck option #2035

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

BIwashi
Copy link

@BIwashi BIwashi commented Jun 12, 2023

What does this PR do?

This PR adds a new option called WithErrorCheck which allows users to set a custom function to determine whether an error should not be considered as an error for tracing purposes before sending it to DataDog.

Motivation

There are cases where an application produces errors, but they should not be sent to DataDog as errors. While the WithErrorCheck option allows for not treating specific gRPC status codes as errors, there are use cases where a more granular decision is needed based on the specific error, rather than just the status code.
For example, a user might want to consider a NotFound error as non-error for a specific RPC.

example

opts = []rpc.Option{
	...
	rpc.WithUnaryInterceptors(
		ddgrpc.UnaryServerInterceptor(
			ddgrpc.WithErrorCheck(func(method string, err error) bool {
				if err == nil {
					return true
				}
				errCode := status.Code(err)
				if errCode == codes.NotFound && method == "/example.ExampleService/GetInfo" {
					return true
				}
				return false
			}),
		),
	),
	...
}
...
rpcServer = rpc.NewServer(rpcHandler, opts...)

Describe how to test/QA your changes

Unit tests are included.

Reviewer's Checklist

  • Changed code has unit tests for its functionality.
  • If this interacts with the agent in a new way, a system test has been added.

@BIwashi BIwashi requested a review from a team June 12, 2023 18:11
@BIwashi BIwashi force-pushed the add-rpc-non-error-func-option branch from c39258b to 057eaab Compare June 12, 2023 18:13
@BIwashi BIwashi changed the title Add rpc non error func option contrib/google.golang.org/grpc: add NonErrorFunc option for interceptor Jun 12, 2023
@BIwashi BIwashi changed the title contrib/google.golang.org/grpc: add NonErrorFunc option for interceptor contrib/google.golang.org/grpc: add NonErrorFunc option Jun 12, 2023
Copy link
Contributor

@dianashevchenko dianashevchenko left a comment

Choose a reason for hiding this comment

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

Hi @BIwashi, thank you for the contribution! I think it wouldn't hurt to add some unit tests covering the NonErrorFunc option.

@BIwashi
Copy link
Author

BIwashi commented Jul 12, 2023

@dianashevchenko
I added unit tests for the NonErrorFunc option!
50c90b2
How does this implementation look to you? I would appreciate your feedback.

Comment on lines 133 to 141
// NonErrorFunc sets a custom function to determine whether an error should not be considered as an error for tracing purposes.
// This function is evaluated when an error occurs, and if it returns true, the error will not be recorded in the trace.
// f: A function taking the gRPC method and error as arguments, returning a boolean to indicate if the error should be ignored.
func NonErrorFunc(f func(method string, err error) bool) Option {
return func(cfg *config) {
cfg.nonErrorFunc = f
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest aligning naming with other Options in this package, for example 'WithErrorCheck' naming is widely used across other packages.

Copy link
Author

Choose a reason for hiding this comment

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

I have renamed the function name and config field names considering this comment as well. Thanks!
100083f

Comment on lines 826 to 834
var (
rig *rig
err error
)
if tt.nonErrorFunc == nil {
rig, err = newRig(true)
} else {
rig, err = newRig(true, NonErrorFunc(tt.nonErrorFunc))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
var (
rig *rig
err error
)
if tt.nonErrorFunc == nil {
rig, err = newRig(true)
} else {
rig, err = newRig(true, NonErrorFunc(tt.nonErrorFunc))
}
var opts []Option
if tt.nonErrorFunc != nil {
opts = append(opts, NonErrorFunc(tt.nonErrorFunc))
}
rig, err := newRig(true, opts...)

Copy link
Author

Choose a reason for hiding this comment

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

Thank you so much!
I fixed it.
6a3227e

Comment on lines 723 to 731
var (
rig *rig
err error
)
if tt.nonErrorFunc == nil {
rig, err = newRig(true)
} else {
rig, err = newRig(true, NonErrorFunc(tt.nonErrorFunc))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
var (
rig *rig
err error
)
if tt.nonErrorFunc == nil {
rig, err = newRig(true)
} else {
rig, err = newRig(true, NonErrorFunc(tt.nonErrorFunc))
}
var opts []Option
if tt.nonErrorFunc != nil {
opts = append(opts, NonErrorFunc(tt.nonErrorFunc))
}
rig, err := newRig(true, opts...)

Comment on lines 772 to 773
mt := mocktracer.Start()
defer mt.Stop()
Copy link
Contributor

Choose a reason for hiding this comment

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

Please move the start of the mocktracer this into the test case run

Copy link
Author

Choose a reason for hiding this comment

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

Is this the correct revision of your point?
4fca224

Comment on lines 56 to 63
val := ctx.Value(fullMethodNameKey{})
if val == nil {
return
}
fullMethod, ok := val.(string)
if !ok {
return
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Returning early here will result in finishWithError(span, v.Error, fullMethod, h.cfg) not being called unless fullMethodNameKey is set. Is this the expected behaviour?

Suggested change
val := ctx.Value(fullMethodNameKey{})
if val == nil {
return
}
fullMethod, ok := val.(string)
if !ok {
return
}
method, _ := ctx.Value(fullMethodNameKey{}).(string)
if v, ok := rs.(*stats.End); ok {
finishWithError(span, v.Error, method, h.cfg)
}

Copy link
Author

Choose a reason for hiding this comment

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

You are correct, finishWithError needs to be called. I made a mistake. Thanks.
I fixed it.

5b83467

Comment on lines 62 to 70
val := ctx.Value(fullMethodNameKey{})
if val == nil {
return
}
fullMethod, ok := val.(string)
if !ok {
return
}
finishWithError(span, rs.Error, fullMethod, h.cfg)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, don't we want to run finishWithError regardless?

Suggested change
val := ctx.Value(fullMethodNameKey{})
if val == nil {
return
}
fullMethod, ok := val.(string)
if !ok {
return
}
finishWithError(span, rs.Error, fullMethod, h.cfg)
method, _ := ctx.Value(fullMethodNameKey{}).(string)
finishWithError(span, rs.Error, method, h.cfg)

Copy link
Author

Choose a reason for hiding this comment

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

I fixed it, too.
5b83467

Comment on lines 861 to 873
noError := true
for _, s := range spans {
if s.Tag(ext.Error) != nil {
noError = false
break
}
}

if tt.withError {
assert.False(t, noError)
} else {
assert.True(t, noError)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
noError := true
for _, s := range spans {
if s.Tag(ext.Error) != nil {
noError = false
break
}
}
if tt.withError {
assert.False(t, noError)
} else {
assert.True(t, noError)
}
for _, s := range spans {
if s.Tag(ext.Error) != nil && !tt.withError {
assert.FailNow(t, "expected no error tag on the span")
}
}

Copy link
Author

Choose a reason for hiding this comment

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

I had not thought of this idea. Thank you so much!
I fixed it.
cff830c

@ahmed-mez ahmed-mez added the apm:ecosystem contrib/* related feature requests or bugs label Aug 17, 2023
Copy link
Contributor

@ajgajg1134 ajgajg1134 left a comment

Choose a reason for hiding this comment

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

See comment on proposal here: #2043 (comment)

@katiehockman katiehockman added the stale Stuck for more than 1 month label Nov 28, 2023
@BIwashi
Copy link
Author

BIwashi commented Dec 5, 2023

I apologize for the delay in making the corrections. It would be much appreciated if you could take the time to review the revisions again. Thanks.
@dianashevchenko cc @ajgajg1134

@BIwashi BIwashi changed the title contrib/google.golang.org/grpc: add NonErrorFunc option contrib/google.golang.org/grpc: add WithErrorCheck option Dec 7, 2023
@BIwashi BIwashi changed the title contrib/google.golang.org/grpc: add WithErrorCheck option contrib/google.golang.org/grpc: add WithErrorCheck option Dec 7, 2023
@BIwashi BIwashi requested a review from a team as a code owner January 18, 2024 04:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
apm:ecosystem contrib/* related feature requests or bugs stale Stuck for more than 1 month
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants