Skip to content

Commit

Permalink
Merge pull request #486 from bugsnag/robin/Enhance-automatic-breadcru…
Browse files Browse the repository at this point in the history
…mbs-for-orientation-change

(feat) Enhanced orientation breadcrumbs with explicit "from" and "to"
  • Loading branch information
robinmacharg authored Mar 27, 2020
2 parents e9a9c92 + 38c49a3 commit 2526dbb
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 83 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,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

Expand Down
219 changes: 138 additions & 81 deletions Source/BugsnagClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -132,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];

Expand All @@ -143,6 +150,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
*
Expand Down Expand Up @@ -209,6 +253,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 (nonatomic, strong) NSString *lastOrientation;
#endif
@end

@interface BugsnagConfiguration ()
Expand All @@ -231,6 +279,13 @@ - (NSDictionary *_Nonnull)toDictionary;

@implementation BugsnagClient

#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
/**
* Storage for the device orientation. It is "last" whenever an orientation change is received
*/
NSString *_lastOrientation = nil;
#endif

@synthesize configuration;

- (id)initWithConfiguration:(BugsnagConfiguration *)initConfiguration {
Expand All @@ -255,7 +310,6 @@ - (id)initWithConfiguration:(BugsnagConfiguration *)initConfiguration {
BSGKeyUrl : NOTIFIER_URL
} mutableCopy];


NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(
NSCachesDirectory, NSUserDomainMask, YES) firstObject];
if (cacheDir) {
Expand Down Expand Up @@ -290,6 +344,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;
}
Expand All @@ -304,6 +362,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
Expand All @@ -320,10 +381,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",
Expand Down Expand Up @@ -391,7 +450,7 @@ - (void)start {
selector:@selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];

[center addObserver:self
selector:@selector(lowMemoryWarning:)
name:UIApplicationDidReceiveMemoryWarningNotification
Expand All @@ -401,7 +460,6 @@ - (void)start {
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];

[self batteryChanged:nil];
[self orientationChanged:nil];
[self addTerminationObserver:UIApplicationWillTerminateNotification];

#elif TARGET_OS_MAC
Expand Down Expand Up @@ -438,12 +496,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];
Expand Down Expand Up @@ -563,16 +619,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
}];
}];
}

Expand Down Expand Up @@ -720,16 +775,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];
}

Expand Down Expand Up @@ -794,60 +846,44 @@ - (void)batteryChanged:(NSNotification *)notification {
toTabWithName:BSGKeyDeviceState];
}

- (void)orientationChanged:(NSNotification *)notif {
NSString *orientation;
UIDeviceOrientation deviceOrientation =
[UIDevice currentDevice].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; // always ignore unknown breadcrumbs
}

NSDictionary *lastBreadcrumb =
[[self.configuration.breadcrumbs arrayValue] lastObject];
NSString *orientationNotifName =
BSGBreadcrumbNameForNotificationName(notif.name);

if (lastBreadcrumb &&
[orientationNotifName isEqualToString:lastBreadcrumb[BSGKeyName]]) {
NSDictionary *metadata = lastBreadcrumb[BSGKeyMetadata];

if ([orientation isEqualToString:metadata[BSGKeyOrientation]]) {
return; // ignore duplicate orientation event
}
}
/**
* 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;
NSString *orientation = BSGOrientationNameFromEnum(currentDeviceOrientation);

if ([[self configuration] shouldRecordBreadcrumbType:BSGBreadcrumbTypeState]) {
[self addBreadcrumbWithBlock:^(BugsnagBreadcrumb *_Nonnull breadcrumb) {
breadcrumb.type = BSGBreadcrumbTypeState;
breadcrumb.message = orientationNotifName;
breadcrumb.metadata = @{BSGKeyOrientation : orientation};
}];
// No orientation, nothing to be done
if (!orientation) {
return;
}

// Update the Device orientation
// 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;
}

// We have an orientation, it's not a dupe and we have a lastOrientation.
// Send a breadcrumb and preserve the orientation.

[self addAutoBreadcrumbOfType:BSGBreadcrumbTypeState
withMessage:BSGBreadcrumbNameForNotificationName(notification.name)
andMetadata:@{
@"from" : _lastOrientation,
@"to" : orientation
}];

_lastOrientation = orientation;
}

- (void)lowMemoryWarning:(NSNotification *)notif {
Expand All @@ -862,6 +898,27 @@ - (void)lowMemoryWarning:(NSNotification *)notif {
}
#endif

/**
* 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)updateCrashDetectionSettings {
if (self.configuration.autoDetectErrors) {
// Enable all crash detection
Expand Down Expand Up @@ -1025,8 +1082,8 @@ - (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);
}];
}

Expand Down
Loading

0 comments on commit 2526dbb

Please sign in to comment.