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

feat: Support modifying events within a callback function #146

Merged
merged 2 commits into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@

* Extract stacktrace contents on errors wrapped by
[`pkg/errors`](https://github.com/pkg/errors).
* Support modifying an individual event using a callback function argument.

```go
bugsnag.Notify(err, func(event *bugsnag.Event) {
event.ErrorClass = "Unexpected Termination"
event.MetaData.Update(loadJobData())

if event.Stacktrace[0].File = "mylogger.go" {
event.Stacktrace = event.Stacktrace[1:]
}
})
```

The stack trace of an event is now mutable so frames can be removed or
modified.

### Bug fixes

Expand Down
6 changes: 3 additions & 3 deletions bugsnag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func TestNotify(t *testing.T) {
}

exception := getIndex(event, "exceptions", 0)
verifyExistsInStackTrace(t, exception, &stackFrame{File: "bugsnag_test.go", Method: "TestNotify", LineNumber: 98, InProject: true})
verifyExistsInStackTrace(t, exception, &StackFrame{File: "bugsnag_test.go", Method: "TestNotify", LineNumber: 98, InProject: true})
}

type testPublisher struct {
Expand Down Expand Up @@ -315,7 +315,7 @@ func TestHandler(t *testing.T) {
}

exception := getIndex(event, "exceptions", 0)
verifyExistsInStackTrace(t, exception, &stackFrame{File: "bugsnag_test.go", Method: "crashyHandler", InProject: true, LineNumber: 24})
verifyExistsInStackTrace(t, exception, &StackFrame{File: "bugsnag_test.go", Method: "crashyHandler", InProject: true, LineNumber: 24})
}

func TestAutoNotify(t *testing.T) {
Expand Down Expand Up @@ -611,7 +611,7 @@ func assertValidSession(t *testing.T, event *simplejson.Json, unhandled bool) {
}
}

