Skip to content

Commit

Permalink
feat: Add fallback metadata in the event of report file corruption (#327
Browse files Browse the repository at this point in the history
)
  • Loading branch information
kattrali authored Feb 21, 2019
2 parents e515d81 + 3cb0c7d commit 200e89b
Show file tree
Hide file tree
Showing 39 changed files with 407 additions and 130 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

## TBD

### Enhancements

* Capture basic report diagnostics in the file path in case of crash report
content corruption
[#327](https://github.com/bugsnag/bugsnag-cocoa/pull/327)

## 5.17.3 (2018-12-19)

### Bug Fixes
Expand Down
4 changes: 4 additions & 0 deletions OSX/Bugsnag.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
8A12006C221C50F40008C9C3 /* BSGFilepathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A12006B221C50F40008C9C3 /* BSGFilepathTests.m */; };
8A2C8FAC1C6BC1F700846019 /* Bugsnag.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A2C8FA11C6BC1F700846019 /* Bugsnag.framework */; };
8A2C8FCD1C6BC2C800846019 /* Bugsnag.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A2C8FBB1C6BC2C800846019 /* Bugsnag.h */; settings = {ATTRIBUTES = (Public, ); }; };
8A2C8FCE1C6BC2C800846019 /* Bugsnag.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A2C8FBC1C6BC2C800846019 /* Bugsnag.m */; };
Expand Down Expand Up @@ -188,6 +189,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
8A12006B221C50F40008C9C3 /* BSGFilepathTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSGFilepathTests.m; sourceTree = "<group>"; };
8A2C8FA11C6BC1F700846019 /* Bugsnag.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Bugsnag.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8A2C8FA61C6BC1F700846019 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8A2C8FAB1C6BC1F700846019 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -469,6 +471,7 @@
8A2C8FAF1C6BC1F700846019 /* Tests */ = {
isa = PBXGroup;
children = (
8A12006B221C50F40008C9C3 /* BSGFilepathTests.m */,
E7CE78861FD94E40001D07E0 /* KSCrash */,
E791482D1FD82B0C003EFEBF /* BugsnagSessionTest.m */,
E791482B1FD82B0C003EFEBF /* BugsnagSessionTrackerTest.m */,
Expand Down Expand Up @@ -933,6 +936,7 @@
8ACF0F752018136200173809 /* BugsnagCrashReportTests.m in Sources */,
E7CE78CC1FD94E77001D07E0 /* KSSystemInfo_Tests.m in Sources */,
E7CE78C91FD94E77001D07E0 /* KSSignalInfo_Tests.m in Sources */,
8A12006C221C50F40008C9C3 /* BSGFilepathTests.m in Sources */,
E7CE78C61FD94E77001D07E0 /* KSMach_Tests.m in Sources */,
E7CE78D61FD94E9E001D07E0 /* FileBasedTestCase.m in Sources */,
E7CE78D51FD94E93001D07E0 /* XCTestCase+KSCrash.m in Sources */,
Expand Down
12 changes: 6 additions & 6 deletions Source/BugsnagApiClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@class BugsnagConfiguration;

typedef void (^RequestCompletion)(id data, BOOL success, NSError *error);
typedef void (^RequestCompletion)(NSUInteger reportCount, BOOL success, NSError *error);

@interface BugsnagApiClient : NSObject

Expand All @@ -21,11 +21,11 @@ typedef void (^RequestCompletion)(id data, BOOL success, NSError *error);

- (NSOperation *)deliveryOperation;

- (void)sendData:(id)data
withPayload:(NSDictionary *)payload
toURL:(NSURL *)url
headers:(NSDictionary *)headers
onCompletion:(RequestCompletion)onCompletion;
- (void)sendItems:(NSUInteger)count
withPayload:(NSDictionary *)payload
toURL:(NSURL *)url
headers:(NSDictionary *)headers
onCompletion:(RequestCompletion)onCompletion;

@property(readonly) NSOperationQueue *sendQueue;
@property(readonly) BugsnagConfiguration *config;
Expand Down
18 changes: 9 additions & 9 deletions Source/BugsnagApiClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ - (NSOperation *)deliveryOperation {
#pragma mark - Delivery


- (void)sendData:(id)data
withPayload:(NSDictionary *)payload
toURL:(NSURL *)url
headers:(NSDictionary *)headers
onCompletion:(RequestCompletion)onCompletion {
- (void)sendItems:(NSUInteger)count
withPayload:(NSDictionary *)payload
toURL:(NSURL *)url
headers:(NSDictionary *)headers
onCompletion:(RequestCompletion)onCompletion {

@try {
NSError *error = nil;
Expand All @@ -64,7 +64,7 @@ - (void)sendData:(id)data

if (jsonData == nil) {
if (onCompletion) {
onCompletion(data, NO, error);
onCompletion(0, NO, error);
}
return;
}
Expand All @@ -79,7 +79,7 @@ - (void)sendData:(id)data
NSURLResponse *_Nullable response,
NSError *_Nullable requestErr) {
if (onCompletion) {
onCompletion(data, requestErr == nil, requestErr);
onCompletion(count, requestErr == nil, requestErr);
}
}];
[task resume];
Expand All @@ -92,13 +92,13 @@ - (void)sendData:(id)data
returningResponse:&response
error:&error];
if (onCompletion) {
onCompletion(data, error == nil, error);
onCompletion(count, error == nil, error);
}
#pragma clang diagnostic pop
}
} @catch (NSException *exception) {
if (onCompletion) {
onCompletion(data, NO,
onCompletion(count, NO,
[NSError errorWithDomain:exception.reason
code:420
userInfo:@{BSGKeyException: exception}]);
Expand Down
16 changes: 16 additions & 0 deletions Source/BugsnagCrashReport.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ NSString *_Nonnull BSGFormatSeverity(BSGSeverity severity);

@interface BugsnagCrashReport : NSObject

/**
* Create a new crash report from a JSON crash report generated by
* BugsnagCrashSentry
*
* @param report a BugsnagCrashSentry JSON report
* @param metadata additional report info encoded as a string
*
* @return a Bugsnag crash report
*/
- (instancetype _Nonnull)initWithKSReport:(NSDictionary *_Nonnull)report
fileMetadata:(NSString *_Nonnull)metadata;

/**
* Create a new crash report from a JSON crash report generated by
* BugsnagCrashSentry
Expand Down Expand Up @@ -193,6 +205,10 @@ __deprecated_msg("Use toJson: instead.");
*/
@property(readwrite, copy, nullable) NSDictionary *appState;

/**
* If YES, a complete report was not able to be obtained at generation time
*/
@property (readonly, nonatomic, getter=isIncomplete) BOOL incomplete;

/**
* Returns the enhanced error message for the thread, or nil if none exists.
Expand Down
73 changes: 67 additions & 6 deletions Source/BugsnagCrashReport.m
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@
}

NSString *_Nonnull BSGParseErrorClass(NSDictionary *error,
NSString *errorType) {
NSString *errorType,
NSString *fallbackValue) {
NSString *errorClass;

if ([errorType isEqualToString:BSGKeyCppException]) {
Expand All @@ -86,7 +87,7 @@
}

if (!errorClass) { // use a default value
errorClass = @"Exception";
errorClass = fallbackValue.length > 0 ? fallbackValue : @"Exception";
}
return errorClass;
}
Expand Down Expand Up @@ -184,6 +185,13 @@ + (instancetype)errorDataFromThreads:(NSArray *)threads;
- (instancetype)initWithClass:(NSString *_Nonnull)errorClass message:(NSString *_Nonnull)errorMessage NS_DESIGNATED_INITIALIZER;
@end

@interface FallbackReportData : NSObject
@property (nonatomic, strong) NSString *errorClass;
@property (nonatomic, getter=isUnhandled) BOOL unhandled;
@property (nonatomic) BSGSeverity severity;
- (instancetype)initWithMetadata:(NSString *)metadata;
@end

@interface BugsnagCrashReport ()

/**
Expand Down Expand Up @@ -212,25 +220,33 @@ @interface BugsnagCrashReport ()
@property(nonatomic, readwrite, copy, nullable) NSDictionary *customException;
@property(nonatomic) BugsnagSession *session;

@property (nonatomic, readwrite, getter=isIncomplete) BOOL incomplete;
@end

@implementation BugsnagCrashReport

- (instancetype)initWithKSReport:(NSDictionary *)report {
return [self initWithKSReport:report fileMetadata:@""];
}

- (instancetype)initWithKSReport:(NSDictionary *)report
fileMetadata:(NSString *)metadata {
if (self = [super init]) {
FallbackReportData *fallback = [[FallbackReportData alloc] initWithMetadata:metadata];
_notifyReleaseStages =
[report valueForKeyPath:@"user.config.notifyReleaseStages"];
_releaseStage = BSGParseReleaseStage(report);

_error = [report valueForKeyPath:@"crash.error"];
_incomplete = report.count == 0;
_errorType = _error[BSGKeyType];
_threads = [report valueForKeyPath:@"crash.threads"];
RegisterErrorData *data = [RegisterErrorData errorDataFromThreads:_threads];
if (data) {
_errorClass = data.errorClass;
_errorClass = data.errorClass ?: fallback.errorClass;
_errorMessage = data.errorMessage;
} else {
_errorClass = BSGParseErrorClass(_error, _errorType);
_errorClass = BSGParseErrorClass(_error, _errorType, fallback.errorClass);
_errorMessage = BSGParseErrorMessage(report, _error, _errorType);
}
_binaryImages = report[@"binary_images"];
Expand Down Expand Up @@ -259,7 +275,7 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
// only makes sense to use serialised value for handled exceptions
_depth = [[report valueForKeyPath:@"user.state.crash.depth"]
unsignedIntegerValue];
} else { // the event was unhandled.
} else if (_errorType != nil) { // the event was unhandled.
BOOL isSignal = [BSGKeySignal isEqualToString:_errorType];
SeverityReasonType severityReason =
isSignal ? Signal : UnhandledException;
Expand All @@ -268,6 +284,11 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
severity:BSGSeverityError
attrValue:_errorClass];
_depth = 0;
} else { // Incomplete report
SeverityReasonType severityReason = [fallback isUnhandled] ? UnhandledException : HandledError;
_handledState = [BugsnagHandledState handledStateWithSeverityReason:severityReason
severity:fallback.severity
attrValue:nil];
}
_severity = _handledState.currentSeverity;

Expand Down Expand Up @@ -478,7 +499,11 @@ - (NSDictionary *)toJson {
BSGDictSetSafeObject(event, BSGFormatSeverity(self.severity), BSGKeySeverity);
BSGDictSetSafeObject(event, [self breadcrumbs], BSGKeyBreadcrumbs);
BSGDictSetSafeObject(event, metaData, BSGKeyMetaData);


if ([self isIncomplete]) {
BSGDictSetSafeObject(event, @YES, BSGKeyIncomplete);
}

NSDictionary *device = [self.device bsg_mergedInto:self.deviceState];
BSGDictSetSafeObject(event, device, BSGKeyDevice);

Expand Down Expand Up @@ -620,6 +645,42 @@ - (NSString *_Nullable)enhancedErrorMessageForThread:(NSDictionary *_Nullable)th

@end

@implementation FallbackReportData

- (instancetype)initWithMetadata:(NSString *)metadata {
if (self = [super init]) {
NSString *separator = @"-";
NSString *location = metadata;
NSRange range = [location rangeOfString:separator options:NSBackwardsSearch];
if (range.location != NSNotFound) {
_errorClass = [location substringFromIndex:range.location + 1];
location = [location substringToIndex:range.location];
}
range = [location rangeOfString:separator options:NSBackwardsSearch];
if (range.location != NSNotFound) {
NSString *value = [location substringFromIndex:range.location + 1];
_unhandled = ![value isEqualToString:@"h"];
location = [location substringToIndex:range.location + 1];
} else {
_unhandled = YES;
}
range = [location rangeOfString:separator options:NSBackwardsSearch];
if (range.location != NSNotFound) {
NSString *value = [location substringFromIndex:range.location];
if ([value isEqualToString:@"w"]) {
_severity = BSGSeverityWarning;
} else if ([value isEqualToString:@"i"]) {
_severity = BSGSeverityInfo;
} else {
_severity = BSGSeverityError;
}
}
}
return self;
}

@end

@implementation RegisterErrorData
+ (instancetype)errorDataFromThreads:(NSArray *)threads {
for (NSDictionary *thread in threads) {
Expand Down
4 changes: 2 additions & 2 deletions Source/BugsnagErrorReportApiClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ - (void)main {
@autoreleasepool {
@try {
[[BSG_KSCrash sharedInstance]
sendAllReportsWithCompletion:^(NSArray *filteredReports,
sendAllReportsWithCompletion:^(NSUInteger sentReportCount,
BOOL completed, NSError *error) {
if (error) {
bsg_log_warn(@"Failed to send reports: %@", error);
} else if (filteredReports.count > 0) {
} else if (sentReportCount > 0) {
bsg_log_info(@"Reports sent.");
}
}];
Expand Down
6 changes: 6 additions & 0 deletions Source/BugsnagFileStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
*/
- (NSArray *)allFiles;

/** Get a list of all files by filename.
*
* @return A collection of file contents indexed by filename.
*/
- (NSDictionary <NSString *, NSDictionary *> *)allFilesByName;

/** Delete a file.
*
* @param fileId The file ID.
Expand Down
10 changes: 7 additions & 3 deletions Source/BugsnagFileStore.m
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,17 @@ - (NSUInteger)fileCount {
}

- (NSArray *)allFiles {
return [[self allFilesByName] allValues];
}

- (NSDictionary <NSString *, NSDictionary *> *)allFilesByName {
NSArray *fileIds = [self fileIds];
NSMutableArray *files =
[NSMutableArray arrayWithCapacity:[fileIds count]];
NSMutableDictionary *files =
[NSMutableDictionary dictionaryWithCapacity:[fileIds count]];
for (NSString *fileId in fileIds) {
NSDictionary *fileContents = [self fileWithId:fileId];
if (fileContents != nil) {
[files addObject:fileContents];
[files setObject:fileContents forKey:fileId];
}
}

Expand Down
1 change: 1 addition & 0 deletions Source/BugsnagKeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ static NSString *const BSGKeyName = @"name";
static NSString *const BSGKeyTimestamp = @"timestamp";
static NSString *const BSGKeyType = @"type";
static NSString *const BSGKeyMetaData = @"metaData";
static NSString *const BSGKeyIncomplete = @"incomplete";
static NSString *const BSGKeyId = @"id";
static NSString *const BSGKeyUser = @"user";
static NSString *const BSGKeyEmail = @"email";
Expand Down
10 changes: 5 additions & 5 deletions Source/BugsnagSessionTrackingApiClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ - (void)deliverSessionsInStore:(BugsnagSessionFileStore *)store {
@"Bugsnag-API-Key": apiKey,
@"Bugsnag-Sent-At": [BSG_RFC3339DateTool stringFromDate:[NSDate new]]
};
[self sendData:payload
withPayload:[payload toJson]
toURL:sessionURL
headers:HTTPHeaders
onCompletion:^(id data, BOOL success, NSError *error) {
[self sendItems:sessions.count
withPayload:[payload toJson]
toURL:sessionURL
headers:HTTPHeaders
onCompletion:^(NSUInteger sentCount, BOOL success, NSError *error) {
if (success && error == nil) {
bsg_log_info(@"Sent %lu sessions to Bugsnag", (unsigned long) sessionCount);

Expand Down
Loading

0 comments on commit 200e89b

Please sign in to comment.