-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c7a0901
commit c6f1ab3
Showing
8 changed files
with
423 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package bugsnag | ||
|
||
type BreadcrumbType = string | ||
|
||
const ( | ||
// Changing screens or content being displayed, with a defined destination and optionally a previous location. | ||
BreadcrumbTypeNavigation BreadcrumbType = "navigation" | ||
// Sending and receiving requests and responses. | ||
BreadcrumbTypeRequest BreadcrumbType = "request" | ||
// Performing an intensive task or query. | ||
BreadcrumbTypeProcess BreadcrumbType = "process" | ||
// Messages and severity sent to a logging platform. | ||
BreadcrumbTypeLog BreadcrumbType = "log" | ||
// Actions performed by the user, like text input, button presses, or confirming/cancelling an alert dialog. | ||
BreadcrumbTypeUser BreadcrumbType = "user" | ||
// Changing the overall state of an app, such as closing, pausing, or being moved to the background, as well as device state changes like memory or battery warnings and network connectivity changes. | ||
BreadcrumbTypeState BreadcrumbType = "state" | ||
// An error which was reported to Bugsnag encountered in the same session. | ||
BreadcrumbTypeError BreadcrumbType = "error" | ||
// User-defined, manually added breadcrumbs. | ||
BreadcrumbTypeManual BreadcrumbType = "manual" | ||
) | ||
|
||
// Key value metadata that is displayed with the breadcrumb. | ||
type BreadcrumbMetaData map[string]interface{} | ||
|
||
// Remove any values from meta-data that have keys matching the filters, | ||
// and any that are recursive data-structures. | ||
func (meta BreadcrumbMetaData) sanitize(filters []string) interface{} { | ||
return sanitizer{ | ||
Filters: filters, | ||
Seen: make([]interface{}, 0), | ||
}.Sanitize(meta) | ||
} | ||
|
||
type Breadcrumb struct { | ||
// The time at which the event occurred, in ISO 8601 format. | ||
Timestamp string | ||
// A short summary describing the event, such as the user action taken or a new application state. | ||
Name string | ||
// A category which describes the breadcrumb. | ||
Type BreadcrumbType | ||
// Additional information about the event, as key/value pairs. | ||
MetaData BreadcrumbMetaData | ||
} | ||
|
||
type ( | ||
// A breadcrumb callback that returns if the breadcrumb should be added. | ||
OnBreadcrumbCallback func(*Breadcrumb) bool | ||
|
||
BreadcrumbState struct { | ||
// These callbacks are run in reverse order and determine if the breadcrumb should be added. | ||
OnBreadcrumbCallbacks []OnBreadcrumbCallback | ||
// Currently added breadcrumbs in order from newest to oldest | ||
Breadcrumbs []Breadcrumb | ||
} | ||
) | ||
|
||
// OnBreadcrumb adds a callback to be run before a breadcrumb is added. | ||
// If false is returned, the breadcrumb will be discarded. | ||
func (breadcrumbs *BreadcrumbState) OnBreadcrumb(callback OnBreadcrumbCallback) { | ||
if breadcrumbs.OnBreadcrumbCallbacks == nil { | ||
breadcrumbs.OnBreadcrumbCallbacks = []OnBreadcrumbCallback{} | ||
} | ||
|
||
breadcrumbs.OnBreadcrumbCallbacks = append(breadcrumbs.OnBreadcrumbCallbacks, callback) | ||
} | ||
|
||
// Runs all the OnBreadcrumb callbacks, returning true if the breadcrumb should be added. | ||
func (breadcrumbs *BreadcrumbState) runBreadcrumbCallbacks(breadcrumb *Breadcrumb) bool { | ||
if breadcrumbs.OnBreadcrumbCallbacks == nil { | ||
return true | ||
} | ||
|
||
// run in reverse order | ||
for i := range breadcrumbs.OnBreadcrumbCallbacks { | ||
callback := breadcrumbs.OnBreadcrumbCallbacks[len(breadcrumbs.OnBreadcrumbCallbacks)-i-1] | ||
if !callback(breadcrumb) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
// Add the breadcrumb onto the list of breadcrumbs, ensuring that the number of breadcrumbs remains below maximumBreadcrumbs. | ||
func (breadcrumbs *BreadcrumbState) appendBreadcrumb(breadcrumb Breadcrumb, maximumBreadcrumbs int) error { | ||
if breadcrumbs.runBreadcrumbCallbacks(&breadcrumb) { | ||
if breadcrumbs.Breadcrumbs == nil { | ||
breadcrumbs.Breadcrumbs = []Breadcrumb{} | ||
} | ||
breadcrumbs.Breadcrumbs = append([]Breadcrumb{breadcrumb}, breadcrumbs.Breadcrumbs...) | ||
if len(breadcrumbs.Breadcrumbs) > 0 && len(breadcrumbs.Breadcrumbs) > maximumBreadcrumbs { | ||
breadcrumbs.Breadcrumbs = breadcrumbs.Breadcrumbs[:len(breadcrumbs.Breadcrumbs)-1] | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
package bugsnag_test | ||
|
||
import ( | ||
"fmt" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/bitly/go-simplejson" | ||
"github.com/bugsnag/bugsnag-go/v2" | ||
"github.com/bugsnag/bugsnag-go/v2/testutil" | ||
) | ||
|
||
func TestDefaultBreadcrumbValues(t *testing.T) { | ||
testServer, reports, notifier := setupServer(bugsnag.Configuration{}) | ||
defer testServer.Close() | ||
notifier.LeaveBreadcrumb("test breadcrumb") | ||
notifier.Notify(fmt.Errorf("test error")) | ||
breadcrumbs := getBreadcrumbs(reports) | ||
|
||
if len(breadcrumbs) != 1 { | ||
t.Fatal("expected 1 breadcrumb") | ||
} | ||
if breadcrumbs[0].Name != "test breadcrumb" { | ||
t.Fatal("expected breadcrumb name") | ||
} | ||
if len(breadcrumbs[0].Timestamp) < 6 { | ||
t.Fatal("expected timestamp") | ||
} | ||
if len(breadcrumbs[0].MetaData) != 0 { | ||
t.Fatal("expected no metadata") | ||
} | ||
if breadcrumbs[0].Type != bugsnag.BreadcrumbTypeManual { | ||
t.Fatal("expected manual type") | ||
} | ||
} | ||
|
||
func TestCustomBreadcrumbValues(t *testing.T) { | ||
testServer, reports, notifier := setupServer(bugsnag.Configuration{}) | ||
defer testServer.Close() | ||
notifier.LeaveBreadcrumb("test breadcrumb", bugsnag.BreadcrumbMetaData{"hello": "world"}, bugsnag.BreadcrumbTypeProcess) | ||
notifier.Notify(fmt.Errorf("test error")) | ||
breadcrumbs := getBreadcrumbs(reports) | ||
|
||
if len(breadcrumbs) != 1 { | ||
t.Fatal("expected 1 breadcrumb") | ||
} | ||
if breadcrumbs[0].Name != "test breadcrumb" { | ||
t.Fatal("expected breadcrumb name") | ||
} | ||
if len(breadcrumbs[0].Timestamp) < 6 { | ||
t.Fatal("expected timestamp") | ||
} | ||
if len(breadcrumbs[0].MetaData) != 1 || breadcrumbs[0].MetaData["hello"] != "world" { | ||
t.Fatal("expected correct metadata") | ||
} | ||
if breadcrumbs[0].Type != bugsnag.BreadcrumbTypeProcess { | ||
t.Fatal("expected process type") | ||
} | ||
} | ||
|
||
func TestDefaultMaxBreadcrumbs(t *testing.T) { | ||
testServer, reports, notifier := setupServer(bugsnag.Configuration{}) | ||
defer testServer.Close() | ||
defaultMaximum := 25 | ||
|
||
for i := 1; i <= defaultMaximum*2; i++ { | ||
notifier.LeaveBreadcrumb(fmt.Sprintf("breadcrumb%v", i)) | ||
} | ||
|
||
notifier.Notify(fmt.Errorf("test error")) | ||
breadcrumbs := getBreadcrumbs(reports) | ||
|
||
if len(breadcrumbs) != defaultMaximum { | ||
t.Fatal("incorrect number of breadcrumbs") | ||
} | ||
for i := 0; i < defaultMaximum; i++ { | ||
if breadcrumbs[i].Name != fmt.Sprintf("breadcrumb%v", defaultMaximum*2-i) { | ||
t.Fatal("invalid breadcrumb at ", i) | ||
} | ||
} | ||
} | ||
|
||
func TestCustomMaxBreadcrumbs(t *testing.T) { | ||
customMaximum := 5 | ||
testServer, reports, notifier := setupServer(bugsnag.Configuration{MaximumBreadcrumbs: customMaximum}) | ||
defer testServer.Close() | ||
|
||
for i := 1; i <= customMaximum*2; i++ { | ||
notifier.LeaveBreadcrumb(fmt.Sprintf("breadcrumb%v", i)) | ||
} | ||
|
||
notifier.Notify(fmt.Errorf("test error")) | ||
breadcrumbs := getBreadcrumbs(reports) | ||
|
||
if len(breadcrumbs) != customMaximum { | ||
t.Fatal("incorrect number of breadcrumbs") | ||
} | ||
for i := 0; i < customMaximum; i++ { | ||
if breadcrumbs[i].Name != fmt.Sprintf("breadcrumb%v", customMaximum*2-i) { | ||
t.Fatal("invalid breadcrumb at ", i) | ||
} | ||
} | ||
} | ||
|
||
func TestBreadcrumbCallbacksAreReversed(t *testing.T) { | ||
testServer, reports, notifier := setupServer(bugsnag.Configuration{}) | ||
defer testServer.Close() | ||
|
||
callback1Called := false | ||
callback2Called := false | ||
notifier.BreadcrumbState.OnBreadcrumb(func(breadcrumb *bugsnag.Breadcrumb) bool { | ||
callback2Called = true | ||
if breadcrumb.Name != "breadcrumb" { | ||
t.Fatal("incorrect name") | ||
} | ||
if callback1Called == false { | ||
t.Fatal("callbacks should occur in reverse order") | ||
} | ||
return true | ||
}) | ||
notifier.BreadcrumbState.OnBreadcrumb(func(breadcrumb *bugsnag.Breadcrumb) bool { | ||
callback1Called = true | ||
if breadcrumb.Name != "breadcrumb" { | ||
t.Fatal("incorrect name") | ||
} | ||
if callback2Called == true { | ||
t.Fatal("callbacks should occur in reverse order") | ||
} | ||
return true | ||
}) | ||
|
||
notifier.LeaveBreadcrumb("breadcrumb") | ||
|
||
if !callback2Called { | ||
t.Fatal("breadcrumb callback not called") | ||
} | ||
|
||
notifier.Notify(fmt.Errorf("test error")) | ||
if len(getBreadcrumbs(reports)) != 1 { | ||
t.Fatal("expected one breadcrumb") | ||
} | ||
} | ||
|
||
func TestBreadcrumbCallbacksCanCancel(t *testing.T) { | ||
testServer, reports, notifier := setupServer(bugsnag.Configuration{}) | ||
defer testServer.Close() | ||
|
||
callbackCalled := false | ||
notifier.BreadcrumbState.OnBreadcrumb(func(breadcrumb *bugsnag.Breadcrumb) bool { | ||
t.Fatal("Callback should be canceled") | ||
return true | ||
}) | ||
notifier.BreadcrumbState.OnBreadcrumb(func(breadcrumb *bugsnag.Breadcrumb) bool { | ||
callbackCalled = true | ||
return false | ||
}) | ||
|
||
notifier.LeaveBreadcrumb("breadcrumb") | ||
|
||
if !callbackCalled { | ||
t.Fatal("first breadcrumb callback not called") | ||
} | ||
|
||
notifier.Notify(fmt.Errorf("test error")) | ||
if len(getBreadcrumbs(reports)) != 0 { | ||
t.Fatal("breadcrumb not canceled") | ||
} | ||
} | ||
|
||
func TestSendNoBreadcrumbs(t *testing.T) { | ||
testServer, reports, notifier := setupServer(bugsnag.Configuration{}) | ||
defer testServer.Close() | ||
notifier.Notify(fmt.Errorf("test error")) | ||
if len(getBreadcrumbs(reports)) != 0 { | ||
t.Fatal("expected no breadcrumbs") | ||
} | ||
} | ||
|
||
func TestSendOrderedBreadcrumbs(t *testing.T) { | ||
testServer, reports, notifier := setupServer(bugsnag.Configuration{}) | ||
defer testServer.Close() | ||
notifier.LeaveBreadcrumb("breadcrumb1") | ||
notifier.LeaveBreadcrumb("breadcrumb2") | ||
notifier.Notify(fmt.Errorf("test error")) | ||
breadcrumbs := getBreadcrumbs(reports) | ||
if len(breadcrumbs) != 2 { | ||
t.Fatal("expected 2 breadcrumbs", breadcrumbs) | ||
} | ||
if breadcrumbs[0].Name != "breadcrumb2" || breadcrumbs[1].Name != "breadcrumb1" { | ||
t.Fatal("expected ordered breadcrumbs", breadcrumbs) | ||
} | ||
} | ||
|
||
func TestSendCleanMetadata(t *testing.T) { | ||
testServer, reports, notifier := setupServer(bugsnag.Configuration{}) | ||
defer testServer.Close() | ||
type Recursive struct { | ||
Inner *Recursive | ||
} | ||
recursiveValue := Recursive{} | ||
recursiveValue.Inner = &recursiveValue | ||
notifier.LeaveBreadcrumb("breadcrumb2", bugsnag.BreadcrumbMetaData{"recursive": recursiveValue}) | ||
notifier.Notify(fmt.Errorf("test error")) | ||
breadcrumbs := getBreadcrumbs(reports) | ||
if len(breadcrumbs) != 1 { | ||
t.Fatal("expected 1 breadcrumb", breadcrumbs) | ||
} | ||
if breadcrumbs[0].MetaData["recursive"].(map[string]interface{})["Inner"] != "[RECURSION]" { | ||
t.Fatal("remove recursive") | ||
} | ||
} | ||
|
||
func getBreadcrumbs(reports chan []byte) []bugsnag.Breadcrumb { | ||
event, _ := simplejson.NewJson(<-reports) | ||
fmt.Println(event) | ||
firstEventJson := testutil.GetIndex(event, "events", 0) | ||
breadcrumbsJson := testutil.Get(firstEventJson, "breadcrumbs") | ||
|
||
breadcrumbs := []bugsnag.Breadcrumb{} | ||
for index := 0; index < len(breadcrumbsJson.MustArray()); index++ { | ||
breadcrumbJson := breadcrumbsJson.GetIndex(index) | ||
fmt.Println(breadcrumbJson) | ||
breadcrumbs = append(breadcrumbs, bugsnag.Breadcrumb{ | ||
Timestamp: breadcrumbJson.Get("timestamp").MustString(), | ||
Name: breadcrumbJson.Get("name").MustString(), | ||
Type: breadcrumbJson.Get("type").MustString(), | ||
MetaData: breadcrumbJson.Get("metaData").MustMap(), | ||
}) | ||
} | ||
return breadcrumbs | ||
} | ||
|
||
func setupServer(configuration bugsnag.Configuration) (*httptest.Server, chan []byte, *bugsnag.Notifier) { | ||
testServer, reports := testutil.Setup() | ||
configuration.APIKey = testutil.TestAPIKey | ||
configuration.Endpoints = bugsnag.Endpoints{Notify: testServer.URL, Sessions: testServer.URL + "/sessions"} | ||
notifier := bugsnag.New(configuration) | ||
return testServer, reports, notifier | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.