diff --git a/Source/Bugsnag/BugsnagConfiguration.h b/Source/Bugsnag/BugsnagConfiguration.h index 1c44614fb..18234f32d 100644 --- a/Source/Bugsnag/BugsnagConfiguration.h +++ b/Source/Bugsnag/BugsnagConfiguration.h @@ -28,24 +28,40 @@ #import "BugsnagMetaData.h" #import "KSCrashReportWriter.h" +/** + * A handler for modifying data before sending it to Bugsnag + * + * @param rawEventReports The raw event data written at crash time. This + * includes data added in onCrashHandler. + * @param report The default report payload + * + * @return the report payload intended to be sent or nil to cancel sending + */ +typedef NSDictionary * (^BugsnagBeforeNotifyHook)(NSArray *rawEventReports, + NSDictionary *report); @class BugsnagBreadcrumbs; @interface BugsnagConfiguration : NSObject -@property(nonatomic,readwrite,retain) NSString* apiKey; -@property(nonatomic,readwrite,retain) NSURL* notifyURL; -@property(nonatomic,readwrite,retain) NSString* releaseStage; -@property(nonatomic,readwrite,retain) NSArray* notifyReleaseStages; -@property(nonatomic,readwrite,retain) NSString* context; -@property(nonatomic,readwrite,retain) NSString* appVersion; -@property(nonatomic,readwrite,retain) BugsnagMetaData* metaData; -@property(nonatomic,readwrite,retain) BugsnagMetaData* config; -@property(nonatomic,readonly,strong) BugsnagBreadcrumbs* breadcrumbs; -@property(nonatomic) void (*onCrashHandler)(const KSCrashReportWriter* writer); +@property(nonatomic, readwrite, retain) NSString *apiKey; +@property(nonatomic, readwrite, retain) NSURL *notifyURL; +@property(nonatomic, readwrite, retain) NSString *releaseStage; +@property(nonatomic, readwrite, retain) NSArray *notifyReleaseStages; +@property(nonatomic, readwrite, retain) NSString *context; +@property(nonatomic, readwrite, retain) NSString *appVersion; +@property(nonatomic, readwrite, retain) BugsnagMetaData *metaData; +@property(nonatomic, readwrite, retain) BugsnagMetaData *config; +@property(nonatomic, readonly, strong) BugsnagBreadcrumbs *breadcrumbs; +@property(nonatomic, readonly, strong) NSArray *beforeNotifyHooks; +@property(nonatomic) void (*onCrashHandler)(const KSCrashReportWriter *writer); @property(nonatomic) BOOL autoNotify; -- (void) setUser:(NSString*) userId withName:(NSString*) name andEmail:(NSString*) email; +- (void)setUser:(NSString *)userId + withName:(NSString *)name + andEmail:(NSString *)email; + +- (void)addBeforeNotifyHook:(BugsnagBeforeNotifyHook)hook; @end diff --git a/Source/Bugsnag/BugsnagConfiguration.m b/Source/Bugsnag/BugsnagConfiguration.m index 662dc14f9..20fe66088 100644 --- a/Source/Bugsnag/BugsnagConfiguration.m +++ b/Source/Bugsnag/BugsnagConfiguration.m @@ -24,69 +24,74 @@ // THE SOFTWARE. // -#import "BugsnagConfiguration.h" #import "BugsnagBreadcrumb.h" +#import "BugsnagConfiguration.h" -@implementation BugsnagConfiguration +@interface BugsnagConfiguration () +@property(nonatomic, readwrite, strong) NSMutableArray *beforeNotifyHooks; +@end --(id)init { - if (self = [super init]) { - self.metaData = [[BugsnagMetaData alloc] init]; - self.config = [[BugsnagMetaData alloc] init]; - self.apiKey = @""; - self.autoNotify = true; - self.notifyURL = [NSURL URLWithString:@"https://notify.bugsnag.com/"]; +@implementation BugsnagConfiguration - self.notifyReleaseStages = nil; - _breadcrumbs = [BugsnagBreadcrumbs new]; +- (id)init { + if (self = [super init]) { + _metaData = [[BugsnagMetaData alloc] init]; + _config = [[BugsnagMetaData alloc] init]; + _apiKey = @""; + _autoNotify = true; + _notifyURL = [NSURL URLWithString:@"https://notify.bugsnag.com/"]; + _beforeNotifyHooks = [NSMutableArray new]; + _notifyReleaseStages = nil; + _breadcrumbs = [BugsnagBreadcrumbs new]; #if DEBUG - self.releaseStage = @"development"; + _releaseStage = @"development"; #else - self.releaseStage = @"production"; + _releaseStage = @"production"; #endif - } - return self; + } + return self; } --(void)setUser:(NSString*)userId withName: (NSString*) userName andEmail:(NSString*)userEmail -{ - [self.metaData addAttribute:@"id" withValue:userId toTabWithName:@"user"]; - [self.metaData addAttribute:@"name" withValue:userName toTabWithName:@"user"]; - [self.metaData addAttribute:@"email" withValue:userEmail toTabWithName:@"user"]; +- (void)setUser:(NSString *)userId + withName:(NSString *)userName + andEmail:(NSString *)userEmail { + [self.metaData addAttribute:@"id" withValue:userId toTabWithName:@"user"]; + [self.metaData addAttribute:@"name" withValue:userName toTabWithName:@"user"]; + [self.metaData addAttribute:@"email" + withValue:userEmail + toTabWithName:@"user"]; } -@synthesize metaData; -@synthesize config; - -@synthesize notifyURL; -@synthesize apiKey; -@synthesize autoNotify; -@synthesize releaseStage; -@synthesize notifyReleaseStages; -@synthesize context; -@synthesize appVersion; - --(void) setReleaseStage:(NSString *)newReleaseStage { - self->releaseStage = newReleaseStage; - [self.config addAttribute:@"releaseStage" withValue:newReleaseStage toTabWithName:@"config"]; +- (void)addBeforeNotifyHook:(BugsnagBeforeNotifyHook)hook { + [(NSMutableArray *)self.beforeNotifyHooks addObject:[hook copy]]; } +- (void)setReleaseStage:(NSString *)newReleaseStage { + _releaseStage = newReleaseStage; + [self.config addAttribute:@"releaseStage" + withValue:newReleaseStage + toTabWithName:@"config"]; +} - (void)setNotifyReleaseStages:(NSArray *)newNotifyReleaseStages; { - NSArray *notifyReleaseStagesCopy = [newNotifyReleaseStages copy]; - self->notifyReleaseStages = notifyReleaseStagesCopy; - [self.config addAttribute:@"notifyReleaseStages" withValue:notifyReleaseStagesCopy toTabWithName:@"config"]; + NSArray *notifyReleaseStagesCopy = [newNotifyReleaseStages copy]; + _notifyReleaseStages = notifyReleaseStagesCopy; + [self.config addAttribute:@"notifyReleaseStages" + withValue:notifyReleaseStagesCopy + toTabWithName:@"config"]; } --(void) setContext:(NSString *)newContext -{ - self->context = newContext; - [self.config addAttribute:@"context" withValue: newContext toTabWithName:@"config"]; +- (void)setContext:(NSString *)newContext { + _context = newContext; + [self.config addAttribute:@"context" + withValue:newContext + toTabWithName:@"config"]; } --(void) setAppVersion:(NSString *)newVersion -{ - self->appVersion = newVersion; - [self.config addAttribute:@"appVersion" withValue: newVersion toTabWithName:@"config"]; +- (void)setAppVersion:(NSString *)newVersion { + _appVersion = newVersion; + [self.config addAttribute:@"appVersion" + withValue:newVersion + toTabWithName:@"config"]; } @end \ No newline at end of file diff --git a/Source/Bugsnag/BugsnagSink.m b/Source/Bugsnag/BugsnagSink.m index c423bef23..f49886957 100644 --- a/Source/Bugsnag/BugsnagSink.m +++ b/Source/Bugsnag/BugsnagSink.m @@ -73,9 +73,22 @@ - (void) filterReports:(NSArray*) reports onCompletion:(KSCrashReportFilterCompl } return; } - - - NSData* jsonData = [KSJSONCodec encode:[self getBodyFromReports: bugsnagReports] + + NSDictionary *reportData = [self getBodyFromReports:bugsnagReports]; + for (BugsnagBeforeNotifyHook hook in configuration.beforeNotifyHooks) { + if (reportData) { + reportData = hook(reports, reportData); + } else { + break; + } + } + if (reportData == nil) { + if (onCompletion) { + onCompletion(@[], YES, nil); + } + return; + } + NSData* jsonData = [KSJSONCodec encode:reportData options:KSJSONEncodeOptionSorted | KSJSONEncodeOptionPretty error:&error]; diff --git a/docs/Configuration.md b/docs/Configuration.md index 013019c86..53422ea4d 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -32,6 +32,29 @@ to NO: [Bugsnag configuration].autoNotify = NO; ``` +### `beforeNotifyHooks` + +When an app first launches after a crash or a manual notification is triggered, +crash reports are sent to Bugsnag. The `beforeNotifyHooks` allow you to +modify or filter report information uploaded. Each `report` has an `apiKey`, +`notifier` info, and `events`, which contains crash details and `metaData` about +the application state. The `rawEventReports` are the data written at crash-time, +including any additional information written during `onCrashHandler`. + +**NOTE:** If a hook returns nil from execution, no report is sent. + +**NOTE:** Segmentation faults and other similar crashes cannot be caught within +handlers. + +```objective-c +BugsnagConfiguration *config = [BugsnagConfiguration new]; +[config addBeforeNotifyHook:^NSDictionary *(NSArray *rawEventReports, NSDictionary *report) { + NSMutableDictionary *reportCopy = [report mutableCopy]; + // ... + return [reportCopy copy]; +}]; +``` + ### `context` Bugsnag uses the concept of "contexts" to help display and group your errors. @@ -72,15 +95,17 @@ config.apiKey = @"YOUR_API_KEY_HERE"; When a crash occurs in an application, information about the runtime state of the application is collected and prepared to be sent to Bugsnag on the next launch. The `onCrashHandler` hook allows you to execute additional code after -the crash report has been written. +the crash report has been written. This data is available for inspection after +the next launch during the [`beforeNotifyHooks`](#beforenotifyhooks) phase. **NOTE:** All functions called from a signal handler must be [asynchronous-safe](https://www.securecoding.cert.org/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers). This excludes any Objective-C, in particular. -```objective-c +```c void HandleCrashedThread(const KSCrashReportWriter *writer) { // possibly serialize data, call another crash reporter + writer->addJSONElement(writer, "dessertMap", dessertMapObj); } // ... @@ -89,6 +114,9 @@ BugsnagConfiguration *config = [[BugsnagConfiguration alloc] init]; config.onCrashHandler = &HandleCrashedThread; ``` +[Functions available on `KSCrashReportWriter`](https://github.com/kstenerud/KSCrash/blob/master/Source/KSCrash/Recording/KSCrashReportWriter.h) + + ### `releaseStage` In order to distinguish between errors that occur in different stages of the