diff --git a/v2/bugsnag.go b/v2/bugsnag.go index cc64c9b..ea670e6 100644 --- a/v2/bugsnag.go +++ b/v2/bugsnag.go @@ -253,23 +253,23 @@ 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, - NumGoroutines: 10, - MaxPendingReports: 1000, - GoroutinePooler: goroutinePoolCall, - - flushSessionsOnRepanic: true, + 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, + NumGoroutines: 10, + MaxPendingReports: 1000, + GoroutinePooler: goroutinePoolCall, + GoroutinePoolerCallback: nil, + flushSessionsOnRepanic: true, }) updateSessionConfig() } diff --git a/v2/bugsnag_test.go b/v2/bugsnag_test.go index e29324f..0318931 100644 --- a/v2/bugsnag_test.go +++ b/v2/bugsnag_test.go @@ -139,7 +139,7 @@ type testPublisher struct { sync bool } -func (tp *testPublisher) publishReport(p *payload, goroutinePool GoroutinePooler) error { +func (tp *testPublisher) publishReport(p *payload, goroutinePool GoroutinePooler, poolerCallback GoroutinePoolerCallback) error { tp.sync = p.Synchronous return nil } diff --git a/v2/configuration.go b/v2/configuration.go index adef69b..ee35767 100644 --- a/v2/configuration.go +++ b/v2/configuration.go @@ -117,9 +117,12 @@ type Configuration struct { // fatal crash. flushSessionsOnRepanic bool - // This thread pooler will be used when sending async events - // to minimize resource usage. + // This thread pooler is used when sending async events to minimize resource usage. GoroutinePooler + + // This callback is used when async pooler fails to send events. + // User can handle persistence or resending of event body provided in JSON format. + GoroutinePoolerCallback // TODO: remember to update the update() function when modifying this struct } @@ -178,6 +181,9 @@ func (config *Configuration) update(other *Configuration) *Configuration { if other.GoroutinePooler != nil { config.GoroutinePooler = other.GoroutinePooler } + if other.GoroutinePoolerCallback != nil { + config.GoroutinePoolerCallback = other.GoroutinePoolerCallback + } if other.AutoCaptureSessions != nil { config.AutoCaptureSessions = other.AutoCaptureSessions diff --git a/v2/notifier.go b/v2/notifier.go index 4ba9a4f..62ce980 100644 --- a/v2/notifier.go +++ b/v2/notifier.go @@ -70,9 +70,9 @@ func (notifier *Notifier) NotifySync(err error, sync bool, rawData ...interface{ skipFrames := 1 event, config := newEvent(append(rawData, errors.New(err, skipFrames), sync), notifier) - // Never block, start throwing away errors if we have too many. + // Never block, default behaviour is to start throwing away errors if we have too many. e := middleware.Run(event, config, func() error { - return publisher.publishReport(&payload{event, config}, notifier.Config.GoroutinePooler) + return publisher.publishReport(&payload{event, config}, notifier.Config.GoroutinePooler, notifier.Config.GoroutinePoolerCallback) }) if e != nil { diff --git a/v2/report_publisher.go b/v2/report_publisher.go index c862aa9..0276f5d 100644 --- a/v2/report_publisher.go +++ b/v2/report_publisher.go @@ -3,13 +3,20 @@ package bugsnag import "fmt" type reportPublisher interface { - publishReport(*payload, GoroutinePooler) error + publishReport(*payload, GoroutinePooler, GoroutinePoolerCallback) error } type defaultReportPublisher struct{} + +// GoroutinePooler type describes user's goroutine pooler's "Submit" function type GoroutinePooler func(eventSendFunc func()) error -func (*defaultReportPublisher) publishReport(p *payload, goroutinePool GoroutinePooler) error { +// GoroutinePoolerCallback is a function to call in case sending event failed. +// Event payload in JSON form is passed as an argument. +// By default events are dropped and not sent again. +type GoroutinePoolerCallback func(payloadJSON []byte) + +func (*defaultReportPublisher) publishReport(p *payload, goroutinePool GoroutinePooler, failCallback GoroutinePoolerCallback) error { p.logf("notifying bugsnag: %s", p.Message) if !p.notifyInReleaseStage() { return fmt.Errorf("not notifying in %s", p.ReleaseStage) @@ -25,7 +32,14 @@ func (*defaultReportPublisher) publishReport(p *payload, goroutinePool Goroutine } }) if err != nil { - return fmt.Errorf("could not notify bugsnag via async pool") + p.logf("could not notify bugsnag via async pool") + if (failCallback != nil) { + payloadJSON, errJSON := p.MarshalJSON() + if errJSON != nil { + failCallback(payloadJSON) + } + } + return err } return nil }