Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix missing context for crash, OOM, and app hang errors #1079

Merged
merged 1 commit into from
Apr 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Bugsnag/Client/BugsnagClient+AppHangs.m
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ - (void)appHangDetectedWithThreads:(nonnull NSArray<BugsnagThread *> *)threads {
threads:threads
session:self.sessionTracker.runningSession];

self.appHangEvent.context = self.context;

NSError *writeError = nil;
NSDictionary *json = [self.appHangEvent toJsonWithRedactedKeys:self.configuration.redactedKeys];
if (![BSGJSONSerialization writeJSONObject:json toFile:BSGFileLocations.current.appHangEvent options:0 error:&writeError]) {
Expand Down
2 changes: 2 additions & 0 deletions Bugsnag/Client/BugsnagClient+OutOfMemory.m
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ - (BugsnagEvent *)generateOutOfMemoryEvent {
threads:@[]
session:session];

event.context = self.stateMetadataFromLastLaunch[BSGKeyClient][BSGKeyContext];

return event;
}

Expand Down
26 changes: 26 additions & 0 deletions Bugsnag/Client/BugsnagClient+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,32 @@ NS_ASSUME_NONNULL_BEGIN

@property (readonly, nonatomic) BOOL started;

/// State related metadata
///
/// Upon change this is automatically persisted to disk, making it available when contructing OOM payloads.
/// Is it also added to KSCrashReports under `user.state` by `BSSerializeDataCrashHandler()`.
///
/// Example contents:
///
/// {
/// "app": {
/// "codeBundleId": "com.example.app",
/// "isLaunching": true
/// },
/// "client": {
/// "context": "MyViewController",
/// },
/// "deviceState": {
/// "batteryLevel": 0.5,
/// "charging": false,
/// "lowMemoryWarning": "2021-01-01T15:29:02.170Z",
/// "orientation": "portrait"
/// },
/// "user": {
/// "id": "abc123",
/// "name": "bob"
/// }
/// }
@property (strong, nonatomic) BugsnagMetadata *state;

