diff --git a/v2/breadcrumb.go b/v2/breadcrumb.go index 39f8d77..a828ead 100644 --- a/v2/breadcrumb.go +++ b/v2/breadcrumb.go @@ -1,5 +1,7 @@ package bugsnag +import "time" + type BreadcrumbType = string const ( @@ -44,37 +46,55 @@ type Breadcrumb struct { MetaData BreadcrumbMetaData } +type maximumBreadcrumbsValue interface { + isValid() bool + trimBreadcrumbs(breadcrumbs []Breadcrumb) []Breadcrumb +} + +type MaximumBreadcrumbs int + +func (length MaximumBreadcrumbs) isValid() bool { + return length >= 0 && length <= 100 +} + +func (length MaximumBreadcrumbs) trimBreadcrumbs(breadcrumbs []Breadcrumb) []Breadcrumb { + if int(length) >= 0 && len(breadcrumbs) > int(length) { + return breadcrumbs[:int(length)] + } + return breadcrumbs +} + type ( // A breadcrumb callback that returns if the breadcrumb should be added. - OnBreadcrumbCallback func(*Breadcrumb) bool + onBreadcrumbCallback func(*Breadcrumb) bool - BreadcrumbState struct { + breadcrumbState struct { // These callbacks are run in reverse order and determine if the breadcrumb should be added. - OnBreadcrumbCallbacks []OnBreadcrumbCallback + onBreadcrumbCallbacks []onBreadcrumbCallback // Currently added breadcrumbs in order from newest to oldest - Breadcrumbs []Breadcrumb + breadcrumbs []Breadcrumb } ) -// OnBreadcrumb adds a callback to be run before a breadcrumb is added. +// 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{} +func (breadcrumbs *breadcrumbState) onBreadcrumb(callback onBreadcrumbCallback) { + if breadcrumbs.onBreadcrumbCallbacks == nil { + breadcrumbs.onBreadcrumbCallbacks = []onBreadcrumbCallback{} } - breadcrumbs.OnBreadcrumbCallbacks = append(breadcrumbs.OnBreadcrumbCallbacks, callback) + 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 { +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] + for i := range breadcrumbs.onBreadcrumbCallbacks { + callback := breadcrumbs.onBreadcrumbCallbacks[len(breadcrumbs.onBreadcrumbCallbacks)-i-1] if !callback(breadcrumb) { return false } @@ -83,15 +103,65 @@ func (breadcrumbs *BreadcrumbState) runBreadcrumbCallbacks(breadcrumb *Breadcrum } // 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 { +func (breadcrumbs *breadcrumbState) leaveBreadcrumb(message string, configuration *Configuration, rawData ...interface{}) { + breadcrumb := Breadcrumb{ + Timestamp: time.Now().Format(time.RFC3339), + Name: message, + Type: BreadcrumbTypeManual, + MetaData: BreadcrumbMetaData{}, + } + for _, datum := range rawData { + switch datum := datum.(type) { + case BreadcrumbMetaData: + breadcrumb.MetaData = datum + case BreadcrumbType: + breadcrumb.Type = datum + default: + panic("Unexpected type") + } + } + if breadcrumbs.runBreadcrumbCallbacks(&breadcrumb) { - if breadcrumbs.Breadcrumbs == nil { - breadcrumbs.Breadcrumbs = []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] + breadcrumbs.breadcrumbs = append([]Breadcrumb{breadcrumb}, breadcrumbs.breadcrumbs...) + if configuration.MaximumBreadcrumbs != nil { + breadcrumbs.breadcrumbs = configuration.MaximumBreadcrumbs.trimBreadcrumbs(breadcrumbs.breadcrumbs) } } - return nil +} + +func (configuration *Configuration) breadcrumbEnabled(breadcrumbType BreadcrumbType) bool { + if configuration.EnabledBreadcrumbTypes == nil { + return true + } + for _, enabled := range configuration.EnabledBreadcrumbTypes { + if enabled == breadcrumbType { + return true + } + } + return false +} + +func (breadcrumbs *breadcrumbState) leaveBugsnagStartBreadcrumb(configuration *Configuration) { + if configuration.breadcrumbEnabled(BreadcrumbTypeState) { + breadcrumbs.leaveBreadcrumb("Bugsnag loaded", configuration, BreadcrumbTypeState) + } +} + +func (breadcrumbs *breadcrumbState) leaveEventBreadcrumb(event *Event, configuration *Configuration) { + if event == nil { + return + } + if !configuration.breadcrumbEnabled(BreadcrumbTypeError) { + return + } + metadata := BreadcrumbMetaData{ + "errorClass": event.ErrorClass, + "message": event.Message, + "unhandled": event.Unhandled, + "severity": event.Severity.String, + } + breadcrumbs.leaveBreadcrumb(event.Error.Error(), configuration, BreadcrumbTypeError, metadata) } diff --git a/v2/breadcrumb_test.go b/v2/breadcrumb_test.go index 51765bb..d4bd409 100644 --- a/v2/breadcrumb_test.go +++ b/v2/breadcrumb_test.go @@ -11,7 +11,7 @@ import ( ) func TestDefaultBreadcrumbValues(t *testing.T) { - testServer, reports, notifier := setupServer(bugsnag.Configuration{}) + testServer, reports, notifier := setupServer(bugsnag.Configuration{EnabledBreadcrumbTypes: []bugsnag.BreadcrumbType{}}) defer testServer.Close() notifier.LeaveBreadcrumb("test breadcrumb") notifier.Notify(fmt.Errorf("test error")) @@ -35,7 +35,7 @@ func TestDefaultBreadcrumbValues(t *testing.T) { } func TestCustomBreadcrumbValues(t *testing.T) { - testServer, reports, notifier := setupServer(bugsnag.Configuration{}) + testServer, reports, notifier := setupServer(bugsnag.Configuration{EnabledBreadcrumbTypes: []bugsnag.BreadcrumbType{}}) defer testServer.Close() notifier.LeaveBreadcrumb("test breadcrumb", bugsnag.BreadcrumbMetaData{"hello": "world"}, bugsnag.BreadcrumbTypeProcess) notifier.Notify(fmt.Errorf("test error")) @@ -59,9 +59,9 @@ func TestCustomBreadcrumbValues(t *testing.T) { } func TestDefaultMaxBreadcrumbs(t *testing.T) { - testServer, reports, notifier := setupServer(bugsnag.Configuration{}) + testServer, reports, notifier := setupServer(bugsnag.Configuration{EnabledBreadcrumbTypes: []bugsnag.BreadcrumbType{}}) defer testServer.Close() - defaultMaximum := 25 + defaultMaximum := 50 for i := 1; i <= defaultMaximum*2; i++ { notifier.LeaveBreadcrumb(fmt.Sprintf("breadcrumb%v", i)) @@ -81,34 +81,44 @@ func TestDefaultMaxBreadcrumbs(t *testing.T) { } func TestCustomMaxBreadcrumbs(t *testing.T) { - customMaximum := 5 - testServer, reports, notifier := setupServer(bugsnag.Configuration{MaximumBreadcrumbs: customMaximum}) - defer testServer.Close() + for _, customMaximum := range []int{-1, 0, 1, 99, 100, 101} { + testServer, reports, notifier := setupServer(bugsnag.Configuration{ + MaximumBreadcrumbs: bugsnag.MaximumBreadcrumbs(customMaximum), + EnabledBreadcrumbTypes: []bugsnag.BreadcrumbType{}, + }) + defer testServer.Close() - for i := 1; i <= customMaximum*2; i++ { - notifier.LeaveBreadcrumb(fmt.Sprintf("breadcrumb%v", i)) - } + breadcrumbsToAdd := 200 + for i := 1; i <= breadcrumbsToAdd; i++ { + notifier.LeaveBreadcrumb(fmt.Sprintf("breadcrumb%v", i)) + } - notifier.Notify(fmt.Errorf("test error")) - breadcrumbs := getBreadcrumbs(reports) + 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) + expectedBreadcrumbs := customMaximum + // The default value should be kept when the custom value is invalid + if customMaximum < 0 || customMaximum > 100 { + expectedBreadcrumbs = 50 + } + if len(breadcrumbs) != expectedBreadcrumbs { + t.Fatal("incorrect number of breadcrumbs, expected", expectedBreadcrumbs, "but found", len(breadcrumbs)) + } + for i := 0; i < expectedBreadcrumbs; i++ { + if breadcrumbs[i].Name != fmt.Sprintf("breadcrumb%v", breadcrumbsToAdd-i) { + t.Fatal("invalid breadcrumb at", i, "with custom maximum of", customMaximum) + } } } } func TestBreadcrumbCallbacksAreReversed(t *testing.T) { - testServer, reports, notifier := setupServer(bugsnag.Configuration{}) + testServer, reports, notifier := setupServer(bugsnag.Configuration{EnabledBreadcrumbTypes: []bugsnag.BreadcrumbType{}}) defer testServer.Close() callback1Called := false callback2Called := false - notifier.BreadcrumbState.OnBreadcrumb(func(breadcrumb *bugsnag.Breadcrumb) bool { + notifier.OnBreadcrumb(func(breadcrumb *bugsnag.Breadcrumb) bool { callback2Called = true if breadcrumb.Name != "breadcrumb" { t.Fatal("incorrect name") @@ -118,7 +128,7 @@ func TestBreadcrumbCallbacksAreReversed(t *testing.T) { } return true }) - notifier.BreadcrumbState.OnBreadcrumb(func(breadcrumb *bugsnag.Breadcrumb) bool { + notifier.OnBreadcrumb(func(breadcrumb *bugsnag.Breadcrumb) bool { callback1Called = true if breadcrumb.Name != "breadcrumb" { t.Fatal("incorrect name") @@ -142,15 +152,15 @@ func TestBreadcrumbCallbacksAreReversed(t *testing.T) { } func TestBreadcrumbCallbacksCanCancel(t *testing.T) { - testServer, reports, notifier := setupServer(bugsnag.Configuration{}) + testServer, reports, notifier := setupServer(bugsnag.Configuration{EnabledBreadcrumbTypes: []bugsnag.BreadcrumbType{}}) defer testServer.Close() callbackCalled := false - notifier.BreadcrumbState.OnBreadcrumb(func(breadcrumb *bugsnag.Breadcrumb) bool { + notifier.OnBreadcrumb(func(breadcrumb *bugsnag.Breadcrumb) bool { t.Fatal("Callback should be canceled") return true }) - notifier.BreadcrumbState.OnBreadcrumb(func(breadcrumb *bugsnag.Breadcrumb) bool { + notifier.OnBreadcrumb(func(breadcrumb *bugsnag.Breadcrumb) bool { callbackCalled = true return false }) @@ -168,7 +178,7 @@ func TestBreadcrumbCallbacksCanCancel(t *testing.T) { } func TestSendNoBreadcrumbs(t *testing.T) { - testServer, reports, notifier := setupServer(bugsnag.Configuration{}) + testServer, reports, notifier := setupServer(bugsnag.Configuration{EnabledBreadcrumbTypes: []bugsnag.BreadcrumbType{}}) defer testServer.Close() notifier.Notify(fmt.Errorf("test error")) if len(getBreadcrumbs(reports)) != 0 { @@ -177,7 +187,7 @@ func TestSendNoBreadcrumbs(t *testing.T) { } func TestSendOrderedBreadcrumbs(t *testing.T) { - testServer, reports, notifier := setupServer(bugsnag.Configuration{}) + testServer, reports, notifier := setupServer(bugsnag.Configuration{EnabledBreadcrumbTypes: []bugsnag.BreadcrumbType{}}) defer testServer.Close() notifier.LeaveBreadcrumb("breadcrumb1") notifier.LeaveBreadcrumb("breadcrumb2") @@ -191,9 +201,85 @@ func TestSendOrderedBreadcrumbs(t *testing.T) { } } -func TestSendCleanMetadata(t *testing.T) { +func TestBugsnagStart(t *testing.T) { + testServer, reports, notifier := setupServer(bugsnag.Configuration{EnabledBreadcrumbTypes: []bugsnag.BreadcrumbType{bugsnag.BreadcrumbTypeState}}) + defer testServer.Close() + notifier.Notify(fmt.Errorf("test error")) + breadcrumbs := getBreadcrumbs(reports) + if len(breadcrumbs) != 1 { + t.Fatal("expected 1 breadcrumb", breadcrumbs) + } + if breadcrumbs[0].Name != "Bugsnag loaded" { + t.Fatal("expected the name to be 'Bugsnag loaded' but got", breadcrumbs[0].Name) + } + if breadcrumbs[0].Type != bugsnag.BreadcrumbTypeState { + t.Fatal("expected the type to be 'state' but got", breadcrumbs[0].Type) + } + if len(breadcrumbs[0].MetaData) != 0 { + t.Fatal("expected no metadata but got", breadcrumbs[0].MetaData) + } +} + +func TestBugsnagErrorBreadcrumb(t *testing.T) { + testServer, reports, notifier := setupServer(bugsnag.Configuration{EnabledBreadcrumbTypes: []bugsnag.BreadcrumbType{bugsnag.BreadcrumbTypeError}}) + defer testServer.Close() + notifier.Notify(fmt.Errorf("test error 1")) + breadcrumbs := getBreadcrumbs(reports) + if len(breadcrumbs) != 0 { + t.Fatal("expected 0 breadcrumbs", breadcrumbs) + } + notifier.Notify(fmt.Errorf("test error 2")) + breadcrumbs = getBreadcrumbs(reports) + if len(breadcrumbs) != 1 { + t.Fatal("expected 1 breadcrumb", breadcrumbs) + } + if breadcrumbs[0].Name != "test error 1" { + t.Fatal("expected the name to be 'test error 1' but got", breadcrumbs[0].Name) + } + if breadcrumbs[0].Type != bugsnag.BreadcrumbTypeError { + t.Fatal("expected the type to be 'error' but got", breadcrumbs[0].Type) + } + if len(breadcrumbs[0].MetaData) != 4 { + t.Fatal("expected 4 pieces of metadata metadata but got", breadcrumbs[0].MetaData) + } + if breadcrumbs[0].MetaData["errorClass"] != "*errors.errorString" { + t.Fatal("expected the errorClass to be '*errors.errorString' but got", breadcrumbs[0].MetaData["errorClass"]) + } + if breadcrumbs[0].MetaData["message"] != "test error 1" { + t.Fatal("expected the message to be 'test error 1' but got", breadcrumbs[0].MetaData["message"]) + } + if breadcrumbs[0].MetaData["unhandled"] != false { + t.Fatal("expected unhandled to be false") + } + if breadcrumbs[0].MetaData["severity"] != "info" { + t.Fatal("expected the severity to be 'info' bug got", breadcrumbs[0].MetaData["severity"]) + } +} + +func TestBreadcrumbsEnabledByDefault(t *testing.T) { testServer, reports, notifier := setupServer(bugsnag.Configuration{}) defer testServer.Close() + notifier.Notify(fmt.Errorf("test error 1")) + breadcrumbs := getBreadcrumbs(reports) + if len(breadcrumbs) != 1 { + t.Fatal("expected 1 breadcrumb", breadcrumbs) + } + notifier.Notify(fmt.Errorf("test error 2")) + breadcrumbs = getBreadcrumbs(reports) + if len(breadcrumbs) != 2 { + t.Fatal("expected 2 breadcrumb", breadcrumbs) + } + if breadcrumbs[0].Name != "test error 1" { + t.Fatal("expected the name to be 'test error 1' but got", breadcrumbs[0].Name) + } + if breadcrumbs[1].Name != "Bugsnag loaded" { + t.Fatal("expected the name to be 'Bugsnag loaded' but got", breadcrumbs[1].Name) + } +} + +func TestSendCleanMetadata(t *testing.T) { + testServer, reports, notifier := setupServer(bugsnag.Configuration{EnabledBreadcrumbTypes: []bugsnag.BreadcrumbType{}}) + defer testServer.Close() type Recursive struct { Inner *Recursive } diff --git a/v2/bugsnag.go b/v2/bugsnag.go index 25fc7d0..dfe71be 100644 --- a/v2/bugsnag.go +++ b/v2/bugsnag.go @@ -26,6 +26,7 @@ const Version = "2.5.0" var panicHandlerOnce sync.Once var sessionTrackerOnce sync.Once var readEnvConfigOnce sync.Once +var startBugsnagBreadcrumbOnce sync.Once var middleware middlewareStack // Config is the configuration for the default bugsnag notifier. @@ -36,7 +37,7 @@ var sessionTrackingConfig sessions.SessionTrackingConfiguration // Bugsnag. // Deprecated: Exposed for developer sanity in testing. Modify at own risk. var DefaultSessionPublishInterval = 60 * time.Second -var defaultNotifier = Notifier{&Config, nil, BreadcrumbState{}} +var defaultNotifier = Notifier{&Config, nil, breadcrumbState{}} var sessionTracker sessions.SessionTracker // Configure Bugsnag. The only required setting is the APIKey, which can be @@ -51,6 +52,10 @@ func Configure(config Configuration) { // Only do once in case the user overrides the default panichandler, and // configures multiple times. panicHandlerOnce.Do(Config.PanicHandler) + // Trigger the busnag start breadcrumb + startBugsnagBreadcrumbOnce.Do(func() { + defaultNotifier.breadcrumbState.leaveBugsnagStartBreadcrumb(defaultNotifier.Config) + }) } // StartSession creates new context from the context.Context instance with @@ -155,12 +160,6 @@ func Recover(rawData ...interface{}) { } } -// OnBeforeNotify adds a callback to be run before a breadcrumb is added to the default notifier. -// If false is returned, the breadcrumb will be discarded. These callbacks are run in reverse order. -func OnBreadcrumb(callback func(breadcrumb *Breadcrumb) bool) { - defaultNotifier.BreadcrumbState.OnBreadcrumb(callback) -} - // OnBeforeNotify adds a callback to be run before a notification is sent to // Bugsnag. It can be used to modify the event or its MetaData. Changes made // to the configuration are local to notifying about this event. To prevent the @@ -224,6 +223,12 @@ func LeaveBreadcrumb(message string, rawData ...interface{}) { defaultNotifier.LeaveBreadcrumb(message, rawData...) } +// OnBreadcrumb adds a callback to be run before a breadcrumb is added to the default notifier. +// If false is returned, the breadcrumb will be discarded. These callbacks are run in reverse order. +func OnBreadcrumb(callback func(breadcrumb *Breadcrumb) bool) { + defaultNotifier.OnBreadcrumb(callback) +} + // checkForEmptyError checks if the given error (to be reported to Bugsnag) is // nil. If it is, then log an error message and return another error wrapping // this error message. @@ -253,19 +258,20 @@ func init() { Notify: "https://notify.bugsnag.com", Sessions: "https://sessions.bugsnag.com", }, - Hostname: device.GetHostname(), - AppType: "", - AppVersion: "", - AutoCaptureSessions: true, - ReleaseStage: "", - ParamsFilters: []string{"password", "secret", "authorization", "cookie", "access_token"}, - SourceRoot: sourceRoot, - ProjectPackages: []string{"main*"}, - NotifyReleaseStages: nil, - Logger: log.New(os.Stdout, log.Prefix(), log.Flags()), - PanicHandler: defaultPanicHandler, - Transport: http.DefaultTransport, - MaximumBreadcrumbs: 25, + Hostname: device.GetHostname(), + AppType: "", + AppVersion: "", + AutoCaptureSessions: true, + ReleaseStage: "", + ParamsFilters: []string{"password", "secret", "authorization", "cookie", "access_token"}, + SourceRoot: sourceRoot, + ProjectPackages: []string{"main*"}, + NotifyReleaseStages: nil, + Logger: log.New(os.Stdout, log.Prefix(), log.Flags()), + PanicHandler: defaultPanicHandler, + Transport: http.DefaultTransport, + MaximumBreadcrumbs: MaximumBreadcrumbs(50), + EnabledBreadcrumbTypes: nil, flushSessionsOnRepanic: true, }) diff --git a/v2/configuration.go b/v2/configuration.go index 2887e35..f34aeb7 100644 --- a/v2/configuration.go +++ b/v2/configuration.go @@ -2,6 +2,7 @@ package bugsnag import ( "context" + "fmt" "log" "net/http" "os" @@ -109,8 +110,11 @@ type Configuration struct { // and will try to send any remaining events. MainContext context.Context - // The largets number of breadcrumbs that will be stored. Defaults to 25. - MaximumBreadcrumbs int + // The largets number of breadcrumbs that will be stored. Defaults to 50. + MaximumBreadcrumbs maximumBreadcrumbsValue + + // Breacrumbs that can be automatically added. If nil then all breadcrumbs will be added. + EnabledBreadcrumbTypes []BreadcrumbType // Whether the notifier should send all sessions recorded so far to Bugsnag // when repanicking to ensure that no session information is lost in a @@ -175,8 +179,15 @@ func (config *Configuration) update(other *Configuration) *Configuration { config.MainContext = other.MainContext publisher.setMainProgramContext(other.MainContext) } - if other.MaximumBreadcrumbs != 0 { - config.MaximumBreadcrumbs = other.MaximumBreadcrumbs + if other.MaximumBreadcrumbs != nil { + if other.MaximumBreadcrumbs.isValid() { + config.MaximumBreadcrumbs = other.MaximumBreadcrumbs + } else { + fmt.Println("Invalid maximum breadcrumbs of", other.MaximumBreadcrumbs) + } + } + if other.EnabledBreadcrumbTypes != nil { + config.EnabledBreadcrumbTypes = other.EnabledBreadcrumbTypes } if other.AutoCaptureSessions != nil { diff --git a/v2/event.go b/v2/event.go index b5c3826..92003ae 100644 --- a/v2/event.go +++ b/v2/event.go @@ -182,7 +182,7 @@ func newEvent(rawData []interface{}, notifier *Notifier) (*Event, *Configuration } event.Stacktrace = generateStacktrace(err, config) - event.Breadcrumbs = notifier.BreadcrumbState.Breadcrumbs + event.Breadcrumbs = notifier.breadcrumbState.breadcrumbs for _, callback := range callbacks { callback(event) diff --git a/v2/notifier.go b/v2/notifier.go index 85f413a..72e89ea 100644 --- a/v2/notifier.go +++ b/v2/notifier.go @@ -1,8 +1,6 @@ package bugsnag import ( - "time" - "github.com/bugsnag/bugsnag-go/v2/errors" ) @@ -12,7 +10,7 @@ var publisher reportPublisher = newPublisher() type Notifier struct { Config *Configuration RawData []interface{} - BreadcrumbState BreadcrumbState + breadcrumbState breadcrumbState } // New creates a new notifier. @@ -27,11 +25,13 @@ func New(rawData ...interface{}) *Notifier { } } - return &Notifier{ + notifier := Notifier{ Config: config, RawData: rawData, - BreadcrumbState: BreadcrumbState{}, + breadcrumbState: breadcrumbState{}, } + notifier.breadcrumbState.leaveBugsnagStartBreadcrumb(notifier.Config) + return ¬ifier } // FlushSessionsOnRepanic takes a boolean that indicates whether sessions @@ -76,6 +76,7 @@ func (notifier *Notifier) NotifySync(err error, sync bool, rawData ...interface{ // Never block, start throwing away errors if we have too many. e := middleware.Run(event, config, func() error { + notifier.breadcrumbState.leaveEventBreadcrumb(event, notifier.Config) return publisher.publishReport(&payload{event, config}) }) @@ -123,23 +124,13 @@ func (notifier *Notifier) Recover(rawData ...interface{}) { // Adds a breadcrumb to the current notifier which is sent with subsequent errors. // Optionally accepts bugsnag.BreadcrumbMetaData and bugsnag.BreadcrumbType. func (notifier *Notifier) LeaveBreadcrumb(message string, rawData ...interface{}) { - breadcrumb := Breadcrumb{ - Timestamp: time.Now().Format(time.RFC3339), - Name: message, - Type: BreadcrumbTypeManual, - MetaData: BreadcrumbMetaData{}, - } - for _, datum := range rawData { - switch datum := datum.(type) { - case BreadcrumbMetaData: - breadcrumb.MetaData = datum - case BreadcrumbType: - breadcrumb.Type = datum - default: - panic("Unexpected type") - } - } - notifier.BreadcrumbState.appendBreadcrumb(breadcrumb, notifier.Config.MaximumBreadcrumbs) + notifier.breadcrumbState.leaveBreadcrumb(message, notifier.Config, rawData...) +} + +// OnBreadcrumb adds a callback to be run before a breadcrumb is added to this notifier. +// If false is returned, the breadcrumb will be discarded. These callbacks are run in reverse order. +func (notifier *Notifier) OnBreadcrumb(callback func(breadcrumb *Breadcrumb) bool) { + notifier.breadcrumbState.onBreadcrumb(callback) } func (notifier *Notifier) dontPanic() {