func verifyExistsInStackTrace(t *testing.T, exception *simplejson.Json, exp *stackFrame) {
func verifyExistsInStackTrace(t *testing.T, exception *simplejson.Json, exp *StackFrame) {
isFile := func(frame *simplejson.Json) bool { return strings.HasSuffix(getString(frame, "file"), exp.File) }
isMethod := func(frame *simplejson.Json) bool { return getString(frame, "method") == exp.Method }
isLineNumber := func(frame *simplejson.Json) bool { return getInt(frame, "lineNumber") == exp.LineNumber }
Expand Down
18 changes: 14 additions & 4 deletions event.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type severity struct {
}

// The form of stacktrace that Bugsnag expects
type stackFrame struct {
type StackFrame struct {
Method string `json:"method"`
File string `json:"file"`
LineNumber int `json:"lineNumber"`
Expand Down Expand Up @@ -85,7 +85,7 @@ type Event struct {
// The error message to be sent to Bugsnag. This defaults to the return value of Error.Error()
Message string
// The stacktrrace of the error to be sent to Bugsnag.
Stacktrace []stackFrame
Stacktrace []StackFrame

// The context to be sent to Bugsnag. This should be set to the part of the app that was running,
// e.g. for http requests, set it to the path.
Expand Down Expand Up @@ -123,6 +123,7 @@ func newEvent(rawData []interface{}, notifier *Notifier) (*Event, *Configuration
}

var err *errors.Error
var callbacks []func(*Event)

for _, datum := range event.RawData {
switch datum := datum.(type) {
Expand All @@ -135,7 +136,7 @@ func newEvent(rawData []interface{}, notifier *Notifier) (*Event, *Configuration
event.ErrorClass = err.TypeName()
}
event.Message = err.Error()
event.Stacktrace = make([]stackFrame, len(err.StackFrames()))
event.Stacktrace = make([]StackFrame, len(err.StackFrames()))

case bool:
config = config.merge(&Configuration{Synchronous: bool(datum)})
Expand Down Expand Up @@ -169,6 +170,8 @@ func newEvent(rawData []interface{}, notifier *Notifier) (*Event, *Configuration
case HandledState:
event.handledState = datum
event.Severity = datum.OriginalSeverity
case func(*Event):
callbacks = append(callbacks, datum)
}
}

Expand All @@ -184,14 +187,21 @@ func newEvent(rawData []interface{}, notifier *Notifier) (*Event, *Configuration
file = config.stripProjectPackages(file)
}

event.Stacktrace[i] = stackFrame{
event.Stacktrace[i] = StackFrame{
Method: frame.Name,
File: file,
LineNumber: frame.LineNumber,
InProject: inProject,
}
}

for _, callback := range callbacks {
callback(event)
if event.Severity != event.handledState.OriginalSeverity {
event.handledState.SeverityReason = SeverityReasonCallbackSpecified
}
}

return event, config
}

Expand Down
14 changes: 14 additions & 0 deletions features/fixtures/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ func main() {
unhandledCrash()
case "handled", "endpoint legacy", "endpoint notify", "endpoint session":
handledError()
case "handled with callback":
handledCallbackError()
case "session":
session()
case "autonotify":
Expand Down Expand Up @@ -231,3 +233,15 @@ func user() {

time.Sleep(200 * time.Millisecond)
}

func handledCallbackError() {
bugsnag.Notify(fmt.Errorf("Inadequent Prep Error"), func(event *bugsnag.Event) {
event.Context = "nonfatal.go:14"
event.Severity = bugsnag.SeverityInfo

event.Stacktrace[1].File = ">insertion<"
event.Stacktrace[1].LineNumber = 0
})
// Give some time for the error to be sent before exiting
time.Sleep(200 * time.Millisecond)
}
13 changes: 13 additions & 0 deletions features/plain_features/handled.feature
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,16 @@ Scenario: A handled error sends a report with a custom name
And the event "severityReason.type" equals "handledError"
And the exception "errorClass" equals "MyCustomErrorClass"
And the "file" of stack frame 0 equals "main.go"

Scenario: Sending an event using a callback to modify report contents
When I run the go service "app" with the test case "handled with callback"
Then I wait to receive a request
And the request is a valid error report with api key "a35a2a72bd230ac0aa0f52715bbdc6aa"
And the event "unhandled" is false
And the event "severity" equals "info"
And the event "severityReason.type" equals "userCallbackSetSeverity"
And the event "context" equals "nonfatal.go:14"
And the "file" of stack frame 0 equals "main.go"
And stack frame 0 contains a local function spanning 238 to 244
And the "file" of stack frame 1 equals ">insertion<"
And the "lineNumber" of stack frame 1 equals 0
9 changes: 9 additions & 0 deletions features/steps/go_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,12 @@
sleep 1
assert_equal(request_count, stored_requests.size, "#{stored_requests.size} requests received")
end

Then("stack frame {int} contains a local function spanning {int} to {int}") do |frame, val, old_val|
# Old versions of Go put the line number on the end of the function
if ['1.7', '1.8'].include? ENV['GO_VERSION']
step "the \"lineNumber\" of stack frame #{frame} equals #{old_val}"
else
step "the \"lineNumber\" of stack frame #{frame} equals #{val}"
end
end
38 changes: 30 additions & 8 deletions notifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,36 @@ func TestStackframesAreSkippedCorrectly(t *testing.T) {
})
}

func assertStackframeCount(t *testing.T, expCount int) {
report, _ := simplejson.NewJson(<-bugsnaggedReports)
stacktrace := GetIndex(GetIndex(report, "events", 0), "exceptions", 0).Get("stacktrace")
if s := stacktrace.MustArray(); len(s) != expCount {
t.Errorf("Expected %d stackframe(s), but there were %d stackframes", expCount, len(s))
s, _ := stacktrace.EncodePretty()
t.Errorf(string(s))
}
func TestModifyingEventsWithCallbacks(t *testing.T) {
server, eventQueue := Setup()
defer server.Close()
notifier := notifierSetup(server.URL)

bugsnag.Configure(bugsnag.Configuration{
APIKey: TestAPIKey,
Endpoints: bugsnag.Endpoints{Notify: server.URL, Sessions: server.URL + "/sessions"},
})

t.Run("bugsnag.Notify with block", func(st *testing.T) {
notifier.Notify(fmt.Errorf("bnuuy"), bugsnag.Context{String: "should be overridden"}, func(event *bugsnag.Event) {
event.Context = "known unknowns"
})
json, _ := simplejson.NewJson(<-eventQueue)
event := GetIndex(json, "events", 0)
context := event.Get("context").MustString()
exception := GetIndex(event, "exceptions", 0)
class := exception.Get("errorClass").MustString()
message := exception.Get("message").MustString()
if class != "*errors.errorString" {
st.Errorf("incorrect error class '%s'", class)
}
if message != "bnuuy" {
st.Errorf("incorrect error message '%s'", message)
}
if context != "known unknowns" {
st.Errorf("failed to change context in block. '%s'", context)
}
})
}

func assertStackframesMatch(t *testing.T, expected []errors.StackFrame) {
Expand Down
2 changes: 1 addition & 1 deletion payload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestMarshalLargePayload(t *testing.T) {
}

func makeLargePayload() *payload {
stackframes := []stackFrame{
stackframes := []StackFrame{
{Method: "doA", File: "a.go", LineNumber: 65, InProject: false},
{Method: "fetchB", File: "b.go", LineNumber: 99, InProject: true},
{Method: "incrementI", File: "i.go", LineNumber: 651, InProject: false},
Expand Down
2 changes: 1 addition & 1 deletion report.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type appJSON struct {
type exceptionJSON struct {
ErrorClass string `json:"errorClass"`
Message string `json:"message"`
Stacktrace []stackFrame `json:"stacktrace"`
Stacktrace []StackFrame `json:"stacktrace"`
}

type severityReasonJSON struct {
Expand Down