@property (strong, nonatomic) NSMutableArray *stateEventBlocks;
Expand Down
11 changes: 6 additions & 5 deletions Bugsnag/Client/BugsnagClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,6 @@
// Contains notifier state, under "deviceState" and crash-specific
// information under "crash".
char *statePath;
// Contains properties in the Bugsnag payload overridden by the user before
// it was sent
char *userOverridesJSON;
// User onCrash handler
void (*onCrash)(const BSG_KSCrashReportWriter *writer);
} bsg_g_bugsnag_data;
Expand Down Expand Up @@ -248,7 +245,10 @@ - (instancetype)initWithConfiguration:(BugsnagConfiguration *)configuration {
if ((self = [super init])) {
// Take a shallow copy of the configuration
_configuration = [configuration copy];
_state = [[BugsnagMetadata alloc] initWithDictionary:@{BSGKeyApp: @{BSGKeyIsLaunching: @YES}}];
_state = [[BugsnagMetadata alloc] initWithDictionary:@{
BSGKeyApp: @{BSGKeyIsLaunching: @YES},
BSGKeyClient: BSGDictionaryWithKeyAndObject(BSGKeyContext, _configuration.context)
}];
self.notifier = [BugsnagNotifier new];
self.systemState = [[BugsnagSystemState alloc] initWithConfiguration:configuration];

Expand Down Expand Up @@ -763,11 +763,12 @@ - (void)removeOnBreadcrumbBlock:(BugsnagOnBreadcrumbBlock _Nonnull)block {
}

// =============================================================================
// MARK: - Other methods
// MARK: - Context
// =============================================================================

- (void)setContext:(nullable NSString *)context {
self.configuration.context = context;
[self.state addMetadata:context withKey:BSGKeyContext toSection:BSGKeyClient];
[self notifyObservers:[[BugsnagStateEvent alloc] initWithName:kStateEventContext data:context]];
}

Expand Down
3 changes: 3 additions & 0 deletions Bugsnag/Helpers/BugsnagCollections.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ NSArray * BSGArraySubarrayFromIndex(NSArray *array, NSUInteger index);

// MARK: - NSDictionary

/// Returns a dictionary containing the key and object, or an empty dictionary if the object is nil.
NSDictionary * BSGDictionaryWithKeyAndObject(NSString *key, id _Nullable object);

/**
* Merge values from source dictionary with destination
*
Expand Down
4 changes: 4 additions & 0 deletions Bugsnag/Helpers/BugsnagCollections.m
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ void BSGArrayAddIfNonnull(NSMutableArray *array, id _Nullable object) {

// MARK: - NSDictionary

NSDictionary * BSGDictionaryWithKeyAndObject(NSString *key, id _Nullable object) {
return object ? @{key: (id _Nonnull)object} : @{};
}

NSDictionary *BSGDictMerge(NSDictionary *source, NSDictionary *destination) {
if ([destination count] == 0) {
return source;
Expand Down
1 change: 1 addition & 0 deletions Bugsnag/Helpers/BugsnagKeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ extern NSString *const BSGKeyBatteryLevel;
extern NSString *const BSGKeyBreadcrumbs;
extern NSString *const BSGKeyBundleVersion;
extern NSString *const BSGKeyCharging;
extern NSString *const BSGKeyClient;
extern NSString *const BSGKeyCodeBundleId;
extern NSString *const BSGKeyConfig;
extern NSString *const BSGKeyContext;
Expand Down
1 change: 1 addition & 0 deletions Bugsnag/Helpers/BugsnagKeys.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
NSString *const BSGKeyBreadcrumbs = @"breadcrumbs";
NSString *const BSGKeyBundleVersion = @"bundleVersion";
NSString *const BSGKeyCharging = @"charging";
NSString *const BSGKeyClient = @"client";
NSString *const BSGKeyCodeBundleId = @"codeBundleId";
NSString *const BSGKeyConfig = @"config";
NSString *const BSGKeyContext = @"context";
Expand Down
5 changes: 3 additions & 2 deletions Bugsnag/Payload/BugsnagEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ - (instancetype)initWithKSReport:(NSDictionary *)event {
} else if ([event valueForKeyPath:@"user.event"] != nil) {
return [self initWithUserData:event];
} else {
return [self initWithKSCrashData:event];
return [self initWithKSCrashReport:event];
}
}

Expand All @@ -259,7 +259,7 @@ - (instancetype)initWithKSReport:(NSDictionary *)event {
*
* @return a BugsnagEvent containing the parsed information
*/
- (instancetype)initWithKSCrashData:(NSDictionary *)event {
- (instancetype)initWithKSCrashReport:(NSDictionary *)event {
NSMutableDictionary *error = [[event valueForKeyPath:@"crash.error"] mutableCopy];
NSString *errorType = error[BSGKeyType];

Expand Down Expand Up @@ -377,6 +377,7 @@ - (instancetype)initWithKSCrashData:(NSDictionary *)event {
obj.enabledReleaseStages = BSGLoadConfigValue(event, BSGKeyEnabledReleaseStages);
obj.releaseStage = BSGParseReleaseStage(event);
obj.deviceAppHash = deviceAppHash;
obj.context = [event valueForKeyPath:@"user.state.client.context"];
obj.customException = BSGParseCustomException(event, [errors[0].errorClass copy], [errors[0].errorMessage copy]);
obj.error = error;
obj.depth = depth;
Expand Down
2 changes: 1 addition & 1 deletion Bugsnag/include/Bugsnag/BugsnagConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ typedef BOOL (^BugsnagOnSessionBlock)(BugsnagSession *_Nonnull session);
/**
* A general summary of what was occuring in the application
*/
@property (copy, nullable, nonatomic) NSString *context;
@property (copy, nullable, atomic) NSString *context;

/**
* The version of the application
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Changelog

### Bug fixes

* Fix missing `context` for crash, OOM, and app hang errors.
[#1079](https://github.com/bugsnag/bugsnag-cocoa/pull/1079)

* Fix `app` properties in OOMs for apps that override `appType`, `appVersion`, `bundleVersion` or `releaseStage` in their config.
[#1078](https://github.com/bugsnag/bugsnag-cocoa/pull/1078)

Expand Down
2 changes: 2 additions & 0 deletions features/app_hangs.feature
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Feature: App hangs
And the event "session.events.handled" equals 1
And the event "session.events.unhandled" equals 0

And the event "context" equals "App Hang Scenario"

#
# Checks copied from app_and_device_attributes.feature
#
Expand Down
2 changes: 2 additions & 0 deletions features/barebone_tests.feature
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ Feature: Barebone tests
And the event "app.version" equals "12.3"
And the event "breadcrumbs.0.name" equals "Bugsnag loaded"
And the event "breadcrumbs.1.name" is null
And the event "context" equals "Something"
And the event "device.freeMemory" is less than the event "device.totalMemory"
And the event "device.id" is not null
And the event "device.jailbroken" is false
Expand Down Expand Up @@ -190,6 +191,7 @@ Feature: Barebone tests
And the event "app.version" equals "3.2.1"
And the event "breadcrumbs.0.name" equals "Bugsnag loaded"
And the event "breadcrumbs.1.name" equals "Memory Warning"
And the event "context" equals "OOM Scenario"
And the event "device.id" is not null
And the event "device.jailbroken" is false
And the event "device.locale" is not null
Expand Down
1 change: 1 addition & 0 deletions features/fixtures/shared/scenarios/AppHangScenarios.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class AppHangScenario: Scenario {
}

override func run() {
Bugsnag.setContext("App Hang Scenario")
let timeInterval = TimeInterval(eventMode!)!
NSLog("Simulating an app hang of \(timeInterval) seconds...")
Thread.sleep(forTimeInterval: timeInterval)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class BareboneTestUnhandledErrorScenario: Scenario {
}

override func run() {
Bugsnag.setContext("Something")
Bugsnag.setUser("barfoo", withEmail: "barfoo@example.com", andName: "Bar Foo")

// Triggers "Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: ..."
Expand Down
1 change: 1 addition & 0 deletions features/fixtures/shared/scenarios/OOMScenario.m
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ - (void)startBugsnag {
}

- (void)run {
[Bugsnag setContext:@"OOM Scenario"];
[NSNotificationCenter.defaultCenter addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil
queue:nil usingBlock:^(NSNotification *note) {
NSLog(@"*** Received memory warning");
Expand Down