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

Add support for chained errors #185

Merged
merged 1 commit into from
Apr 16, 2020

Conversation

rhcarvalho
Copy link
Contributor

@rhcarvalho rhcarvalho commented Mar 23, 2020

Instead of reporting only the top parent of an error chain, report all errors that are part of the chain.

Supports chains of errors using the Cause method (used by popular error packages predating Go 1.13).

Support for errors using the Unwrap method (introduced in Go 1.13) or a combination of Unwrap and Cause will come briefly in a follow up.

Fixes #176.


Example report of chained errors

image

Comment on lines -62 to -67
func TestCaptureExceptionShouldSendEventWithProvidedError(t *testing.T) {
client, scope, transport := setupClientTest()
client.CaptureException(errors.New("custom error"), nil, scope)
assertEqual(t, transport.lastEvent.Exception[0].Type, "*errors.errorString")
assertEqual(t, transport.lastEvent.Exception[0].Value, "custom error")
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This got replaced by the test SimpleError below.

Comment on lines -69 to -93
func TestCaptureExceptionShouldNotFailWhenPassedNil(t *testing.T) {
client, scope, transport := setupClientTest()
client.CaptureException(nil, nil, scope)
want := &Event{
Level: "error",
Platform: "go",
Exception: []Exception{
{
Type: "sentry.usageError",
Value: "CaptureException called with nil error",
},
},
}
opts := cmp.Options{
cmpopts.IgnoreFields(Event{}, "EventID", "Sdk", "ServerName", "Timestamp"),
cmpopts.IgnoreTypes(&Stacktrace{}),
cmpopts.EquateEmpty(),
}
if diff := cmp.Diff(want, transport.lastEvent, opts); diff != "" {
t.Errorf("event mismatch (-want +got):\n%s", diff)
}
if transport.lastEvent.Exception[0].Stacktrace == nil {
t.Errorf("missing stacktrace")
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This got replaced by the test NilError below.

Comment on lines -101 to -108
func TestCaptureExceptionShouldExtractCorrectTypeAndValueForWrappedErrors(t *testing.T) {
client, scope, transport := setupClientTest()
cause := &customErr{}
err := pkgErrors.WithStack(cause)
client.CaptureException(err, nil, scope)
assertEqual(t, transport.lastEvent.Exception[0].Type, "*sentry.customErr")
assertEqual(t, transport.lastEvent.Exception[0].Value, "wat")
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This got replaced by the test MostRecentErrorHasStack below.

Comment on lines -120 to -125
func TestCaptureExceptionShouldNotUseCauseIfCauseIsNil(t *testing.T) {
client, scope, transport := setupClientTest()
err := &customErrWithCause{cause: nil}
client.CaptureException(err, nil, scope)
assertEqual(t, transport.lastEvent.Exception[0].Type, "*sentry.customErrWithCause")
assertEqual(t, transport.lastEvent.Exception[0].Value, "err")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This got replaced with the test ChainWithNilCause below.

Comment on lines -128 to -133
func TestCaptureExceptionShouldUseCauseIfCauseIsNotNil(t *testing.T) {
client, scope, transport := setupClientTest()
err := &customErrWithCause{cause: &customErr{}}
client.CaptureException(err, nil, scope)
assertEqual(t, transport.lastEvent.Exception[0].Type, "*sentry.customErr")
assertEqual(t, transport.lastEvent.Exception[0].Value, "wat")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This got replaced with the test ChainWithoutStacktrace below.

Comment on lines +191 to +232
func TestCaptureEvent(t *testing.T) {
client, _, transport := setupClientTest()

eventID := EventID("0123456789abcdef")
timestamp := time.Now().UTC()
serverName := "testServer"

client.CaptureEvent(&Event{
EventID: eventID,
Timestamp: timestamp,
ServerName: serverName,
}, nil, nil)

if transport.lastEvent == nil {
t.Fatal("missing event")
}
want := &Event{
EventID: eventID,
Timestamp: timestamp,
ServerName: serverName,
Level: LevelInfo,
Platform: "go",
Sdk: SdkInfo{
Name: "sentry.go",
Version: Version,
Integrations: []string{},
Packages: []SdkPackage{
{
// FIXME: name format doesn't follow spec in
// https://docs.sentry.io/development/sdk-dev/event-payloads/sdk/
Name: "sentry-go",
Version: Version,
},
// TODO: perhaps the list of packages is incomplete or there
// should not be any package at all.
},
},
}
got := transport.lastEvent
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("Event mismatch (-want +got):\n%s", diff)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is notably not a very good test, but a starting point to add tests around code that fills in event fields.

It is so that TestCaptureException can focus on the list of "exceptions" (errors) instead of dealing with stuff like SDK info, Platform, etc.

Comment on lines +217 to +226
Packages: []SdkPackage{
{
// FIXME: name format doesn't follow spec in
// https://docs.sentry.io/development/sdk-dev/event-payloads/sdk/
Name: "sentry-go",
Version: Version,
},
// TODO: perhaps the list of packages is incomplete or there
// should not be any package at all.
},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is something we need to review in the future.

@rhcarvalho rhcarvalho marked this pull request as ready for review April 15, 2020 11:47
@rhcarvalho rhcarvalho requested a review from kamilogorek April 15, 2020 11:47
Copy link
Member

@bruno-garcia bruno-garcia left a comment

Choose a reason for hiding this comment

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

Got a lot to contribute here.

return event
}

// reverse reverses the slice a in place.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// reverse reverses the slice a in place.
// reverse reverses the slice in place.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not a typo, a is the name of the argument :)

Copy link
Member

@bruno-garcia bruno-garcia Apr 15, 2020

Choose a reason for hiding this comment

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

I have 0 to contribute.
Maybe useless questions: Is there really no built-in generic array reversal function in Go? Oh, no generic? (I mean, it's an array, forget generics)
Also, what about renaming a -> exceptions or, e?
And finally:

Suggested change
// reverse reverses the slice a in place.
// reverse reverses the slice 'a' in place.

:trollface:

return event
}

// reverse reverses the slice a in place.
func reverse(a []Exception) {
Copy link
Contributor

Choose a reason for hiding this comment

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


for _, grp := range tests {
for _, tt := range grp.tests {
tt := tt
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it for closure scoping purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Because tt is in the scope of the subtest, having a new variable for each test prevents running into https://github.com/golang/go/wiki/CommonMistakes and allows us to run tests in parallel if we wanted to.

The observed behavior in the current code base today would be the same without this line, but I like to have it to help whomever will maintain this later.

It is easy to imagine some contribution to make tests run in parallel (not a priority today because they are pretty fast anyway), the tests would be green and we would not realize that we're only running the last test case over and over...

@rhcarvalho rhcarvalho merged commit 6aa4e2b into getsentry:master Apr 16, 2020
@rhcarvalho rhcarvalho deleted the chained-errors branch April 16, 2020 12:08
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.

Make eventFromException handle recursively wrapped errors
3 participants