From 562583366c2f7f799662ed1d0d0796c1eaa47d17 Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Wed, 18 Mar 2020 11:14:40 +0000 Subject: [PATCH 01/17] (feat) Enhanced orientation breadcrumbs with explicit "from" and "to" --- CHANGELOG.md | 4 ++++ Source/BugsnagClient.m | 34 +++++++++++++++++++++++++--------- Source/BugsnagKeys.h | 1 + 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aaf536a3..a0f1c2eee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -146,6 +146,10 @@ Bugsnag Notifiers on other platforms. * Added `addOnSendBlock:`, `removeOnSendBlock:` and `clearOnSendBlocks` methods to `Bugsnag` and `BugsnagConfiguration`. (#485)[https://github.com/bugsnag/bugsnag-cocoa/pull/485] + +* Enhanced device orientation change breadcrumbs. These are now reported with "from" and "to" values + in a form consistent with the Android notifier. + (#486)[https://github.com/bugsnag/bugsnag-cocoa/pull/486] ## Bug fixes diff --git a/Source/BugsnagClient.m b/Source/BugsnagClient.m index 84007b85e..f156fe6f2 100644 --- a/Source/BugsnagClient.m +++ b/Source/BugsnagClient.m @@ -84,6 +84,10 @@ static NSUInteger handledCount; static NSUInteger unhandledCount; static bool hasRecordedSessions; +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE +// The previous device orientation - iOS only +static NSString *lastOrientation = NULL; +#endif /** * Handler executed when the application crashes. Writes information about the @@ -376,7 +380,7 @@ - (void)start { selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil]; - + [center addObserver:self selector:@selector(lowMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification @@ -776,10 +780,10 @@ - (void)batteryChanged:(NSNotification *)notif { - (void)orientationChanged:(NSNotification *)notif { NSString *orientation; - UIDeviceOrientation deviceOrientation = - [UIDevice currentDevice].orientation; + + UIDeviceOrientation currentDeviceOrientation = [UIDevice currentDevice].orientation; - switch (deviceOrientation) { + switch (currentDeviceOrientation) { case UIDeviceOrientationPortraitUpsideDown: orientation = @"portraitupsidedown"; break; @@ -811,14 +815,26 @@ - (void)orientationChanged:(NSNotification *)notif { [orientationNotifName isEqualToString:lastBreadcrumb[BSGKeyName]]) { NSDictionary *metadata = lastBreadcrumb[BSGKeyMetadata]; - if ([orientation isEqualToString:metadata[BSGKeyOrientation]]) { + if ([orientation isEqualToString:metadata[BSGKeyOrientation]]) return; // ignore duplicate orientation event - } } - [[self state] addAttribute:BSGKeyOrientation - withValue:orientation - toTabWithName:BSGKeyDeviceState]; + // It's not a change + if ([orientation isEqualToString:lastOrientation]) + return; + + // We previously had an orientation + if (lastOrientation) { + [[self state] addAttribute:BSGKeyOrientationChange + withValue:@{@"from" : lastOrientation, + @"to" : orientation} + toTabWithName:BSGKeyDeviceState]; + } + + // We shouldn't get here without orientation being set, but to be on the safe side: + if (orientation) + // Preserve the orientation + lastOrientation = orientation; } - (void)lowMemoryWarning:(NSNotification *)notif { diff --git a/Source/BugsnagKeys.h b/Source/BugsnagKeys.h index da39ba3b0..ab621cba0 100644 --- a/Source/BugsnagKeys.h +++ b/Source/BugsnagKeys.h @@ -43,6 +43,7 @@ static NSString *const BSGKeyLabel = @"label"; static NSString *const BSGKeySeverityReason = @"severityReason"; static NSString *const BSGKeyLogLevel = @"logLevel"; static NSString *const BSGKeyOrientation = @"orientation"; +static NSString *const BSGKeyOrientationChange = @"Orientation change"; static NSString *const BSGKeySimulatorModelId = @"SIMULATOR_MODEL_IDENTIFIER"; static NSString *const BSGKeyFrameAddrFormat = @"0x%lx"; static NSString *const BSGKeySymbolAddr = @"symbolAddress"; From 7133d6fb58eeee89d90e57eec4b204086708f507 Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Fri, 20 Mar 2020 18:31:30 +0000 Subject: [PATCH 02/17] Orientation change breadcrumb PR feedback --- Source/BugsnagClient.h | 5 ++ Source/BugsnagClient.m | 152 +++++++++++++++++++++++++---------------- Tests/BugsnagTests.m | 32 ++++++++- 3 files changed, 128 insertions(+), 61 deletions(-) diff --git a/Source/BugsnagClient.h b/Source/BugsnagClient.h index f76abdfe6..c8c965841 100644 --- a/Source/BugsnagClient.h +++ b/Source/BugsnagClient.h @@ -28,6 +28,11 @@ #import "BugsnagConfiguration.h" #import "BugsnagMetadata.h" +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE +#import +#elif TARGET_OS_MAC +#import +#endif @class BugsnagSessionTracker; diff --git a/Source/BugsnagClient.m b/Source/BugsnagClient.m index f156fe6f2..42f235f45 100644 --- a/Source/BugsnagClient.m +++ b/Source/BugsnagClient.m @@ -42,12 +42,6 @@ #import "BSG_KSSystemInfo.h" #import "BSG_KSMach.h" -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE -#import -#elif TARGET_OS_MAC -#import -#endif - NSString *const NOTIFIER_VERSION = @"5.23.0"; NSString *const NOTIFIER_URL = @"https://github.com/bugsnag/bugsnag-cocoa"; NSString *const BSTabCrash = @"crash"; @@ -84,10 +78,6 @@ static NSUInteger handledCount; static NSUInteger unhandledCount; static bool hasRecordedSessions; -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE -// The previous device orientation - iOS only -static NSString *lastOrientation = NULL; -#endif /** * Handler executed when the application crashes. Writes information about the @@ -147,6 +137,43 @@ void BSSerializeDataCrashHandler(const BSG_KSCrashReportWriter *writer, int type } } +/** + * Convert a device orientation into its Bugsnag string representation + * + * @param deviceOrientation The platform device orientation + * + * @returns A string representing the device orientation or nil if there's no equivalent + */ +#if TARGET_OS_IOS +NSString *BSGOrientationNameFromEnum(UIDeviceOrientation deviceOrientation) +{ + NSString *orientation; + switch (deviceOrientation) { + case UIDeviceOrientationPortraitUpsideDown: + orientation = @"portraitupsidedown"; + break; + case UIDeviceOrientationPortrait: + orientation = @"portrait"; + break; + case UIDeviceOrientationLandscapeRight: + orientation = @"landscaperight"; + break; + case UIDeviceOrientationLandscapeLeft: + orientation = @"landscapeleft"; + break; + case UIDeviceOrientationFaceUp: + orientation = @"faceup"; + break; + case UIDeviceOrientationFaceDown: + orientation = @"facedown"; + break; + default: + return nil; // always ignore unknown breadcrumbs + } + return orientation; +} +#endif + /** * Writes a dictionary to a destination using the BSG_KSCrash JSON encoding * @@ -213,6 +240,10 @@ @interface BugsnagClient () @property (nonatomic, strong) BSGOutOfMemoryWatchdog *oomWatchdog; @property (nonatomic, strong) BugsnagPluginClient *pluginClient; @property (nonatomic) BOOL appCrashedLastLaunch; +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE +// The previous device orientation - iOS only +@property (class, nonatomic, strong) NSString *lastOrientation; +#endif @end @interface BugsnagConfiguration () @@ -221,6 +252,23 @@ @interface BugsnagConfiguration () @implementation BugsnagClient +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE +/** + * Storage for the device orientation. It is "last" whenever an orientation change is received + */ +static NSString *_lastOrientation = nil; + ++ (NSString *)lastOrientation +{ + return _lastOrientation; +} + ++ (void)setLastOrientation:(NSString *)lastOrientation +{ + _lastOrientation = lastOrientation; +} +#endif + @synthesize configuration; - (id)initWithConfiguration:(BugsnagConfiguration *)initConfiguration { @@ -280,6 +328,10 @@ - (id)initWithConfiguration:(BugsnagConfiguration *)initConfiguration { [self metadataChanged:self.configuration.config]; [self metadataChanged:self.state]; self.pluginClient = [[BugsnagPluginClient alloc] initWithPlugins:self.configuration.plugins]; + +#if TARGET_OS_IOS + _lastOrientation = BSGOrientationNameFromEnum([UIDevice currentDevice].orientation); +#endif } return self; } @@ -390,7 +442,6 @@ - (void)start { [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; [self batteryChanged:nil]; - [self orientationChanged:nil]; [self addTerminationObserver:UIApplicationWillTerminateNotification]; #elif TARGET_OS_MAC @@ -778,63 +829,46 @@ - (void)batteryChanged:(NSNotification *)notif { toTabWithName:BSGKeyDeviceState]; } -- (void)orientationChanged:(NSNotification *)notif { - NSString *orientation; - +/** + * Called when an orientation change notification is received to record an + * equivalent breadcrumb. + * + * @param notification The orientation-change notification + */ +- (void)orientationChanged:(NSNotification *)notification { UIDeviceOrientation currentDeviceOrientation = [UIDevice currentDevice].orientation; - - switch (currentDeviceOrientation) { - case UIDeviceOrientationPortraitUpsideDown: - orientation = @"portraitupsidedown"; - break; - case UIDeviceOrientationPortrait: - orientation = @"portrait"; - break; - case UIDeviceOrientationLandscapeRight: - orientation = @"landscaperight"; - break; - case UIDeviceOrientationLandscapeLeft: - orientation = @"landscapeleft"; - break; - case UIDeviceOrientationFaceUp: - orientation = @"faceup"; - break; - case UIDeviceOrientationFaceDown: - orientation = @"facedown"; - break; - default: - return; // always ignore unknown breadcrumbs + NSString *orientation = BSGOrientationNameFromEnum(currentDeviceOrientation); + + // Short-circuit the exit if we don't have enough info to record a full breadcrumb + // or the orientation hasn't changed (false positive). + if (!orientation) { + return; + } + else if (!_lastOrientation || [orientation isEqualToString:_lastOrientation]) { + _lastOrientation = orientation; + return; } - NSDictionary *lastBreadcrumb = - [[self.configuration.breadcrumbs arrayValue] lastObject]; - NSString *orientationNotifName = - BSGBreadcrumbNameForNotificationName(notif.name); + NSDictionary *lastBreadcrumb = [[self.configuration.breadcrumbs arrayValue] lastObject]; + NSString *orientationNotifName = BSGBreadcrumbNameForNotificationName(notification.name); - if (lastBreadcrumb && - [orientationNotifName isEqualToString:lastBreadcrumb[BSGKeyName]]) { + if (lastBreadcrumb && [orientationNotifName isEqualToString:lastBreadcrumb[BSGKeyName]]) + { NSDictionary *metadata = lastBreadcrumb[BSGKeyMetadata]; - if ([orientation isEqualToString:metadata[BSGKeyOrientation]]) + if ([orientation isEqualToString:metadata[BSGKeyOrientation]]) { return; // ignore duplicate orientation event + } } - - // It's not a change - if ([orientation isEqualToString:lastOrientation]) - return; - - // We previously had an orientation - if (lastOrientation) { - [[self state] addAttribute:BSGKeyOrientationChange - withValue:@{@"from" : lastOrientation, - @"to" : orientation} - toTabWithName:BSGKeyDeviceState]; - } - // We shouldn't get here without orientation being set, but to be on the safe side: - if (orientation) - // Preserve the orientation - lastOrientation = orientation; + // Record the breadcrumb + [[self state] addAttribute:BSGKeyOrientationChange + withValue:@{@"from" : _lastOrientation, + @"to" : orientation} + toTabWithName:BSGKeyDeviceState]; + + // Preserve the orientation + _lastOrientation = orientation; } - (void)lowMemoryWarning:(NSNotification *)notif { diff --git a/Tests/BugsnagTests.m b/Tests/BugsnagTests.m index b5f79b009..57141710b 100644 --- a/Tests/BugsnagTests.m +++ b/Tests/BugsnagTests.m @@ -8,6 +8,7 @@ // Unit tests of global Bugsnag behaviour #import "Bugsnag.h" +#import "BugsnagClient.h" #import "BugsnagTestConstants.h" #import @@ -21,6 +22,11 @@ @interface BugsnagConfiguration () @property(nonatomic, readwrite, strong) NSMutableArray *onSendBlocks; @end +@interface BugsnagClient () ++ (NSString *)lastOrientation; ++ (void)setLastOrientation:(NSString *)lastOrientation; +@end + @interface BugsnagTests : XCTestCase @end @@ -391,7 +397,7 @@ - (void) testOnSendBlocks { [Bugsnag notify:exception1]; // Both called? - [self waitForExpectations:@[expectation1, expectation2] timeout:5.0]; + [self waitForExpectations:@[expectation1, expectation2] timeout:10.0]; [Bugsnag removeOnSendBlock:block2]; XCTAssertEqual([[[Bugsnag configuration] onSendBlocks] count], 1); @@ -401,9 +407,31 @@ - (void) testOnSendBlocks { NSException *exception2 = [[NSException alloc] initWithName:@"exception1" reason:@"reason1" userInfo:nil]; [Bugsnag notify:exception2]; // One removed, should only call one - [self waitForExpectations:@[expectation3, expectation4] timeout:5.0]; + [self waitForExpectations:@[expectation3, expectation4] timeout:10.0]; [self waitForExpectations:@[expectation5, expectation6] timeout:1.0]; } +/** + * Test that the Orientation -> string mapping is as expected + * NOTE: should be moved to BugsnagClientTests when that file exists + */ +#if TARGET_OS_IOS +NSString *BSGOrientationNameFromEnum(UIDeviceOrientation deviceOrientation); +- (void)testBSGOrientationNameFromEnum { + XCTAssertEqualObjects(BSGOrientationNameFromEnum(UIDeviceOrientationPortraitUpsideDown), @"portraitupsidedown"); + XCTAssertEqualObjects(BSGOrientationNameFromEnum(UIDeviceOrientationPortrait), @"portrait"); + XCTAssertEqualObjects(BSGOrientationNameFromEnum(UIDeviceOrientationLandscapeRight), @"landscaperight"); + XCTAssertEqualObjects(BSGOrientationNameFromEnum(UIDeviceOrientationLandscapeLeft), @"landscapeleft"); + XCTAssertEqualObjects(BSGOrientationNameFromEnum(UIDeviceOrientationFaceUp), @"faceup"); + XCTAssertEqualObjects(BSGOrientationNameFromEnum(UIDeviceOrientationFaceDown), @"facedown"); + + XCTAssertNil(BSGOrientationNameFromEnum(-1)); + XCTAssertNil(BSGOrientationNameFromEnum(99)); + + [BugsnagClient setLastOrientation:@"testOrientation"]; + XCTAssertEqualObjects([BugsnagClient lastOrientation], @"testOrientation"); +} +#endif + @end From 41a9e0fe65dd1e86e9929d6ad271500d92d80210 Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Thu, 19 Mar 2020 14:55:55 +0000 Subject: [PATCH 03/17] (feat) Increased detail in handled event breadcrumb --- CHANGELOG.md | 3 + Source/BugsnagClient.m | 45 +++++++------- Tests/BugsnagClientTests.m | 84 +++++++++++++++++++++++++++ iOS/Bugsnag.xcodeproj/project.pbxproj | 14 +++-- 4 files changed, 121 insertions(+), 25 deletions(-) create mode 100644 Tests/BugsnagClientTests.m diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aaf536a3..820385333 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -157,6 +157,9 @@ Bugsnag Notifiers on other platforms. `locale`) that were missing from the OOM reports. [#444](https://github.com/bugsnag/bugsnag-cocoa/pull/444) +* Increased the detail in handled event breadcrumbs + [#493](https://github.com/bugsnag/bugsnag-cocoa/pull/493) + ## 5.23.0 (2019-12-10) This release removes support for reporting 'partial' or 'minimal' crash reports diff --git a/Source/BugsnagClient.m b/Source/BugsnagClient.m index 84007b85e..3a0659262 100644 --- a/Source/BugsnagClient.m +++ b/Source/BugsnagClient.m @@ -552,6 +552,7 @@ - (void)setupConnectivityListener { }]; } +// MARK: - Notify - (void)notifyError:(NSError *)error block:(void (^)(BugsnagEvent *))block { @@ -661,8 +662,9 @@ - (void)notifyOutOfMemoryEvent { } - (void)notify:(NSException *)exception - handledState:(BugsnagHandledState *_Nonnull)handledState - block:(void (^)(BugsnagEvent *))block { + handledState:(BugsnagHandledState *_Nonnull)handledState + block:(void (^)(BugsnagEvent *))block +{ NSString *exceptionName = exception.name ?: NSStringFromClass([exception class]); NSString *message = exception.reason; if (handledState.unhandled) { @@ -671,16 +673,18 @@ - (void)notify:(NSException *)exception [self.sessionTracker handleHandledErrorEvent]; } - BugsnagEvent *report = [[BugsnagEvent alloc] + BugsnagEvent *event = [[BugsnagEvent alloc] initWithErrorName:exceptionName errorMessage:message configuration:self.configuration metadata:[self.configuration.metadata toDictionary] handledState:handledState session:self.sessionTracker.runningSession]; + if (block) { - block(report); + block(event); } + // We discard 5 stack frames (including this one) by default, // and sum that with the number specified by report.depth: // @@ -691,35 +695,36 @@ - (void)notify:(NSException *)exception // 3 -[BugsnagCrashSentry reportUserException:reason:] // 4 -[BugsnagClient notify:message:block:] - int depth = (int)(BSGNotifierStackFrameCount + report.depth); + int depth = (int)(BSGNotifierStackFrameCount + event.depth); - NSString *reportName = - report.errorClass ?: NSStringFromClass([NSException class]); - NSString *reportMessage = report.errorMessage ?: @""; + NSString *eventErrorClass = event.errorClass ?: NSStringFromClass([NSException class]); + NSString *eventMessage = event.errorMessage ?: @""; - [self.crashSentry reportUserException:reportName - reason:reportMessage + [self.crashSentry reportUserException:eventErrorClass + reason:eventMessage originalException:exception handledState:[handledState toJson] appState:[self.state toDictionary] - callbackOverrides:report.overrides - metadata:[report.metadata copy] + callbackOverrides:event.overrides + metadata:[event.metadata copy] config:[self.configuration.config toDictionary] discardDepth:depth]; [self addBreadcrumbWithBlock:^(BugsnagBreadcrumb *_Nonnull crumb) { - crumb.type = BSGBreadcrumbTypeError; - crumb.message = reportName; - crumb.metadata = @{ - BSGKeyMessage : reportMessage, - BSGKeySeverity : BSGFormatSeverity(report.severity) - }; + crumb.type = BSGBreadcrumbTypeError; + crumb.message = eventErrorClass; + crumb.metadata = @{ + BSGKeyMessage : eventMessage, + BSGKeyErrorClass : eventErrorClass, + BSGKeyUnhandled : [[event handledState] unhandled] ? @YES : @NO, + BSGKeySeverity : BSGFormatSeverity(event.severity) + }; }]; [self flushPendingReports]; } -- (void)addBreadcrumbWithBlock: - (void (^_Nonnull)(BugsnagBreadcrumb *_Nonnull))block { +- (void)addBreadcrumbWithBlock:(void (^_Nonnull)(BugsnagBreadcrumb *_Nonnull))block +{ [self.configuration.breadcrumbs addBreadcrumbWithBlock:block]; [self serializeBreadcrumbs]; } diff --git a/Tests/BugsnagClientTests.m b/Tests/BugsnagClientTests.m new file mode 100644 index 000000000..6d0e7ed2d --- /dev/null +++ b/Tests/BugsnagClientTests.m @@ -0,0 +1,84 @@ +// +// BugsnagClientTests.m +// Tests +// +// Created by Robin Macharg on 18/03/2020. +// Copyright © 2020 Bugsnag. All rights reserved. +// + +#import "Bugsnag.h" +#import "BugsnagClient.h" +#import "BugsnagTestConstants.h" +#import + +@interface BugsnagClientTests : XCTestCase +@end + +@interface Bugsnag () ++ (BugsnagConfiguration *)configuration; ++ (BugsnagClient *)client; +@end + +@interface BugsnagClient () +- (void)orientationChanged:(NSNotification *)notif; +@end + +@interface BugsnagBreadcrumb () +- (NSDictionary *)objectValue; +@end + +@implementation BugsnagClientTests + +/** + * A boilerplate helper method to setup Bugsnag + */ +-(void)setUpBugsnagWillCallNotify:(bool)willNotify { + NSError *error; + BugsnagConfiguration *configuration = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1 error:&error]; + if (willNotify) { + [configuration addOnSendBlock:^bool(BugsnagEvent * _Nonnull event) { return false; }]; + } + [Bugsnag startBugsnagWithConfiguration:configuration]; +} + +/** + * Handled events leave a breadcrumb when norify() is called. Test that values are inserted + * correctly. + */ +- (void)testBreadcrumbEnhancedValues { + + [self setUpBugsnagWillCallNotify:false]; + + NSException *ex = [[NSException alloc] initWithName:@"myName" reason:@"myReason1" userInfo:nil]; + + __block NSString *eventErrorClass; + __block NSString *eventErrorMessage; + __block BOOL eventUnhandled; + __block NSString *eventSeverity; + + // Check that the event is passed the apiKey + [Bugsnag notify:ex block:^(BugsnagEvent * _Nonnull event) { + XCTAssertEqual(event.apiKey, DUMMY_APIKEY_32CHAR_1); + + // Grab the values that end up in the event for later comparison + eventErrorClass = [event errorClass]; + eventErrorMessage = [event errorMessage]; + eventUnhandled = [event valueForKeyPath:@"handledState.unhandled"] ? YES : NO; + eventSeverity = BSGFormatSeverity([event severity]); + }]; + + // Check that we can change it + [Bugsnag notify:ex]; + + NSDictionary *breadcrumb = [[[[Bugsnag client] configuration] breadcrumbs][1] objectValue]; + NSDictionary *metadata = [breadcrumb valueForKey:@"metaData"]; + + XCTAssertEqualObjects([breadcrumb valueForKey:@"type"], @"error"); + XCTAssertEqualObjects([breadcrumb valueForKey:@"message"], eventErrorClass); + XCTAssertEqualObjects([metadata valueForKey:@"errorClass"], eventErrorClass); + XCTAssertEqualObjects([metadata valueForKey:@"message"], eventErrorMessage); + XCTAssertEqual((bool)[metadata valueForKey:@"unhandled"], eventUnhandled); + XCTAssertEqualObjects([metadata valueForKey:@"severity"], eventSeverity); +} + +@end diff --git a/iOS/Bugsnag.xcodeproj/project.pbxproj b/iOS/Bugsnag.xcodeproj/project.pbxproj index dab34d044..2421298b0 100644 --- a/iOS/Bugsnag.xcodeproj/project.pbxproj +++ b/iOS/Bugsnag.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 0061D84924067AF80041C068 /* BSG_SSKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = 0061D84524067AF70041C068 /* BSG_SSKeychain.m */; }; 0061D84A24067AF80041C068 /* BSG_SSKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = 0061D84524067AF70041C068 /* BSG_SSKeychain.m */; }; 0061D84B24067AF80041C068 /* BSG_SSKeychain.h in Headers */ = {isa = PBXBuildFile; fileRef = 0061D84624067AF80041C068 /* BSG_SSKeychain.h */; }; + 0089B70324221EDE00D5A7F2 /* BugsnagClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0089B70224221EDE00D5A7F2 /* BugsnagClientTests.m */; }; 009131BC23F46279000810D9 /* BugsnagMetadataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 009131BB23F46279000810D9 /* BugsnagMetadataTests.m */; }; 009131BE23F5884E000810D9 /* BugsnagBaseUnitTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 009131BD23F5884E000810D9 /* BugsnagBaseUnitTest.m */; }; 00D7ACAD23E9C63000FBE4A7 /* BugsnagTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D7ACAC23E9C63000FBE4A7 /* BugsnagTests.m */; }; @@ -432,6 +433,7 @@ 0061D84424067AF70041C068 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 0061D84524067AF70041C068 /* BSG_SSKeychain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BSG_SSKeychain.m; sourceTree = ""; }; 0061D84624067AF80041C068 /* BSG_SSKeychain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BSG_SSKeychain.h; sourceTree = ""; }; + 0089B70224221EDE00D5A7F2 /* BugsnagClientTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagClientTests.m; path = ../../Tests/BugsnagClientTests.m; sourceTree = ""; }; 009131BB23F46279000810D9 /* BugsnagMetadataTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagMetadataTests.m; path = ../../Tests/BugsnagMetadataTests.m; sourceTree = ""; }; 009131BD23F5884E000810D9 /* BugsnagBaseUnitTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagBaseUnitTest.m; path = ../../Tests/BugsnagBaseUnitTest.m; sourceTree = ""; }; 009131BF23F58930000810D9 /* BugsnagBaseUnitTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BugsnagBaseUnitTest.h; path = ../../Tests/BugsnagBaseUnitTest.h; sourceTree = ""; }; @@ -779,9 +781,14 @@ 8A2C8F261C6BBD2300846019 /* Tests */ = { isa = PBXGroup; children = ( + 000DF29323DB4B4900A883CE /* TestConstants.m */, + 00F9393723FC4F63008C7073 /* BugsnagTestsDummyClass.h */, + 00F9393823FC4F64008C7073 /* BugsnagTestsDummyClass.m */, + 00D7ACAC23E9C63000FBE4A7 /* BugsnagTests.m */, 009131BD23F5884E000810D9 /* BugsnagBaseUnitTest.m */, 009131BF23F58930000810D9 /* BugsnagBaseUnitTest.h */, 8A2C8F8B1C6BBFDD00846019 /* BugsnagBreadcrumbsTest.m */, + 0089B70224221EDE00D5A7F2 /* BugsnagClientTests.m */, 4B3B193422CA7B0900475354 /* BugsnagCollectionsBSGDictSetSafeObjectTest.m */, 4B775FCE22CBDEB4004839C5 /* BugsnagCollectionsBSGDictInsertIfNotNilTest.m */, 4BE6C42522CAD61A0056305D /* BugsnagCollectionsBSGDictMergeTest.m */, @@ -791,6 +798,7 @@ E77316E11F73B46600A14F06 /* BugsnagHandledStateTest.m */, F429554A50F3ABE60537F70E /* BugsnagKSCrashSysInfoParserTest.m */, 009131BB23F46279000810D9 /* BugsnagMetadataTests.m */, + E72AE1FF241A57B100ED8972 /* BugsnagPluginTest.m */, E78C1EFB1FCC759B00B976D3 /* BugsnagSessionTest.m */, E78C1EF01FCC2F1700B976D3 /* BugsnagSessionTrackerTest.m */, E78C1EF21FCC615400B976D3 /* BugsnagSessionTrackingPayloadTest.m */, @@ -807,11 +815,6 @@ 8A2C8F951C6BC08600846019 /* report.json */, 000DF29223DB4A6B00A883CE /* Swift Tests */, E70EE0891FD7047D00FA745C /* KSCrash */, - 000DF29323DB4B4900A883CE /* TestConstants.m */, - 00D7ACAC23E9C63000FBE4A7 /* BugsnagTests.m */, - 00F9393723FC4F63008C7073 /* BugsnagTestsDummyClass.h */, - 00F9393823FC4F64008C7073 /* BugsnagTestsDummyClass.m */, - E72AE1FF241A57B100ED8972 /* BugsnagPluginTest.m */, ); name = Tests; path = BugsnagTests; @@ -1321,6 +1324,7 @@ E70EE0781FD7039E00FA745C /* RFC3339DateTool_Tests.m in Sources */, 8A530CC422FDD51F00F0C108 /* KSCrashIdentifierTests.m in Sources */, 000DF29423DB4B4900A883CE /* TestConstants.m in Sources */, + 0089B70324221EDE00D5A7F2 /* BugsnagClientTests.m in Sources */, 8A4E733F1DC13281001F7CC8 /* BugsnagConfigurationTests.m in Sources */, E784D25A1FD70C25004B01E1 /* KSJSONCodec_Tests.m in Sources */, 009131BC23F46279000810D9 /* BugsnagMetadataTests.m in Sources */, From c85f5650190a6de466dfce1b8a873ba0ab6cd23a Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Mon, 23 Mar 2020 19:20:14 +0000 Subject: [PATCH 04/17] Update Tests/BugsnagClientTests.m Co-Authored-By: Tom Longridge --- Tests/BugsnagClientTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/BugsnagClientTests.m b/Tests/BugsnagClientTests.m index 6d0e7ed2d..29afe5327 100644 --- a/Tests/BugsnagClientTests.m +++ b/Tests/BugsnagClientTests.m @@ -42,7 +42,7 @@ -(void)setUpBugsnagWillCallNotify:(bool)willNotify { } /** - * Handled events leave a breadcrumb when norify() is called. Test that values are inserted + * Handled events leave a breadcrumb when notify() is called. Test that values are inserted * correctly. */ - (void)testBreadcrumbEnhancedValues { From 485e0e162ce8f8ddbd5ac8c4cefa2d037f6be11c Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Mon, 23 Mar 2020 19:20:22 +0000 Subject: [PATCH 05/17] Update Tests/BugsnagClientTests.m Co-Authored-By: Tom Longridge --- Tests/BugsnagClientTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/BugsnagClientTests.m b/Tests/BugsnagClientTests.m index 29afe5327..8f6faa806 100644 --- a/Tests/BugsnagClientTests.m +++ b/Tests/BugsnagClientTests.m @@ -45,7 +45,7 @@ -(void)setUpBugsnagWillCallNotify:(bool)willNotify { * Handled events leave a breadcrumb when notify() is called. Test that values are inserted * correctly. */ -- (void)testBreadcrumbEnhancedValues { +- (void)testAutomaticNotifyBreadcrumbData { [self setUpBugsnagWillCallNotify:false]; From eeed34f55f7ea9488285d56f65e46bc238bcae81 Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Tue, 24 Mar 2020 15:25:11 +0000 Subject: [PATCH 06/17] PR feedback --- Source/BugsnagClient.h | 5 ---- Source/BugsnagClient.m | 67 +++++++++++++++++++++++++++--------------- Source/BugsnagKeys.h | 1 - 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/Source/BugsnagClient.h b/Source/BugsnagClient.h index c8c965841..f76abdfe6 100644 --- a/Source/BugsnagClient.h +++ b/Source/BugsnagClient.h @@ -28,11 +28,6 @@ #import "BugsnagConfiguration.h" #import "BugsnagMetadata.h" -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE -#import -#elif TARGET_OS_MAC -#import -#endif @class BugsnagSessionTracker; diff --git a/Source/BugsnagClient.m b/Source/BugsnagClient.m index 42f235f45..a36a2d29c 100644 --- a/Source/BugsnagClient.m +++ b/Source/BugsnagClient.m @@ -42,6 +42,12 @@ #import "BSG_KSSystemInfo.h" #import "BSG_KSMach.h" +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE +#import +#elif TARGET_OS_MAC +#import +#endif + NSString *const NOTIFIER_VERSION = @"5.23.0"; NSString *const NOTIFIER_URL = @"https://github.com/bugsnag/bugsnag-cocoa"; NSString *const BSTabCrash = @"crash"; @@ -126,6 +132,13 @@ void BSSerializeDataCrashHandler(const BSG_KSCrashReportWriter *writer, int type } } +/** + * Maps an NSNotificationName to its standard (Bugsnag) name + * + * @param name The NSNotificationName (type aliased to NSString) + * + * @returns The Bugsnag-standard name, or the notification name minus the "Notification" portion. + */ NSString *BSGBreadcrumbNameForNotificationName(NSString *name) { NSString *readableName = notificationNameMap[name]; @@ -346,6 +359,9 @@ - (id)initWithConfiguration:(BugsnagConfiguration *)initConfiguration { NSString *const kAppWillTerminate = @"App Will Terminate"; NSString *const BSGBreadcrumbLoadedMessage = @"Bugsnag loaded"; +/** + * A map of notification names to human-readable strings + */ - (void)initializeNotificationNameMap { notificationNameMap = @{ #if TARGET_OS_TV @@ -362,10 +378,8 @@ - (void)initializeNotificationNameMap { UIWindowDidBecomeVisibleNotification : kWindowVisible, UIWindowDidBecomeHiddenNotification : kWindowHidden, UIApplicationWillTerminateNotification : kAppWillTerminate, - UIApplicationWillEnterForegroundNotification : - @"App Will Enter Foreground", - UIApplicationDidEnterBackgroundNotification : - @"App Did Enter Background", + UIApplicationWillEnterForegroundNotification : @"App Will Enter Foreground", + UIApplicationDidEnterBackgroundNotification : @"App Did Enter Background", UIKeyboardDidShowNotification : @"Keyboard Became Visible", UIKeyboardDidHideNotification : @"Keyboard Became Hidden", UIMenuControllerDidShowMenuNotification : @"Did Show Menu", @@ -380,7 +394,7 @@ - (void)initializeNotificationNameMap { UITableViewSelectionDidChangeNotification : kTableViewSelectionChange, UIDeviceBatteryStateDidChangeNotification : @"Battery State Changed", UIDeviceBatteryLevelDidChangeNotification : @"Battery Level Changed", - UIDeviceOrientationDidChangeNotification : @"Orientation Changed", + UIDeviceOrientationDidChangeNotification : @"Orientation Change", UIApplicationDidReceiveMemoryWarningNotification : @"Memory Warning", #elif TARGET_OS_MAC @@ -849,23 +863,7 @@ - (void)orientationChanged:(NSNotification *)notification { return; } - NSDictionary *lastBreadcrumb = [[self.configuration.breadcrumbs arrayValue] lastObject]; - NSString *orientationNotifName = BSGBreadcrumbNameForNotificationName(notification.name); - - if (lastBreadcrumb && [orientationNotifName isEqualToString:lastBreadcrumb[BSGKeyName]]) - { - NSDictionary *metadata = lastBreadcrumb[BSGKeyMetadata]; - - if ([orientation isEqualToString:metadata[BSGKeyOrientation]]) { - return; // ignore duplicate orientation event - } - } - - // Record the breadcrumb - [[self state] addAttribute:BSGKeyOrientationChange - withValue:@{@"from" : _lastOrientation, - @"to" : orientation} - toTabWithName:BSGKeyDeviceState]; + [self sendBreadcrumbForOrientationChangeNotification:notification withOrientation:orientation]; // Preserve the orientation _lastOrientation = orientation; @@ -990,8 +988,29 @@ - (void)dealloc { - (void)sendBreadcrumbForNotification:(NSNotification *)note { [self addBreadcrumbWithBlock:^(BugsnagBreadcrumb *_Nonnull breadcrumb) { - breadcrumb.type = BSGBreadcrumbTypeState; - breadcrumb.message = BSGBreadcrumbNameForNotificationName(note.name); + breadcrumb.type = BSGBreadcrumbTypeState; + breadcrumb.message = BSGBreadcrumbNameForNotificationName(note.name); + }]; +} + +/** + * Add a breadcrumb with a device orientation change. Should only called when we have + * a previous orientation to transition from. + * + * @param notification The orientation change notification + * @param orientation The current orientation + */ +- (void)sendBreadcrumbForOrientationChangeNotification:(NSNotification *)notification + withOrientation:(NSString*)orientation +{ + [self addBreadcrumbWithBlock:^(BugsnagBreadcrumb *_Nonnull breadcrumb) { + breadcrumb.metadata = @{ + @"from" : _lastOrientation, + @"to" : orientation + }; + + breadcrumb.type = BSGBreadcrumbTypeState; + breadcrumb.message = BSGBreadcrumbNameForNotificationName(notification.name); }]; } diff --git a/Source/BugsnagKeys.h b/Source/BugsnagKeys.h index ab621cba0..da39ba3b0 100644 --- a/Source/BugsnagKeys.h +++ b/Source/BugsnagKeys.h @@ -43,7 +43,6 @@ static NSString *const BSGKeyLabel = @"label"; static NSString *const BSGKeySeverityReason = @"severityReason"; static NSString *const BSGKeyLogLevel = @"logLevel"; static NSString *const BSGKeyOrientation = @"orientation"; -static NSString *const BSGKeyOrientationChange = @"Orientation change"; static NSString *const BSGKeySimulatorModelId = @"SIMULATOR_MODEL_IDENTIFIER"; static NSString *const BSGKeyFrameAddrFormat = @"0x%lx"; static NSString *const BSGKeySymbolAddr = @"symbolAddress"; From 9b4b54ac49a9c22074e3a73f28b3d4b1c36e9ee6 Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Tue, 24 Mar 2020 18:38:24 +0000 Subject: [PATCH 07/17] PR Feedback --- Source/BugsnagClient.m | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Source/BugsnagClient.m b/Source/BugsnagClient.m index a36a2d29c..d2164487c 100644 --- a/Source/BugsnagClient.m +++ b/Source/BugsnagClient.m @@ -852,20 +852,28 @@ - (void)batteryChanged:(NSNotification *)notif { - (void)orientationChanged:(NSNotification *)notification { UIDeviceOrientation currentDeviceOrientation = [UIDevice currentDevice].orientation; NSString *orientation = BSGOrientationNameFromEnum(currentDeviceOrientation); - - // Short-circuit the exit if we don't have enough info to record a full breadcrumb - // or the orientation hasn't changed (false positive). + + // No orientation, nothing to be done if (!orientation) { return; } - else if (!_lastOrientation || [orientation isEqualToString:_lastOrientation]) { + + // Update the device orientation in metadata + [[self state] addAttribute:BSGKeyOrientation + withValue:orientation + toTabWithName:BSGKeyDeviceState]; + + // Short-circuit the exit if we don't have enough info to record a full breadcrumb + // or the orientation hasn't changed (false positive). + if (!_lastOrientation || [orientation isEqualToString:_lastOrientation]) { _lastOrientation = orientation; return; } - - [self sendBreadcrumbForOrientationChangeNotification:notification withOrientation:orientation]; - // Preserve the orientation + // We have an orientation, it's not a dupe and we have a lastOrientation. + // Send a breadcrumb and preserve the orientation. + [self sendBreadcrumbForOrientationChangeNotification:notification + withOrientation:orientation]; _lastOrientation = orientation; } @@ -1000,6 +1008,7 @@ - (void)sendBreadcrumbForNotification:(NSNotification *)note { * @param notification The orientation change notification * @param orientation The current orientation */ +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE - (void)sendBreadcrumbForOrientationChangeNotification:(NSNotification *)notification withOrientation:(NSString*)orientation { @@ -1013,6 +1022,7 @@ - (void)sendBreadcrumbForOrientationChangeNotification:(NSNotification *)notific breadcrumb.message = BSGBreadcrumbNameForNotificationName(notification.name); }]; } +#endif - (void)sendBreadcrumbForTableViewNotification:(NSNotification *)note { #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE || TARGET_OS_TV From 6e839f531c37ebd0f306d8c717a28e6dea7f3498 Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Fri, 27 Mar 2020 10:09:04 +0000 Subject: [PATCH 08/17] PR feedback and incorporate automatic breadcrumb fix. Abstracted breadcrumb type checking, made "last" orientation instance-level --- Source/BugsnagClient.m | 123 +++++++++++++++++------------------------ Tests/BugsnagTests.m | 8 +-- 2 files changed, 54 insertions(+), 77 deletions(-) diff --git a/Source/BugsnagClient.m b/Source/BugsnagClient.m index a4b20afc7..890530911 100644 --- a/Source/BugsnagClient.m +++ b/Source/BugsnagClient.m @@ -255,7 +255,7 @@ @interface BugsnagClient () @property (nonatomic) BOOL appCrashedLastLaunch; #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE // The previous device orientation - iOS only -@property (class, nonatomic, strong) NSString *lastOrientation; +@property (nonatomic, strong) NSString *lastOrientation; #endif @end @@ -269,17 +269,7 @@ @implementation BugsnagClient /** * Storage for the device orientation. It is "last" whenever an orientation change is received */ -static NSString *_lastOrientation = nil; - -+ (NSString *)lastOrientation -{ - return _lastOrientation; -} - -+ (void)setLastOrientation:(NSString *)lastOrientation -{ - _lastOrientation = lastOrientation; -} +NSString *_lastOrientation = nil; #endif @synthesize configuration; @@ -306,7 +296,6 @@ - (id)initWithConfiguration:(BugsnagConfiguration *)initConfiguration { BSGKeyUrl : NOTIFIER_URL } mutableCopy]; - NSString *cacheDir = [NSSearchPathForDirectoriesInDomains( NSCachesDirectory, NSUserDomainMask, YES) firstObject]; if (cacheDir) { @@ -394,7 +383,7 @@ - (void)initializeNotificationNameMap { UITableViewSelectionDidChangeNotification : kTableViewSelectionChange, UIDeviceBatteryStateDidChangeNotification : @"Battery State Changed", UIDeviceBatteryLevelDidChangeNotification : @"Battery Level Changed", - UIDeviceOrientationDidChangeNotification : @"Orientation Change", + UIDeviceOrientationDidChangeNotification : @"Orientation Changed", UIApplicationDidReceiveMemoryWarningNotification : @"Memory Warning", #elif TARGET_OS_MAC @@ -493,12 +482,10 @@ - (void)start { [self.sessionTracker startNewSessionIfAutoCaptureEnabled]; - if ([self.configuration shouldRecordBreadcrumbType:BSGBreadcrumbTypeState]) { - [self addBreadcrumbWithBlock:^(BugsnagBreadcrumb *_Nonnull breadcrumb) { - breadcrumb.type = BSGBreadcrumbTypeState; - breadcrumb.message = BSGBreadcrumbLoadedMessage; - }]; - } + // Record a "Bugsnag Loaded" message + [self addAutoBreadcrumbOfType:BSGBreadcrumbTypeState + withMessage:BSGBreadcrumbLoadedMessage + andMetadata:nil]; // notification not received in time on initial startup, so trigger manually [self willEnterForeground:self]; @@ -618,16 +605,15 @@ - (void)setupConnectivityListener { [BSGConnectivity monitorURL:url usingCallback:^(BOOL connected, NSString *connectionType) { __strong typeof(weakSelf) strongSelf = weakSelf; - if (connected) + if (connected) { [strongSelf flushPendingReports]; - - if ([[self configuration] shouldRecordBreadcrumbType:BSGBreadcrumbTypeState]) { - [strongSelf addBreadcrumbWithBlock:^(BugsnagBreadcrumb *crumb) { - crumb.message = @"Connectivity change"; - crumb.type = BSGBreadcrumbTypeState; - crumb.metadata = @{ @"type" : connectionType }; - }]; } + + [self addAutoBreadcrumbOfType:BSGBreadcrumbTypeState + withMessage:@"Connectivity changed" + andMetadata:@{ + @"type" : connectionType + }]; }]; } @@ -786,16 +772,13 @@ - (void)notify:(NSException *)exception config:[self.configuration.config toDictionary] discardDepth:depth]; - if ([self.configuration shouldRecordBreadcrumbType:BSGBreadcrumbTypeError]) { - [self addBreadcrumbWithBlock:^(BugsnagBreadcrumb *_Nonnull crumb) { - crumb.type = BSGBreadcrumbTypeError; - crumb.message = reportName; - crumb.metadata = @{ - BSGKeyMessage : reportMessage, - BSGKeySeverity : BSGFormatSeverity(report.severity) - }; - }]; - } + [self addAutoBreadcrumbOfType:BSGBreadcrumbTypeError + withMessage:reportName + andMetadata:@{ + BSGKeyMessage : reportMessage, + BSGKeySeverity : BSGFormatSeverity(report.severity) + }]; + [self flushPendingReports]; } @@ -875,16 +858,6 @@ - (void)orientationChanged:(NSNotification *)notification { return; } - if ([[self configuration] shouldRecordBreadcrumbType:BSGBreadcrumbTypeState]) { - [self addBreadcrumbWithBlock:^(BugsnagBreadcrumb *_Nonnull breadcrumb) { - breadcrumb.type = BSGBreadcrumbTypeState; - breadcrumb.message = orientationNotifName; - breadcrumb.metadata = @{BSGKeyOrientation : orientation}; - }]; - } - - // Update the Device orientation - // Update the device orientation in metadata [[self state] addAttribute:BSGKeyOrientation withValue:orientation @@ -899,11 +872,38 @@ - (void)orientationChanged:(NSNotification *)notification { // We have an orientation, it's not a dupe and we have a lastOrientation. // Send a breadcrumb and preserve the orientation. - [self sendBreadcrumbForOrientationChangeNotification:notification - withOrientation:orientation]; + + [self addAutoBreadcrumbOfType:BSGBreadcrumbTypeState + withMessage:BSGBreadcrumbNameForNotificationName(notification.name) + andMetadata:@{ + @"from" : _lastOrientation, + @"to" : orientation + }]; + _lastOrientation = orientation; } +/** + * A convenience safe-wrapper for conditionally recording automatic breadcrumbs + * based on the configuration. + * + * @param breadcrumbType The type of breadcrumb + * @param message The breadcrumb message + * @param metadata The breadcrumb metadata. If nil this is substituted by an empty dictionary. + */ +- (void)addAutoBreadcrumbOfType:(BSGBreadcrumbType)breadcrumbType + withMessage:(NSString * _Nonnull)message + andMetadata:(NSDictionary *)metadata +{ + if ([[self configuration] shouldRecordBreadcrumbType:breadcrumbType]) { + [self addBreadcrumbWithBlock:^(BugsnagBreadcrumb *_Nonnull breadcrumb) { + breadcrumb.metadata = metadata ?: @{}; + breadcrumb.type = breadcrumbType; + breadcrumb.message = message; + }]; + } +} + - (void)lowMemoryWarning:(NSNotification *)notif { [[self state] addAttribute:BSEventLowMemoryWarning withValue:[[Bugsnag payloadDateFormatter] @@ -1084,29 +1084,6 @@ - (void)sendBreadcrumbForNotification:(NSNotification *)note { }]; } -/** - * Add a breadcrumb with a device orientation change. Should only called when we have - * a previous orientation to transition from. - * - * @param notification The orientation change notification - * @param orientation The current orientation - */ -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE -- (void)sendBreadcrumbForOrientationChangeNotification:(NSNotification *)notification - withOrientation:(NSString*)orientation -{ - [self addBreadcrumbWithBlock:^(BugsnagBreadcrumb *_Nonnull breadcrumb) { - breadcrumb.metadata = @{ - @"from" : _lastOrientation, - @"to" : orientation - }; - - breadcrumb.type = BSGBreadcrumbTypeState; - breadcrumb.message = BSGBreadcrumbNameForNotificationName(notification.name); - }]; -} -#endif - /** * Leave a navigation breadcrumb whenever a tableView selection changes * diff --git a/Tests/BugsnagTests.m b/Tests/BugsnagTests.m index 2d127f4fb..b0b0fed47 100644 --- a/Tests/BugsnagTests.m +++ b/Tests/BugsnagTests.m @@ -23,8 +23,7 @@ @interface BugsnagConfiguration () @end @interface BugsnagClient () -+ (NSString *)lastOrientation; -+ (void)setLastOrientation:(NSString *)lastOrientation; +@property (nonatomic, strong) NSString *lastOrientation; @end @interface BugsnagTests : XCTestCase @@ -423,8 +422,9 @@ - (void)testBSGOrientationNameFromEnum { XCTAssertNil(BSGOrientationNameFromEnum(-1)); XCTAssertNil(BSGOrientationNameFromEnum(99)); - [BugsnagClient setLastOrientation:@"testOrientation"]; - XCTAssertEqualObjects([BugsnagClient lastOrientation], @"testOrientation"); + BugsnagClient *client = [BugsnagClient new]; + [client setLastOrientation:@"testOrientation"]; + XCTAssertEqualObjects([client lastOrientation], @"testOrientation"); } #endif From 38c49a3ce727dd8d26444c096f5a0e50758e56d3 Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Fri, 27 Mar 2020 11:16:14 +0000 Subject: [PATCH 09/17] Fix failing CI due to ifdef occlusion of function on non-iOS platforms --- Source/BugsnagClient.m | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Source/BugsnagClient.m b/Source/BugsnagClient.m index 83dfa0754..df4161fc7 100644 --- a/Source/BugsnagClient.m +++ b/Source/BugsnagClient.m @@ -876,6 +876,18 @@ - (void)orientationChanged:(NSNotification *)notification { _lastOrientation = orientation; } +- (void)lowMemoryWarning:(NSNotification *)notif { + [[self state] addAttribute:BSEventLowMemoryWarning + withValue:[[Bugsnag payloadDateFormatter] + stringFromDate:[NSDate date]] + toTabWithName:BSGKeyDeviceState]; + + if ([[self configuration] shouldRecordBreadcrumbType:BSGBreadcrumbTypeState]) { + [self sendBreadcrumbForNotification:notif]; + } +} +#endif + /** * A convenience safe-wrapper for conditionally recording automatic breadcrumbs * based on the configuration. @@ -897,18 +909,6 @@ - (void)addAutoBreadcrumbOfType:(BSGBreadcrumbType)breadcrumbType } } -- (void)lowMemoryWarning:(NSNotification *)notif { - [[self state] addAttribute:BSEventLowMemoryWarning - withValue:[[Bugsnag payloadDateFormatter] - stringFromDate:[NSDate date]] - toTabWithName:BSGKeyDeviceState]; - - if ([[self configuration] shouldRecordBreadcrumbType:BSGBreadcrumbTypeState]) { - [self sendBreadcrumbForNotification:notif]; - } -} -#endif - - (void)updateCrashDetectionSettings { if (self.configuration.autoDetectErrors) { // Enable all crash detection From fa0c002c3a220cdb9b36e01c115b0e32010bbf82 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Fri, 27 Mar 2020 14:56:08 +0000 Subject: [PATCH 10/17] feat: remove unused APIs from BugsnagSession interface --- CHANGELOG.md | 3 +++ Source/BugsnagClient.m | 5 +++++ Source/BugsnagEvent.m | 5 +++++ Source/BugsnagSession.h | 18 ------------------ Source/BugsnagSession.m | 16 ++++++++++++++++ Source/BugsnagSessionFileStore.m | 4 ++++ Source/BugsnagSessionTracker.m | 10 ++++++++++ Source/BugsnagSessionTrackingPayload.m | 4 ++++ Tests/BugsnagEventTests.m | 5 +++++ Tests/BugsnagSessionTest.m | 7 +++++++ Tests/BugsnagSessionTrackerStopTest.m | 5 +++++ Tests/BugsnagSessionTrackerTest.m | 6 ++++++ UPGRADING.md | 15 +++++++++++++++ 13 files changed, 85 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f9c254b2..4f51a90c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ Bugsnag Notifiers on other platforms. ## Enhancements +* Remove unused APIs from `BugsnagSession` interface +[#506](https://github.com/bugsnag/bugsnag-cocoa/pull/506) + * Remove unused APIs from `BugsnagMetadata` interface [#501](https://github.com/bugsnag/bugsnag-cocoa/pull/501) diff --git a/Source/BugsnagClient.m b/Source/BugsnagClient.m index 9bafebdac..8f1f7b83e 100644 --- a/Source/BugsnagClient.m +++ b/Source/BugsnagClient.m @@ -85,6 +85,11 @@ static NSUInteger unhandledCount; static bool hasRecordedSessions; +@interface BugsnagSession () +@property NSUInteger unhandledCount; +@property NSUInteger handledCount; +@end + /** * Handler executed when the application crashes. Writes information about the * current application state using the crash report writer. diff --git a/Source/BugsnagEvent.m b/Source/BugsnagEvent.m index 43da43b27..370e9cc33 100644 --- a/Source/BugsnagEvent.m +++ b/Source/BugsnagEvent.m @@ -239,6 +239,11 @@ - (BOOL)shouldSendReports; @property(readonly, strong, nullable) BugsnagBreadcrumbs *breadcrumbs; @end +@interface BugsnagSession () +@property NSUInteger unhandledCount; +@property NSUInteger handledCount; +@end + @interface BugsnagEvent () /** diff --git a/Source/BugsnagSession.h b/Source/BugsnagSession.h index c2c6ac468..1ed76549b 100644 --- a/Source/BugsnagSession.h +++ b/Source/BugsnagSession.h @@ -25,26 +25,8 @@ handledCount:(NSUInteger)handledCount unhandledCount:(NSUInteger)unhandledCount; -/** - * Representation used in report payloads - */ -- (NSDictionary *_Nonnull)toJson; - -/** - * Full representation of a session suitable for creating an identical session - * using initWithDictionary - */ -- (NSDictionary *_Nonnull)toDictionary; -- (void)stop; -- (void)resume; - @property(readonly) NSString *_Nonnull sessionId; @property(readonly) NSDate *_Nonnull startedAt; @property(readonly) BugsnagUser *_Nullable user; -@property(readonly) BOOL autoCaptured; -@property(readonly, getter=isStopped) BOOL stopped; - -@property NSUInteger unhandledCount; -@property NSUInteger handledCount; @end diff --git a/Source/BugsnagSession.m b/Source/BugsnagSession.m index d9ef1855c..5939f4beb 100644 --- a/Source/BugsnagSession.m +++ b/Source/BugsnagSession.m @@ -18,6 +18,22 @@ @interface BugsnagSession () @property(readwrite, getter=isStopped) BOOL stopped; +@property(readonly) BOOL autoCaptured; +@property NSUInteger unhandledCount; +@property NSUInteger handledCount; + +/** + * Representation used in report payloads + */ +- (NSDictionary *_Nonnull)toJson; + +/** + * Full representation of a session suitable for creating an identical session + * using initWithDictionary + */ +- (NSDictionary *_Nonnull)toDictionary; +- (void)stop; +- (void)resume; @end @implementation BugsnagSession diff --git a/Source/BugsnagSessionFileStore.m b/Source/BugsnagSessionFileStore.m index 50966e8fd..d1178cbe8 100644 --- a/Source/BugsnagSessionFileStore.m +++ b/Source/BugsnagSessionFileStore.m @@ -8,6 +8,10 @@ static NSString *const kSessionStoreSuffix = @"-Session-"; +@interface BugsnagSession () +- (NSDictionary *)toJson; +@end + @implementation BugsnagSessionFileStore + (BugsnagSessionFileStore *)storeWithPath:(NSString *)path { diff --git a/Source/BugsnagSessionTracker.m b/Source/BugsnagSessionTracker.m index e227471ac..010f76a0a 100644 --- a/Source/BugsnagSessionTracker.m +++ b/Source/BugsnagSessionTracker.m @@ -20,6 +20,16 @@ NSString *const BSGSessionUpdateNotification = @"BugsnagSessionChanged"; +@interface BugsnagSession () + +@property(readwrite, getter=isStopped) BOOL stopped; +@property NSUInteger unhandledCount; +@property NSUInteger handledCount; +- (NSDictionary *_Nonnull)toDictionary; +- (void)stop; +- (void)resume; +@end + @interface BugsnagConfiguration () @property(readonly, retain, nullable) NSURL *sessionURL; @end diff --git a/Source/BugsnagSessionTrackingPayload.m b/Source/BugsnagSessionTrackingPayload.m index 108886fca..3a2943b54 100644 --- a/Source/BugsnagSessionTrackingPayload.m +++ b/Source/BugsnagSessionTrackingPayload.m @@ -15,6 +15,10 @@ #import "BugsnagKSCrashSysInfoParser.h" #import "Private.h" +@interface BugsnagSession () +- (NSDictionary *)toJson; +@end + @interface Bugsnag () + (BugsnagClient *)client; @end diff --git a/Tests/BugsnagEventTests.m b/Tests/BugsnagEventTests.m index fdece3bd8..dd811b1f7 100644 --- a/Tests/BugsnagEventTests.m +++ b/Tests/BugsnagEventTests.m @@ -17,6 +17,11 @@ #import "BugsnagTestConstants.h" #import "BugsnagTestsDummyClass.h" +@interface BugsnagSession () +@property NSUInteger unhandledCount; +@property NSUInteger handledCount; +@end + @interface Bugsnag () + (BugsnagConfiguration *)configuration; @end diff --git a/Tests/BugsnagSessionTest.m b/Tests/BugsnagSessionTest.m index 662c87928..e8e1ede63 100644 --- a/Tests/BugsnagSessionTest.m +++ b/Tests/BugsnagSessionTest.m @@ -11,6 +11,13 @@ #import "BugsnagSession.h" #import "BSG_RFC3339DateTool.h" +@interface BugsnagSession () +@property NSUInteger unhandledCount; +@property NSUInteger handledCount; +- (NSDictionary *_Nonnull)toJson; +- (NSDictionary *_Nonnull)toDictionary; +@end + @interface BugsnagSessionTest : XCTestCase @end diff --git a/Tests/BugsnagSessionTrackerStopTest.m b/Tests/BugsnagSessionTrackerStopTest.m index 31cbe940b..cd635d59b 100644 --- a/Tests/BugsnagSessionTrackerStopTest.m +++ b/Tests/BugsnagSessionTrackerStopTest.m @@ -10,6 +10,11 @@ #import "BugsnagSessionTracker.h" #import "BugsnagTestConstants.h" +@interface BugsnagSession () +@property NSUInteger unhandledCount; +@property NSUInteger handledCount; +@end + @interface BugsnagSessionTrackerStopTest : XCTestCase @property BugsnagConfiguration *configuration; @property BugsnagSessionTracker *tracker; diff --git a/Tests/BugsnagSessionTrackerTest.m b/Tests/BugsnagSessionTrackerTest.m index a0deef191..c7ede9837 100644 --- a/Tests/BugsnagSessionTrackerTest.m +++ b/Tests/BugsnagSessionTrackerTest.m @@ -14,6 +14,12 @@ #import "BugsnagSessionTrackingApiClient.h" #import "BugsnagTestConstants.h" +@interface BugsnagSession () +@property NSUInteger unhandledCount; +@property NSUInteger handledCount; +@property(readonly) BOOL autoCaptured; +@end + @interface BugsnagConfiguration () - (void)deletePersistedUserData; @end diff --git a/UPGRADING.md b/UPGRADING.md index 382759a46..e6923e887 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -205,3 +205,18 @@ of the removed `addAttribute`: - BugsnagCrashReport.addAttribute(_:withValue:toTabWithName:) + BugsnagEvent.addMetadata(sectionName:key:value:) ``` + +### `BugsnagSession` class + +#### Removals + +```diff +- toJson +- toDictionary +- stop +- resume +- autoCaptured +- handledCount +- unhandledCount +- stopped +``` From 239697bb55a39115c54128f7aad4940e8739633e Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Fri, 27 Mar 2020 16:12:03 +0000 Subject: [PATCH 11/17] (fix) Revert a change to the event serialisation that caused breadcrumbs to fail to be reported A global search-and-replace of "name" by "message" in the breadcrumb portions of code (to bring the cocoa notifier in line with other notifiers) failed to take account of the Bugsnag error reporting API, which expects a "name"-keyed field. --- Source/BugsnagBreadcrumb.m | 10 +++++---- Tests/BugsnagBreadcrumbsTest.m | 38 +++++++++++++++++----------------- Tests/BugsnagSinkTests.m | 2 +- features/steps/ios_steps.rb | 4 ++-- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/Source/BugsnagBreadcrumb.m b/Source/BugsnagBreadcrumb.m index bac159a58..4c343947d 100644 --- a/Source/BugsnagBreadcrumb.m +++ b/Source/BugsnagBreadcrumb.m @@ -95,10 +95,12 @@ - (NSDictionary *)objectValue { metadata[[key copy]] = [_metadata[key] copy]; } return @{ - BSGKeyMessage : [_message copy], - BSGKeyTimestamp : timestamp, - BSGKeyType : BSGBreadcrumbTypeValue(_type), - BSGKeyMetadata : metadata + // Note: The Bugsnag Error Reporting API specifies that the breadcrumb "message" + // field should be delivered in as a "name" field. This comment notes that variance. + BSGKeyName : [_message copy], + BSGKeyTimestamp : timestamp, + BSGKeyType : BSGBreadcrumbTypeValue(_type), + BSGKeyMetadata : metadata }; } return nil; diff --git a/Tests/BugsnagBreadcrumbsTest.m b/Tests/BugsnagBreadcrumbsTest.m index 0ffe722d3..f80145d21 100644 --- a/Tests/BugsnagBreadcrumbsTest.m +++ b/Tests/BugsnagBreadcrumbsTest.m @@ -126,9 +126,9 @@ - (void)testArrayValue { XCTAssertTrue([[formatter dateFromString:item[@"timestamp"]] isKindOfClass:[NSDate class]]); } - XCTAssertEqualObjects(value[0][@"message"], @"Launch app"); - XCTAssertEqualObjects(value[1][@"message"], @"Tap button"); - XCTAssertEqualObjects(value[2][@"message"], @"Close tutorial"); + XCTAssertEqualObjects(value[0][@"name"], @"Launch app"); + XCTAssertEqualObjects(value[1][@"name"], @"Tap button"); + XCTAssertEqualObjects(value[2][@"name"], @"Close tutorial"); } - (void)testStateType { @@ -141,7 +141,7 @@ - (void)testStateType { awaitBreadcrumbSync(self.crumbs); NSArray *value = [crumbs arrayValue]; XCTAssertEqualObjects(value[0][@"metaData"][@"direction"], @"right"); - XCTAssertEqualObjects(value[0][@"message"], @"Rotated Menu"); + XCTAssertEqualObjects(value[0][@"name"], @"Rotated Menu"); XCTAssertEqualObjects(value[0][@"type"], @"state"); } @@ -150,13 +150,13 @@ - (void)testPersistentCrumbManual { NSArray *value = [NSJSONSerialization JSONObjectWithData:crumbs options:0 error:nil]; XCTAssertEqual(value.count, 3); XCTAssertEqualObjects(value[0][@"type"], @"manual"); - XCTAssertEqualObjects(value[0][@"message"], @"Launch app"); + XCTAssertEqualObjects(value[0][@"name"], @"Launch app"); XCTAssertNotNil(value[0][@"timestamp"]); XCTAssertEqualObjects(value[1][@"type"], @"manual"); - XCTAssertEqualObjects(value[1][@"message"], @"Tap button"); + XCTAssertEqualObjects(value[1][@"name"], @"Tap button"); XCTAssertNotNil(value[1][@"timestamp"]); XCTAssertEqualObjects(value[2][@"type"], @"manual"); - XCTAssertEqualObjects(value[2][@"message"], @"Close tutorial"); + XCTAssertEqualObjects(value[2][@"name"], @"Close tutorial"); XCTAssertNotNil(value[2][@"timestamp"]); } @@ -170,7 +170,7 @@ - (void)testPersistentCrumbCustom { NSArray *value = [NSJSONSerialization JSONObjectWithData:crumbs options:0 error:nil]; XCTAssertEqual(value.count, 4); XCTAssertEqualObjects(value[3][@"type"], @"state"); - XCTAssertEqualObjects(value[3][@"message"], @"Initiate sequence"); + XCTAssertEqualObjects(value[3][@"name"], @"Initiate sequence"); XCTAssertEqualObjects(value[3][@"metaData"][@"captain"], @"Bob"); XCTAssertNotNil(value[3][@"timestamp"]); } @@ -231,7 +231,7 @@ - (void)testAlwaysAllowManual { NSArray *value = [self.crumbs arrayValue]; XCTAssertEqual(1, value.count); XCTAssertEqualObjects(value[0][@"type"], @"manual"); - XCTAssertEqualObjects(value[0][@"message"], @"this is a test"); + XCTAssertEqualObjects(value[0][@"name"], @"this is a test"); } /** @@ -338,7 +338,7 @@ - (void)testCallbackFreeConstructors2 { XCTAssertEqual(Bugsnag.client.configuration.breadcrumbs.count, 8); XCTAssertEqualObjects(bc0[@"type"], @"state"); - XCTAssertEqualObjects(bc0[@"message"], @"Bugsnag loaded"); + XCTAssertEqualObjects(bc0[@"name"], @"Bugsnag loaded"); XCTAssertEqual([bc0[@"metaData"] count], 0); XCTAssertEqual([bc1[@"metaData"] count], 1); @@ -350,25 +350,25 @@ - (void)testCallbackFreeConstructors2 { XCTAssertEqual([bc4[@"metaData"] count], 2); XCTAssertEqual([bc6[@"metaData"] count], 2); - XCTAssertEqualObjects(bc1[@"message"], @"manual message"); + XCTAssertEqualObjects(bc1[@"name"], @"manual message"); XCTAssertEqualObjects(bc1[@"type"], @"manual"); - XCTAssertEqualObjects(bc2[@"message"], @"log message"); + XCTAssertEqualObjects(bc2[@"name"], @"log message"); XCTAssertEqualObjects(bc2[@"type"], @"log"); - XCTAssertEqualObjects(bc3[@"message"], @"navigation message"); + XCTAssertEqualObjects(bc3[@"name"], @"navigation message"); XCTAssertEqualObjects(bc3[@"type"], @"navigation"); - XCTAssertEqualObjects(bc4[@"message"], @"process message"); + XCTAssertEqualObjects(bc4[@"name"], @"process message"); XCTAssertEqualObjects(bc4[@"type"], @"process"); - XCTAssertEqualObjects(bc5[@"message"], @"request message"); + XCTAssertEqualObjects(bc5[@"name"], @"request message"); XCTAssertEqualObjects(bc5[@"type"], @"request"); - XCTAssertEqualObjects(bc6[@"message"], @"state message"); + XCTAssertEqualObjects(bc6[@"name"], @"state message"); XCTAssertEqualObjects(bc6[@"type"], @"state"); - XCTAssertEqualObjects(bc7[@"message"], @"user message"); + XCTAssertEqualObjects(bc7[@"name"], @"user message"); XCTAssertEqualObjects(bc7[@"type"], @"user"); } @@ -387,8 +387,8 @@ - (void)testCallbackFreeConstructors3 { NSDictionary *bc1 = [Bugsnag.client.configuration.breadcrumbs arrayValue][1]; NSDictionary *bc2 = [Bugsnag.client.configuration.breadcrumbs arrayValue][2]; - XCTAssertEqualObjects(bc1[@"message"], @"message1"); - XCTAssertEqualObjects(bc2[@"message"], @"message2"); + XCTAssertEqualObjects(bc1[@"name"], @"message1"); + XCTAssertEqualObjects(bc2[@"name"], @"message2"); XCTAssertEqual([bc1[@"metaData"] count], 0); XCTAssertEqual([bc2[@"metaData"] count], 0); diff --git a/Tests/BugsnagSinkTests.m b/Tests/BugsnagSinkTests.m index d7b69634a..d8138cbb4 100644 --- a/Tests/BugsnagSinkTests.m +++ b/Tests/BugsnagSinkTests.m @@ -138,7 +138,7 @@ - (void)testEventBreadcrumbs { [self.processedData[@"events"] firstObject][@"breadcrumbs"]; XCTAssertEqual(2, breadcrumbs.count); for (int i = 0; i < breadcrumbs.count; i++) { - XCTAssertEqualObjects(expected[i][@"message"], breadcrumbs[i][@"message"]); + XCTAssertEqualObjects(expected[i][@"name"], breadcrumbs[i][@"message"]); XCTAssertEqualObjects(expected[i][@"type"], breadcrumbs[i][@"type"]); XCTAssertEqualObjects(expected[i][@"timestamp"], breadcrumbs[i][@"timestamp"]); XCTAssertEqualObjects(expected[i][@"metadata"], breadcrumbs[i][@"metadata"]); diff --git a/features/steps/ios_steps.rb b/features/steps/ios_steps.rb index 153705f12..f1af9b1d0 100644 --- a/features/steps/ios_steps.rb +++ b/features/steps/ios_steps.rb @@ -102,7 +102,7 @@ crumbs = read_key_path(find_request(0)[:body], "events.0.breadcrumbs") assert_not_equal(0, crumbs.length, "There are no breadcrumbs on this event") match = crumbs.detect do |crumb| - crumb["message"] == string && crumb["type"] == type + crumb["name"] == string && crumb["type"] == type end assert_not_nil(match, "No crumb matches the provided message and type") end @@ -111,7 +111,7 @@ crumbs = read_key_path(find_request(0)[:body], "events.0.breadcrumbs") assert_not_equal(0, crumbs.length, "There are no breadcrumbs on this event") match = crumbs.detect do |crumb| - crumb["message"] == string + crumb["name"] == string end assert_not_nil(match, "No crumb matches the provided message") end From 0ac9e5f1fc0ebada6363e48ab458b58b1021ce59 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Thu, 19 Mar 2020 17:16:42 +0000 Subject: [PATCH 12/17] feat: add OnBreadcrumb callback --- CHANGELOG.md | 3 + OSX/Bugsnag.xcodeproj/project.pbxproj | 4 + Source/Bugsnag.h | 19 +++ Source/Bugsnag.m | 13 ++ Source/BugsnagBreadcrumbs.m | 21 ++- Source/BugsnagConfiguration.h | 26 ++++ Source/BugsnagConfiguration.m | 14 ++ Tests/BugsnagOnBreadcrumbTest.m | 195 ++++++++++++++++++++++++++ UPGRADING.md | 3 + iOS/Bugsnag.xcodeproj/project.pbxproj | 4 + 10 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 Tests/BugsnagOnBreadcrumbTest.m diff --git a/CHANGELOG.md b/CHANGELOG.md index b5cf3be48..5358a99bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ Bugsnag Notifiers on other platforms. * Remove unused APIs on `BugsnagEvent` interface [#498](https://github.com/bugsnag/bugsnag-cocoa/pull/498) +* Allow addition/removal of `OnBreadcrumb` callbacks +[#508](https://github.com/bugsnag/bugsnag-cocoa/pull/508) + * Remove unused APIs from `BugsnagMetadata` interface [#501](https://github.com/bugsnag/bugsnag-cocoa/pull/501) diff --git a/OSX/Bugsnag.xcodeproj/project.pbxproj b/OSX/Bugsnag.xcodeproj/project.pbxproj index 682d1c876..cbec5ffde 100644 --- a/OSX/Bugsnag.xcodeproj/project.pbxproj +++ b/OSX/Bugsnag.xcodeproj/project.pbxproj @@ -162,6 +162,7 @@ E79E6BDA1F4E3850002B35F9 /* BSG_RFC3339DateTool.m in Sources */ = {isa = PBXBuildFile; fileRef = E79E6B6E1F4E3850002B35F9 /* BSG_RFC3339DateTool.m */; }; E79E6BDB1F4E3850002B35F9 /* BSG_KSCrashReportFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = E79E6B711F4E3850002B35F9 /* BSG_KSCrashReportFilter.h */; }; E79E6BDC1F4E3850002B35F9 /* BSG_KSCrashReportFilterCompletion.h in Headers */ = {isa = PBXBuildFile; fileRef = E79E6B721F4E3850002B35F9 /* BSG_KSCrashReportFilterCompletion.h */; }; + E7AB4B9E2423E184004F015A /* BugsnagOnBreadcrumbTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E7AB4B9D2423E184004F015A /* BugsnagOnBreadcrumbTest.m */; }; E7CE78BB1FD94E77001D07E0 /* KSCrashReportConverter_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7CE78991FD94E60001D07E0 /* KSCrashReportConverter_Tests.m */; }; E7CE78BC1FD94E77001D07E0 /* KSCrashReportStore_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7CE78951FD94E5F001D07E0 /* KSCrashReportStore_Tests.m */; }; E7CE78BE1FD94E77001D07E0 /* KSCrashSentry_NSException_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7CE78911FD94E5F001D07E0 /* KSCrashSentry_NSException_Tests.m */; }; @@ -359,6 +360,7 @@ E79E6B6E1F4E3850002B35F9 /* BSG_RFC3339DateTool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BSG_RFC3339DateTool.m; path = ../Source/KSCrash/Source/KSCrash/Recording/Tools/BSG_RFC3339DateTool.m; sourceTree = SOURCE_ROOT; }; E79E6B711F4E3850002B35F9 /* BSG_KSCrashReportFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BSG_KSCrashReportFilter.h; path = ../Source/KSCrash/Source/KSCrash/Reporting/Filters/BSG_KSCrashReportFilter.h; sourceTree = SOURCE_ROOT; }; E79E6B721F4E3850002B35F9 /* BSG_KSCrashReportFilterCompletion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BSG_KSCrashReportFilterCompletion.h; path = ../Source/KSCrash/Source/KSCrash/Reporting/Filters/BSG_KSCrashReportFilterCompletion.h; sourceTree = SOURCE_ROOT; }; + E7AB4B9D2423E184004F015A /* BugsnagOnBreadcrumbTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagOnBreadcrumbTest.m; sourceTree = ""; }; E7CE78871FD94E5F001D07E0 /* KSMach_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSMach_Tests.m; sourceTree = ""; }; E7CE78881FD94E5F001D07E0 /* NSError+SimpleConstructor_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+SimpleConstructor_Tests.m"; sourceTree = ""; }; E7CE78891FD94E5F001D07E0 /* KSFileUtils_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSFileUtils_Tests.m; sourceTree = ""; }; @@ -503,6 +505,7 @@ 8A2C8FAF1C6BC1F700846019 /* Tests */ = { isa = PBXGroup; children = ( + E7AB4B9D2423E184004F015A /* BugsnagOnBreadcrumbTest.m */, 00F9393B23FD2D9B008C7073 /* BugsnagTestsDummyClass.h */, 00F9393A23FD2D9B008C7073 /* BugsnagTestsDummyClass.m */, 00F9393123FC168F008C7073 /* BugsnagBaseUnitTest.h */, @@ -999,6 +1002,7 @@ E762E9F91F73F7F300E82B43 /* BugsnagHandledStateTest.m in Sources */, E7CE78C51FD94E77001D07E0 /* KSLogger_Tests.m in Sources */, E7CE78C11FD94E77001D07E0 /* KSCrashState_Tests.m in Sources */, + E7AB4B9E2423E184004F015A /* BugsnagOnBreadcrumbTest.m in Sources */, E7CE78C31FD94E77001D07E0 /* KSFileUtils_Tests.m in Sources */, E7CE78BC1FD94E77001D07E0 /* KSCrashReportStore_Tests.m in Sources */, E79148611FD82BB7003EFEBF /* BugsnagSessionTrackerTest.m in Sources */, diff --git a/Source/Bugsnag.h b/Source/Bugsnag.h index d5b90f49e..58ecf4cee 100644 --- a/Source/Bugsnag.h +++ b/Source/Bugsnag.h @@ -338,4 +338,23 @@ static NSString *_Nonnull const BugsnagSeverityInfo = @"info"; */ + (void)removeOnSendBlock:(BugsnagOnSendBlock _Nonnull)block; +// ============================================================================= +// MARK: - onBreadcrumb +// ============================================================================= + +/** + * Add a callback to be invoked when a breadcrumb is captured by Bugsnag, to + * change the breadcrumb contents as needed + * + * @param block A block which returns YES if the breadcrumb should be captured + */ ++ (void)addOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block; + +/** + * Remove the callback that would be invoked when a breadcrumb is captured. + * + * @param block The block to be removed. + */ ++ (void)removeOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block; + @end diff --git a/Source/Bugsnag.m b/Source/Bugsnag.m index d0dd8c882..c8b71652e 100644 --- a/Source/Bugsnag.m +++ b/Source/Bugsnag.m @@ -323,6 +323,19 @@ + (void)removeOnSendBlock:(BugsnagOnSendBlock _Nonnull)block [[self configuration] removeOnSendBlock:block]; } +// ============================================================================= +// MARK: - OnBreadcrumb +// ============================================================================= + ++ (void)addOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block { + [[self configuration] addOnBreadcrumbBlock:block]; +} + + ++ (void)removeOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block { + [[self configuration] removeOnBreadcrumbBlock:block]; +} + @end // diff --git a/Source/BugsnagBreadcrumbs.m b/Source/BugsnagBreadcrumbs.m index 96bda7761..70abe5b77 100644 --- a/Source/BugsnagBreadcrumbs.m +++ b/Source/BugsnagBreadcrumbs.m @@ -12,6 +12,10 @@ #import "BugsnagLogger.h" #import "Private.h" +@interface BugsnagConfiguration () +@property(nonatomic) NSMutableArray *onBreadcrumbBlocks; +@end + @interface BugsnagBreadcrumb () + (instancetype _Nullable)breadcrumbWithBlock: (BSGBreadcrumbConfiguration _Nonnull)block; @@ -58,7 +62,8 @@ - (void)addBreadcrumbWithBlock: return; } BugsnagBreadcrumb *crumb = [BugsnagBreadcrumb breadcrumbWithBlock:block]; - if (crumb) { + + if (crumb != nil && [self shouldSendBreadcrumb:crumb]) { [self resizeToFitCapacity:self.capacity - 1]; dispatch_barrier_sync(self.readWriteQueue, ^{ [self.breadcrumbs addObject:crumb]; @@ -82,6 +87,20 @@ - (void)addBreadcrumbWithBlock: } } +- (BOOL)shouldSendBreadcrumb:(BugsnagBreadcrumb *)crumb { + BugsnagConfiguration *configuration = [Bugsnag configuration]; + for (BugsnagOnBreadcrumbBlock block in configuration.onBreadcrumbBlocks) { + @try { + if (!block(crumb)) { + return NO; + } + } @catch (NSException *exception) { + bsg_log_err(@"Error from onBreadcrumb callback: %@", exception); + } + } + return YES; +} + - (NSArray *)cachedBreadcrumbs { __block NSArray *cache = nil; dispatch_barrier_sync(self.readWriteQueue, ^{ diff --git a/Source/BugsnagConfiguration.h b/Source/BugsnagConfiguration.h index e961cc71b..590be14a8 100644 --- a/Source/BugsnagConfiguration.h +++ b/Source/BugsnagConfiguration.h @@ -60,6 +60,13 @@ typedef void (^BugsnagOnErrorBlock)(BugsnagEvent *_Nonnull event); */ typedef bool (^BugsnagOnSendBlock)(BugsnagEvent *_Nonnull event); +/** + * A configuration block for modifying a captured breadcrumb + * + * @param breadcrumb The breadcrumb + */ +typedef BOOL (^BugsnagOnBreadcrumbBlock)(BugsnagBreadcrumb *_Nonnull breadcrumb); + /** * A configuration block for modifying a session. Intended for internal usage only. * @@ -248,6 +255,25 @@ typedef NS_OPTIONS(NSUInteger, BSGErrorType) { - (void)addPlugin:(id _Nonnull)plugin; +// ============================================================================= +// MARK: - onBreadcrumb +// ============================================================================= + +/** + * Add a callback to be invoked when a breadcrumb is captured by Bugsnag, to + * change the breadcrumb contents as needed + * + * @param block A block which returns YES if the breadcrumb should be captured + */ +- (void)addOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block; + +/** + * Remove the callback that would be invoked when a breadcrumb is captured. + * + * @param block The block to be removed. + */ +- (void)removeOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block; + /** * Should the specified type of breadcrumb be recorded? * diff --git a/Source/BugsnagConfiguration.m b/Source/BugsnagConfiguration.m index 2deb079f3..831627304 100644 --- a/Source/BugsnagConfiguration.m +++ b/Source/BugsnagConfiguration.m @@ -67,6 +67,7 @@ @interface BugsnagConfiguration () * Hooks for modifying sessions before they are sent to Bugsnag. Intended for internal use only by React Native/Unity. */ @property(nonatomic, readwrite, strong) NSMutableArray *onSessionBlocks; +@property(nonatomic, readwrite, strong) NSMutableArray *onBreadcrumbBlocks; @property(nonatomic, readwrite, strong) NSMutableSet *plugins; @property(readonly, retain, nullable) NSURL *notifyURL; @property(readonly, retain, nullable) NSURL *sessionURL; @@ -138,6 +139,7 @@ - (instancetype _Nonnull)initWithApiKey:(NSString *_Nonnull)apiKey _notifyURL = [NSURL URLWithString:BSGDefaultNotifyUrl]; _onSendBlocks = [NSMutableArray new]; _onSessionBlocks = [NSMutableArray new]; + _onBreadcrumbBlocks = [NSMutableArray new]; _plugins = [NSMutableSet new]; _notifyReleaseStages = nil; _breadcrumbs = [BugsnagBreadcrumbs new]; @@ -237,6 +239,18 @@ - (void)removeOnSessionBlock:(BugsnagOnSessionBlock)block { [(NSMutableArray *)self.onSessionBlocks removeObject:block]; } +// ============================================================================= +// MARK: - onBreadcrumbBlock +// ============================================================================= + +- (void)addOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block { + [(NSMutableArray *)self.onBreadcrumbBlocks addObject:[block copy]]; +} + +- (void)removeOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block { + [(NSMutableArray *)self.onBreadcrumbBlocks removeObject:block]; +} + - (NSDictionary *)errorApiHeaders { return @{ kHeaderApiPayloadVersion: @"4.0", diff --git a/Tests/BugsnagOnBreadcrumbTest.m b/Tests/BugsnagOnBreadcrumbTest.m new file mode 100644 index 000000000..06fd03940 --- /dev/null +++ b/Tests/BugsnagOnBreadcrumbTest.m @@ -0,0 +1,195 @@ +// +// BugsnagOnBreadcrumbTest.m +// Tests +// +// Created by Jamie Lynch on 19/03/2020. +// Copyright © 2020 Bugsnag. All rights reserved. +// + +#import + +#import "Bugsnag.h" +#import "BugsnagConfiguration.h" +#import "BugsnagTestConstants.h" +#import "BugsnagBreadcrumbs.h" + +@interface BugsnagConfiguration () +@property NSMutableArray *onBreadcrumbBlocks; +@property BugsnagBreadcrumbs *breadcrumbs; +@end + +@interface BugsnagBreadcrumbs () +@property(nonatomic, readwrite, strong) NSMutableArray *breadcrumbs; +@end + +@interface BugsnagOnBreadcrumbTest : XCTestCase +@end + +@implementation BugsnagOnBreadcrumbTest + + +/** + * Test that onBreadcrumb blocks get called once added + */ +- (void)testAddOnBreadcrumbBlock { + + // Setup + __block XCTestExpectation *expectation = [self expectationWithDescription:@"Remove On Breadcrumb Block"]; + BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; + [config setEndpointsForNotify:@"http://notreal.bugsnag.com" sessions:@"http://notreal.bugsnag.com"]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 0); + BugsnagOnBreadcrumbBlock crumbBlock = ^(BugsnagBreadcrumb * _Nonnull crumb) { + // We expect the breadcrumb block to be called + [expectation fulfill]; + return YES; + }; + [config addOnBreadcrumbBlock:crumbBlock]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 1); + + // Call onbreadcrumb blocks + [Bugsnag startBugsnagWithConfiguration:config]; + [Bugsnag leaveBreadcrumbWithMessage:@"Hello"]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; +} + +/** + * Test that onBreadcrumb blocks do not get called once they've been removed + */ +- (void)testRemoveOnBreadcrumbBlock { + // Setup + // We expect NOT to be called + __block XCTestExpectation *calledExpectation = [self expectationWithDescription:@"Remove On Breadcrumb Block"]; + calledExpectation.inverted = YES; + + BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; + [config setEndpointsForNotify:@"http://notreal.bugsnag.com" sessions:@"http://notreal.bugsnag.com"]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 0); + BugsnagOnBreadcrumbBlock crumbBlock = ^(BugsnagBreadcrumb * _Nonnull crumb) { + [calledExpectation fulfill]; + return YES; + }; + + // It's there (and from other tests we know it gets called) and then it's not there + [config addOnBreadcrumbBlock:crumbBlock]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 1); + [config removeOnBreadcrumbBlock:crumbBlock]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 0); + + [Bugsnag startBugsnagWithConfiguration:config]; + [Bugsnag leaveBreadcrumbWithMessage:@"Hello"]; + + // Wait a second NOT to be called + [self waitForExpectationsWithTimeout:1.0 handler:nil]; +} +/** + * Test that an onBreadcrumb block is called after being added, then NOT called after being removed. + * This test could be expanded to verify the behaviour when multiple blocks are added. + */ +- (void)testAddOnBreadcrumbBlockThenRemove { + + __block int called = 0; // A counter + + // Setup + __block XCTestExpectation *expectation1 = [self expectationWithDescription:@"Remove On Breadcrumb Block 1"]; + __block XCTestExpectation *expectation2 = [self expectationWithDescription:@"Remove On Breadcrumb Block 2"]; + expectation2.inverted = YES; + + BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; + [config setEndpointsForNotify:@"http://notreal.bugsnag.com" sessions:@"http://notreal.bugsnag.com"]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 0); + + BugsnagOnBreadcrumbBlock crumbBlock = ^(BugsnagBreadcrumb * _Nonnull crumb) { + switch (called) { + case 0: + [expectation1 fulfill]; + break; + case 1: + [expectation2 fulfill]; + break; + } + return YES; + }; + + [config addOnBreadcrumbBlock:crumbBlock]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 1); + + // Call onbreadcrumb blocks + [Bugsnag startBugsnagWithConfiguration:config]; + [Bugsnag leaveBreadcrumbWithMessage:@"Hello"]; + [self waitForExpectations:@[expectation1] timeout:1.0]; + + // Check it's NOT called once the block's deleted + called++; + [config removeOnBreadcrumbBlock:crumbBlock]; + [Bugsnag leaveBreadcrumbWithMessage:@"Hello"]; + [self waitForExpectations:@[expectation2] timeout:1.0]; +} + +/** + * Make sure slightly invalid removals and duplicate additions don't break things + */ +- (void)testRemoveNonexistentOnBreadcrumbBlocks { + BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 0); + BugsnagOnBreadcrumbBlock crumbBlock1 = ^(BugsnagBreadcrumb * _Nonnull crumb) { + return YES; + }; + BugsnagOnBreadcrumbBlock crumbBlock2 = ^(BugsnagBreadcrumb * _Nonnull crumb) { + return YES; + }; + + [config addOnBreadcrumbBlock:crumbBlock1]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 1); + [config removeOnBreadcrumbBlock:crumbBlock2]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 1); + [config removeOnBreadcrumbBlock:crumbBlock1]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 0); + [config removeOnBreadcrumbBlock:crumbBlock2]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 0); + [config removeOnBreadcrumbBlock:crumbBlock1]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 0); + + [config addOnBreadcrumbBlock:crumbBlock1]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 1); + [config addOnBreadcrumbBlock:crumbBlock1]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 2); + [config addOnBreadcrumbBlock:crumbBlock1]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 3); +} + +/** + * Test that onBreadcrumb blocks mutate a crumb + */ +- (void)testAddOnBreadcrumbMutation { + BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; + [config setEndpointsForNotify:@"http://notreal.bugsnag.com" sessions:@"http://notreal.bugsnag.com"]; + [config addOnBreadcrumbBlock:^(BugsnagBreadcrumb * _Nonnull crumb) { + crumb.message = @"Foo"; + return YES; + }]; + + // Call onbreadcrumb blocks + [Bugsnag startBugsnagWithConfiguration:config]; + XCTAssertEqual([[config onBreadcrumbBlocks] count], 1); + BugsnagBreadcrumb *crumb = [[config breadcrumbs].breadcrumbs firstObject]; + XCTAssertEqualObjects(@"Foo", crumb.message); +} + +/** + * Test that onBreadcrumb blocks can discard crumbs + */ +- (void)testAddOnBreadcrumbRejection { + BugsnagConfiguration *config = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1]; + [config setEndpointsForNotify:@"http://notreal.bugsnag.com" sessions:@"http://notreal.bugsnag.com"]; + [config addOnBreadcrumbBlock:^(BugsnagBreadcrumb * _Nonnull crumb) { + return NO; + }]; + + // Call onbreadcrumb blocks + [Bugsnag startBugsnagWithConfiguration:config]; + XCTAssertEqual([[config breadcrumbs].breadcrumbs count], 0); + [Bugsnag leaveBreadcrumbWithMessage:@"Hello"]; + XCTAssertEqual([[config breadcrumbs].breadcrumbs count], 0); +} + +@end diff --git a/UPGRADING.md b/UPGRADING.md index 87020135f..5f16f593f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -33,6 +33,9 @@ The exact error is available using the `BSGConfigurationErrorDomain` and + config.persistUserData() + config.deletePersistedUserData() + ++ config.add(onBreadcrumb:) ++ config.remove(onBreadcrumb:) ``` #### Renames diff --git a/iOS/Bugsnag.xcodeproj/project.pbxproj b/iOS/Bugsnag.xcodeproj/project.pbxproj index 8fa7049b5..a7c54cd7d 100644 --- a/iOS/Bugsnag.xcodeproj/project.pbxproj +++ b/iOS/Bugsnag.xcodeproj/project.pbxproj @@ -304,6 +304,7 @@ E79148291FD828E6003EFEBF /* BugsnagUser.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E72BF77D1FC86A7A004BE82F /* BugsnagUser.h */; }; E791482A1FD828E6003EFEBF /* BugsnagKSCrashSysInfoParser.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E7EB06721FCDAF2000C076A6 /* BugsnagKSCrashSysInfoParser.h */; }; E794E8031F9F743D00A67EE7 /* BugsnagKeys.h in Headers */ = {isa = PBXBuildFile; fileRef = E794E8021F9F743D00A67EE7 /* BugsnagKeys.h */; }; + E7AB4B9C2423E16C004F015A /* BugsnagOnBreadcrumbTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E7AB4B9B2423E16C004F015A /* BugsnagOnBreadcrumbTest.m */; }; E7B3291A1FD707EC0098FC47 /* KSCrashReportConverter_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7B329171FD707EC0098FC47 /* KSCrashReportConverter_Tests.m */; }; E7B970311FD702DA00590C27 /* KSLogger_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7B970301FD702DA00590C27 /* KSLogger_Tests.m */; }; E7B970341FD7031500590C27 /* XCTestCase+KSCrash.m in Sources */ = {isa = PBXBuildFile; fileRef = E7B970331FD7031500590C27 /* XCTestCase+KSCrash.m */; }; @@ -615,6 +616,7 @@ E78C1EFD1FCC778700B976D3 /* BugsnagUserTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagUserTest.m; path = ../Tests/BugsnagUserTest.m; sourceTree = SOURCE_ROOT; }; E794E8021F9F743D00A67EE7 /* BugsnagKeys.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BugsnagKeys.h; path = ../Source/BugsnagKeys.h; sourceTree = SOURCE_ROOT; }; E79FEBE61F4CB1320048FAD6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + E7AB4B9B2423E16C004F015A /* BugsnagOnBreadcrumbTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagOnBreadcrumbTest.m; path = ../../Tests/BugsnagOnBreadcrumbTest.m; sourceTree = ""; }; E7B329171FD707EC0098FC47 /* KSCrashReportConverter_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KSCrashReportConverter_Tests.m; path = ../Tests/KSCrash/KSCrashReportConverter_Tests.m; sourceTree = SOURCE_ROOT; }; E7B970301FD702DA00590C27 /* KSLogger_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KSLogger_Tests.m; path = ../Tests/KSCrash/KSLogger_Tests.m; sourceTree = SOURCE_ROOT; }; E7B970321FD7031500590C27 /* XCTestCase+KSCrash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "XCTestCase+KSCrash.h"; path = "../Tests/KSCrash/XCTestCase+KSCrash.h"; sourceTree = SOURCE_ROOT; }; @@ -818,6 +820,7 @@ 00F9393723FC4F63008C7073 /* BugsnagTestsDummyClass.h */, 00F9393823FC4F64008C7073 /* BugsnagTestsDummyClass.m */, E72AE1FF241A57B100ED8972 /* BugsnagPluginTest.m */, + E7AB4B9B2423E16C004F015A /* BugsnagOnBreadcrumbTest.m */, ); name = Tests; path = BugsnagTests; @@ -1303,6 +1306,7 @@ 000E6E9E23D8690F009D8194 /* BugsnagSwiftConfigurationTests.swift in Sources */, E70EE07E1FD703D600FA745C /* NSError+SimpleConstructor_Tests.m in Sources */, E70EE0931FD706C700FA745C /* KSFileUtils_Tests.m in Sources */, + E7AB4B9C2423E16C004F015A /* BugsnagOnBreadcrumbTest.m in Sources */, E70EE08E1FD705A700FA745C /* KSSignalInfo_Tests.m in Sources */, E733A76B1FD7091F003EAA29 /* KSCrashSentry_Signal_Tests.m in Sources */, E784D2561FD70B3E004B01E1 /* KSMach_Tests.m in Sources */, From 2c99701a23ab1d74b7a74ded4fa81f0e78d60efd Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Fri, 27 Mar 2020 17:03:31 +0000 Subject: [PATCH 13/17] add missing test to tvOS project --- tvOS/Bugsnag.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tvOS/Bugsnag.xcodeproj/project.pbxproj b/tvOS/Bugsnag.xcodeproj/project.pbxproj index 2befcdd8f..7a3fd681e 100644 --- a/tvOS/Bugsnag.xcodeproj/project.pbxproj +++ b/tvOS/Bugsnag.xcodeproj/project.pbxproj @@ -143,6 +143,7 @@ E76617D11F4E459C0094CECF /* BSG_KSCrashReportFilterCompletion.h in Headers */ = {isa = PBXBuildFile; fileRef = E76617671F4E459C0094CECF /* BSG_KSCrashReportFilterCompletion.h */; }; E77526C4242D0E3A0077A42F /* BugsnagBreadcrumbs.m in Sources */ = {isa = PBXBuildFile; fileRef = E77526C2242D0E3A0077A42F /* BugsnagBreadcrumbs.m */; }; E77526C5242D0E3A0077A42F /* BugsnagBreadcrumbs.h in Headers */ = {isa = PBXBuildFile; fileRef = E77526C3242D0E3A0077A42F /* BugsnagBreadcrumbs.h */; }; + E77526C7242E694C0077A42F /* BugsnagOnBreadcrumbTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E77526C6242E694C0077A42F /* BugsnagOnBreadcrumbTest.m */; }; E79148771FD82E6D003EFEBF /* BugsnagSession.h in Headers */ = {isa = PBXBuildFile; fileRef = E79148641FD82E6A003EFEBF /* BugsnagSession.h */; }; E79148781FD82E6D003EFEBF /* BugsnagKSCrashSysInfoParser.h in Headers */ = {isa = PBXBuildFile; fileRef = E79148651FD82E6A003EFEBF /* BugsnagKSCrashSysInfoParser.h */; }; E79148791FD82E6D003EFEBF /* BugsnagUser.h in Headers */ = {isa = PBXBuildFile; fileRef = E79148661FD82E6A003EFEBF /* BugsnagUser.h */; }; @@ -343,6 +344,7 @@ E76617671F4E459C0094CECF /* BSG_KSCrashReportFilterCompletion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BSG_KSCrashReportFilterCompletion.h; path = ../Source/KSCrash/Source/KSCrash/Reporting/Filters/BSG_KSCrashReportFilterCompletion.h; sourceTree = SOURCE_ROOT; }; E77526C2242D0E3A0077A42F /* BugsnagBreadcrumbs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagBreadcrumbs.m; path = ../Source/BugsnagBreadcrumbs.m; sourceTree = ""; }; E77526C3242D0E3A0077A42F /* BugsnagBreadcrumbs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagBreadcrumbs.h; path = ../Source/BugsnagBreadcrumbs.h; sourceTree = ""; }; + E77526C6242E694C0077A42F /* BugsnagOnBreadcrumbTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BugsnagOnBreadcrumbTest.m; path = ../Tests/BugsnagOnBreadcrumbTest.m; sourceTree = ""; }; E79148641FD82E6A003EFEBF /* BugsnagSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagSession.h; path = ../Source/BugsnagSession.h; sourceTree = ""; }; E79148651FD82E6A003EFEBF /* BugsnagKSCrashSysInfoParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagKSCrashSysInfoParser.h; path = ../Source/BugsnagKSCrashSysInfoParser.h; sourceTree = ""; }; E79148661FD82E6A003EFEBF /* BugsnagUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagUser.h; path = ../Source/BugsnagUser.h; sourceTree = ""; }; @@ -426,6 +428,7 @@ 8A8D511A1D41343500D33797 = { isa = PBXGroup; children = ( + E77526C6242E694C0077A42F /* BugsnagOnBreadcrumbTest.m */, 8A8D51261D41343500D33797 /* tvOS */, 8AB151131D41356800C9B218 /* Tests */, 8A8D51251D41343500D33797 /* Products */, @@ -983,6 +986,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E77526C7242E694C0077A42F /* BugsnagOnBreadcrumbTest.m in Sources */, 8ACF0F74201812AD00173809 /* BugsnagSinkTests.m in Sources */, 4B3CD2BD22C5676800DBFF33 /* BugsnagThreadTest.m in Sources */, 00D7ACA723E98A5D00FBE4A7 /* BugsnagEventTests.m in Sources */, From 8bb7c5762d235a82d66a3504c41a3e6494461269 Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Fri, 27 Mar 2020 17:30:55 +0000 Subject: [PATCH 14/17] post-merge fixup --- Source/BugsnagClient.m | 1 + Source/BugsnagHandledState.m | 4 ++-- Tests/BugsnagClientTests.m | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/BugsnagClient.m b/Source/BugsnagClient.m index f73f6f39e..2ecd1d227 100644 --- a/Source/BugsnagClient.m +++ b/Source/BugsnagClient.m @@ -270,6 +270,7 @@ @interface BugsnagConfiguration () @interface BugsnagEvent () @property(readonly, copy, nonnull) NSDictionary *overrides; @property(readwrite) NSUInteger depth; +@property(readonly, nonnull) BugsnagHandledState *handledState; @end @interface BugsnagMetadata () diff --git a/Source/BugsnagHandledState.m b/Source/BugsnagHandledState.m index 8a07eb517..bc5eb8a22 100644 --- a/Source/BugsnagHandledState.m +++ b/Source/BugsnagHandledState.m @@ -19,10 +19,10 @@ BSGSeverity BSGParseSeverity(NSString *severity) { NSString *BSGFormatSeverity(BSGSeverity severity) { switch (severity) { - case BSGSeverityInfo: - return BSGKeyInfo; case BSGSeverityError: return BSGKeyError; + case BSGSeverityInfo: + return BSGKeyInfo; case BSGSeverityWarning: return BSGKeyWarning; } diff --git a/Tests/BugsnagClientTests.m b/Tests/BugsnagClientTests.m index 597c05a33..5acf8d478 100644 --- a/Tests/BugsnagClientTests.m +++ b/Tests/BugsnagClientTests.m @@ -10,6 +10,7 @@ #import "BugsnagBreadcrumbs.h" #import "BugsnagClient.h" #import "BugsnagTestConstants.h" +#import "BugsnagKeys.h" #import @interface BugsnagClientTests : XCTestCase @@ -32,6 +33,8 @@ @interface BugsnagConfiguration () @property(readonly, strong, nullable) BugsnagBreadcrumbs *breadcrumbs; @end +NSString *BSGFormatSeverity(BSGSeverity severity); + @implementation BugsnagClientTests /** @@ -78,7 +81,7 @@ - (void)testAutomaticNotifyBreadcrumbData { NSDictionary *metadata = [breadcrumb valueForKey:@"metaData"]; XCTAssertEqualObjects([breadcrumb valueForKey:@"type"], @"error"); - XCTAssertEqualObjects([breadcrumb valueForKey:@"message"], eventErrorClass); + XCTAssertEqualObjects([breadcrumb valueForKey:@"name"], eventErrorClass); XCTAssertEqualObjects([metadata valueForKey:@"errorClass"], eventErrorClass); XCTAssertEqualObjects([metadata valueForKey:@"message"], eventErrorMessage); XCTAssertEqual((bool)[metadata valueForKey:@"unhandled"], eventUnhandled); From c6fe42eedd1987eca5fa6ba42e462a22a19b204d Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Fri, 27 Mar 2020 19:00:08 +0000 Subject: [PATCH 15/17] (fix) Additional reversion of API-breaking changes, addressed a small review issue --- Source/BugsnagClient.m | 21 ++++++++++++++------- Source/BugsnagEvent.m | 1 - 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Source/BugsnagClient.m b/Source/BugsnagClient.m index 2ecd1d227..4b0eb9a28 100644 --- a/Source/BugsnagClient.m +++ b/Source/BugsnagClient.m @@ -778,15 +778,22 @@ - (void)notify:(NSException *)exception metadata:[event.metadata copy] config:[self.configuration.config toDictionary] discardDepth:depth]; - + + // A basic set of event metadata + NSMutableDictionary *metadata = [@{ + BSGKeyErrorClass : eventErrorClass, + BSGKeyUnhandled : [[event handledState] unhandled] ? @YES : @NO, + BSGKeySeverity : BSGFormatSeverity(event.severity) + } mutableCopy]; + + // Only include the eventMessage if it contains something + if (eventMessage && [eventMessage length] > 0) { + [metadata setValue:eventMessage forKey:BSGKeyName]; + } + [self addAutoBreadcrumbOfType:BSGBreadcrumbTypeError withMessage:eventErrorClass - andMetadata:@{ - BSGKeyMessage : eventMessage, - BSGKeyErrorClass : eventErrorClass, - BSGKeyUnhandled : [[event handledState] unhandled] ? @YES : @NO, - BSGKeySeverity : BSGFormatSeverity(event.severity) - }]; + andMetadata:metadata]; [self flushPendingReports]; } diff --git a/Source/BugsnagEvent.m b/Source/BugsnagEvent.m index dac3341ad..95c80732c 100644 --- a/Source/BugsnagEvent.m +++ b/Source/BugsnagEvent.m @@ -622,7 +622,6 @@ - (void)setOverrideProperty:(NSString *)key value:(id)value { } _overrides = metadata; } - } - (NSDictionary *)toJson { From b7a6ea6416be16abc6baa1d68f85ecddbea837b1 Mon Sep 17 00:00:00 2001 From: Robin Macharg Date: Fri, 27 Mar 2020 19:03:50 +0000 Subject: [PATCH 16/17] Small post-merge fixup --- Tests/BugsnagClientTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/BugsnagClientTests.m b/Tests/BugsnagClientTests.m index 5acf8d478..1de6ada9f 100644 --- a/Tests/BugsnagClientTests.m +++ b/Tests/BugsnagClientTests.m @@ -83,7 +83,7 @@ - (void)testAutomaticNotifyBreadcrumbData { XCTAssertEqualObjects([breadcrumb valueForKey:@"type"], @"error"); XCTAssertEqualObjects([breadcrumb valueForKey:@"name"], eventErrorClass); XCTAssertEqualObjects([metadata valueForKey:@"errorClass"], eventErrorClass); - XCTAssertEqualObjects([metadata valueForKey:@"message"], eventErrorMessage); + XCTAssertEqualObjects([metadata valueForKey:@"name"], eventErrorMessage); XCTAssertEqual((bool)[metadata valueForKey:@"unhandled"], eventUnhandled); XCTAssertEqualObjects([metadata valueForKey:@"severity"], eventSeverity); } From 4f1d38a184b25613b631c6583345d95746a44f74 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Mon, 30 Mar 2020 10:13:11 +0100 Subject: [PATCH 17/17] fix: pass mazerunner scenarios --- .../scenarios/HandledErrorOverrideScenario.swift | 2 +- .../iOSTestApp/scenarios/ModifyBreadcrumbScenario.swift | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/HandledErrorOverrideScenario.swift b/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/HandledErrorOverrideScenario.swift index 387a4213e..4104613e9 100644 --- a/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/HandledErrorOverrideScenario.swift +++ b/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/HandledErrorOverrideScenario.swift @@ -22,7 +22,7 @@ class HandledErrorOverrideScenario: Scenario { Bugsnag.notifyError(error) { report in report.errorMessage = "Foo" report.errorClass = "Bar" - var depth: Int = report.value(forKey: "depth") as! Int + let depth: Int = report.value(forKey: "depth") as! Int report.setValue(depth + 2, forKey: "depth") report.metadata["account"] = [ "items": [400,200] diff --git a/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/ModifyBreadcrumbScenario.swift b/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/ModifyBreadcrumbScenario.swift index 102bd76b1..d9e4507d5 100644 --- a/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/ModifyBreadcrumbScenario.swift +++ b/features/fixtures/ios-swift-cocoapods/iOSTestApp/scenarios/ModifyBreadcrumbScenario.swift @@ -5,14 +5,15 @@ class ModifyBreadcrumbScenario: Scenario { override func startBugsnag() { self.config.autoTrackSessions = false; - self.config.add { (event) -> Bool in + + self.config.add(onSend: { event in event.breadcrumbs?.forEach({ crumb in if crumb.message == "Cache cleared" { crumb.message = "Cache locked" } }) - return true; - } + return true + }) super.startBugsnag() }