Skip to content

Commit

Permalink
feat: add App/AppWithState interface
Browse files Browse the repository at this point in the history
  • Loading branch information
fractalwrench committed Apr 3, 2020
1 parent 56e0e4e commit 6638699
Show file tree
Hide file tree
Showing 23 changed files with 382 additions and 227 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Bugsnag Notifiers on other platforms.

## Enhancements

* Convert `event.app` from `NSDictionary` to a structured class
[#520](https://github.com/bugsnag/bugsnag-cocoa/pull/520)

* Make `BugsnagClient` a public interface
[#517](https://github.com/bugsnag/bugsnag-cocoa/pull/517)

Expand Down
4 changes: 4 additions & 0 deletions OSX/Bugsnag.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,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 */; };
E7A9E56924361B6300D99F8A /* BugsnagAppTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E790C4A22434CB6A006FFB26 /* BugsnagAppTest.m */; };
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 */; };
Expand Down Expand Up @@ -291,6 +292,7 @@
E790C46D24349CE2006FFB26 /* BugsnagAppWithState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagAppWithState.m; path = ../Source/BugsnagAppWithState.m; sourceTree = "<group>"; };
E790C46E24349CE2006FFB26 /* BugsnagThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagThread.h; path = ../Source/BugsnagThread.h; sourceTree = "<group>"; };
E790C46F24349CE2006FFB26 /* BugsnagThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagThread.m; path = ../Source/BugsnagThread.m; sourceTree = "<group>"; };
E790C4A22434CB6A006FFB26 /* BugsnagAppTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagAppTest.m; sourceTree = "<group>"; };
E791482B1FD82B0C003EFEBF /* BugsnagSessionTrackerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagSessionTrackerTest.m; sourceTree = "<group>"; };
E791482C1FD82B0C003EFEBF /* BugsnagSessionTrackingPayloadTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagSessionTrackingPayloadTest.m; sourceTree = "<group>"; };
E791482D1FD82B0C003EFEBF /* BugsnagSessionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagSessionTest.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -555,6 +557,7 @@
8A2C8FAF1C6BC1F700846019 /* Tests */ = {
isa = PBXGroup;
children = (
E790C4A22434CB6A006FFB26 /* BugsnagAppTest.m */,
E790C41F2432314A006FFB26 /* BugsnagClientMirrorTest.m */,
E7AB4B9D2423E184004F015A /* BugsnagOnBreadcrumbTest.m */,
00F9393B23FD2D9B008C7073 /* BugsnagTestsDummyClass.h */,
Expand Down Expand Up @@ -1039,6 +1042,7 @@
buildActionMask = 2147483647;
files = (
E7CE78CC1FD94E77001D07E0 /* KSSystemInfo_Tests.m in Sources */,
E7A9E56924361B6300D99F8A /* BugsnagAppTest.m in Sources */,
4B775FD422CBE02F004839C5 /* BugsnagCollectionsBSGDictInsertIfNotNilTest.m in Sources */,
E7CE78C91FD94E77001D07E0 /* KSSignalInfo_Tests.m in Sources */,
E7CE78C61FD94E77001D07E0 /* KSMach_Tests.m in Sources */,
Expand Down
39 changes: 35 additions & 4 deletions Source/BugsnagApp.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,45 @@

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/**
* Stateless information set by the notifier about your app can be found on this class. These values
* can be accessed and amended if necessary.
*/
@interface BugsnagApp : NSObject

@end
/**
* The bundle version used by the application
*/
@property(nonatomic) NSString *_Nullable bundleVersion;

/**
* The revision ID from the manifest (React Native apps only)
*/
@property(nonatomic) NSString *_Nullable codeBundleId;

/**
* Unique identifier for the debug symbols file corresponding to the application
*/
@property(nonatomic) NSString *_Nullable dsymUuid;

NS_ASSUME_NONNULL_END
/**
* The app identifier used by the application
*/
@property(nonatomic) NSString *_Nullable id;

/**
* The release stage set in Configuration
*/
@property(nonatomic) NSString *_Nullable releaseStage;

/**
* The application type set in Configuration
*/
@property(nonatomic) NSString *_Nullable type;

/**
* The version of the application set in Configuration
*/
@property(nonatomic) NSString *_Nullable version;

@end
35 changes: 35 additions & 0 deletions Source/BugsnagApp.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,42 @@
//

#import "BugsnagApp.h"
#import "BugsnagKeys.h"
#import "BugsnagConfiguration.h"
#import "BugsnagCollections.h"

@implementation BugsnagApp

+ (BugsnagApp *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config {
BugsnagApp *app = [BugsnagApp new];
[self populateFields:app dictionary:event config:config];
return app;
}

+ (void)populateFields:(BugsnagApp *)app dictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config {
NSDictionary *system = event[BSGKeySystem];
app.id = system[@"CFBundleIdentifier"];
app.bundleVersion = system[@"CFBundleVersion"];
app.dsymUuid = system[@"app_uuid"];
app.version = [event valueForKeyPath:@"user.config.appVersion"] ?: system[@"CFBundleShortVersionString"];
app.releaseStage = [event valueForKeyPath:@"user.config.releaseStage"] ?: config.releaseStage;
app.codeBundleId = config.codeBundleId;
app.type = config.appType;
}

- (NSDictionary *)toDict {
NSMutableDictionary *dict = [NSMutableDictionary new];
BSGDictInsertIfNotNil(dict, self.bundleVersion, @"bundleVersion");
BSGDictInsertIfNotNil(dict, self.codeBundleId, @"codeBundleId");
BSGDictInsertIfNotNil(dict, self.id, @"id");
BSGDictInsertIfNotNil(dict, self.releaseStage, @"releaseStage");
BSGDictInsertIfNotNil(dict, self.type, @"type");
BSGDictInsertIfNotNil(dict, self.version, @"version");

if (self.dsymUuid != nil) {
BSGDictInsertIfNotNil(dict, @[self.dsymUuid], @"dsymUUIDs");
}
return dict;
}

@end
19 changes: 15 additions & 4 deletions Source/BugsnagAppWithState.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,25 @@

#import "BugsnagApp.h"

NS_ASSUME_NONNULL_BEGIN

/**
* Stateful information set by the notifier about your app can be found on this class. These values
* can be accessed and amended if necessary.
*/
@interface BugsnagAppWithState : BugsnagApp

@end
/**
* The number of milliseconds the application was running before the event occurred
*/
@property(nonatomic) NSUInteger duration;

/**
* The number of milliseconds the application was running in the foreground before the
* event occurred
*/
@property(nonatomic) NSUInteger durationInForeground;

NS_ASSUME_NONNULL_END
/**
* Whether the application was in the foreground when the event occurred
*/
@property(nonatomic) BOOL inForeground;
@end
45 changes: 45 additions & 0 deletions Source/BugsnagAppWithState.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,52 @@
//

#import "BugsnagAppWithState.h"
#import "BugsnagKeys.h"
#import "BugsnagConfiguration.h"
#import "BugsnagCollections.h"

@interface BugsnagApp ()
+ (void)populateFields:(BugsnagApp *)app dictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config;

- (NSDictionary *)toDict;
@end

@implementation BugsnagAppWithState

+ (BugsnagAppWithState *)appWithOomData:(NSDictionary *)event {
BugsnagAppWithState *app = [BugsnagAppWithState new];
app.id = event[@"id"];
app.releaseStage = event[@"releaseStage"];
app.version = event[@"version"];
app.bundleVersion = event[@"bundleVersion"];
app.codeBundleId = event[@"codeBundleId"];
app.inForeground = [event[@"inForeground"] boolValue];
app.type = event[@"type"];
return app;
}

+ (BugsnagAppWithState *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config {
BugsnagAppWithState *app = [BugsnagAppWithState new];
NSDictionary *system = event[BSGKeySystem];
NSDictionary *stats = system[@"application_stats"];

// convert from seconds to milliseconds
NSUInteger activeTimeSinceLaunch = (NSUInteger) ([stats[@"active_time_since_launch"] longValue] * 1000);
NSUInteger backgroundTimeSinceLaunch = (NSUInteger) ([stats[@"background_time_since_launch"] longValue] * 1000);

app.durationInForeground = activeTimeSinceLaunch;
app.duration = activeTimeSinceLaunch + backgroundTimeSinceLaunch;
app.inForeground = [stats[@"application_in_foreground"] boolValue];
[BugsnagApp populateFields:app dictionary:event config:config];
return app;
}

- (NSDictionary *)toDict {
NSMutableDictionary *dict = (NSMutableDictionary *) [super toDict];
BSGDictInsertIfNotNil(dict, @(self.duration), @"duration");
BSGDictInsertIfNotNil(dict, @(self.durationInForeground), @"durationInForeground");
BSGDictInsertIfNotNil(dict, @(self.inForeground), @"inForeground");
return dict;
}

@end
8 changes: 8 additions & 0 deletions Source/BugsnagConfiguration.m
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ - (instancetype _Nonnull)initWithApiKey:(NSString *_Nonnull)apiKey
#else
_releaseStage = BSGKeyProduction;
#endif

#if TARGET_OS_TV
_appType = @"tvOS";
#elif TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
_appType = @"iOS";
#elif TARGET_OS_MAC
_appType = @"macOS";
#endif

return self;
}
Expand Down
14 changes: 4 additions & 10 deletions Source/BugsnagEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
@class BugsnagHandledState;
@class BugsnagSession;
@class BugsnagBreadcrumb;
@class BugsnagAppWithState;
@class BugsnagMetadata;

typedef NS_ENUM(NSUInteger, BSGSeverity) {
Expand Down Expand Up @@ -90,10 +91,6 @@ initWithErrorName:(NSString *_Nonnull)name
* The severity of the error generating the report
*/
@property(readwrite) BSGSeverity severity;
/**
* The release stage of the application
*/
@property(readwrite, copy, nullable) NSString *releaseStage;
/**
* The class of the error generating the report
*/
Expand All @@ -120,19 +117,16 @@ initWithErrorName:(NSString *_Nonnull)name
/**
* Device information such as OS name and version
*/
@property(readwrite, copy, nullable) NSDictionary *device;
@property(readonly, nonnull) NSDictionary *device;
/**
* Device state such as memory allocation at crash time
*/
@property(readwrite, copy, nullable) NSDictionary *deviceState;

/**
* App information such as the name, version, and bundle ID
*/
@property(readwrite, copy, nullable) NSDictionary *app;
/**
* Device state such as oreground status and run duration
*/
@property(readwrite, copy, nullable) NSDictionary *appState;
@property(readonly, nonnull) BugsnagAppWithState *app;

/**
* Whether the event was a crash (i.e. unhandled) or handled error in which the system
Expand Down
48 changes: 20 additions & 28 deletions Source/BugsnagEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
#import "BugsnagKeys.h"
#import "BugsnagClient.h"

@interface BugsnagAppWithState ()
+ (BugsnagAppWithState *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config;
- (NSDictionary *)toDict;
+ (BugsnagAppWithState *)appWithOomData:(NSDictionary *)event;
@end

@interface BugsnagBreadcrumb ()
+ (instancetype _Nullable)breadcrumbWithBlock:
(BSGBreadcrumbConfiguration _Nonnull)block;
Expand Down Expand Up @@ -236,10 +242,6 @@ @interface BugsnagEvent ()
* The type of the error, such as `mach` or `user`
*/
@property(nonatomic, readwrite, copy, nullable) NSString *errorType;
/**
* The UUID of the dSYM file
*/
@property(nonatomic, readonly, copy, nullable) NSString *dsymUUID;
/**
* A unique hash identifying this device for the application or vendor
*/
Expand Down Expand Up @@ -295,6 +297,11 @@ - (BOOL)shouldBeSent;
* Raw error data
*/
@property(readwrite, copy, nullable) NSDictionary *error;

/**
* The release stage of the application
*/
@property(readwrite, copy, nullable) NSString *releaseStage;
@end

@implementation BugsnagEvent
Expand All @@ -310,19 +317,21 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
}

if (self = [super init]) {
BugsnagConfiguration *config = [Bugsnag configuration];

_error = [report valueForKeyPath:@"crash.error"];
_errorType = _error[BSGKeyType];
if ([[report valueForKeyPath:@"user.state.didOOM"] boolValue]) {
_errorClass = BSGParseErrorClass(_error, _errorType);
_errorMessage = BSGParseErrorMessage(report, _error, _errorType);
_breadcrumbs = BSGParseBreadcrumbs(report);
_app = [report valueForKeyPath:@"user.state.oom.app"];
_app = [BugsnagAppWithState appWithOomData:[report valueForKeyPath:@"user.state.oom.app"]];
_device = [report valueForKeyPath:@"user.state.oom.device"];
_releaseStage = [report valueForKeyPath:@"user.state.oom.app.releaseStage"];
_handledState = [BugsnagHandledState handledStateWithSeverityReason:LikelyOutOfMemory];
_deviceAppHash = [report valueForKeyPath:@"user.state.oom.device.id"];
self.metadata = [BugsnagMetadata new];

NSDictionary *sessionData = [report valueForKeyPath:@"user.state.oom.session"];
if (sessionData) {
_session = [[BugsnagSession alloc] initWithDictionary:sessionData];
Expand All @@ -345,7 +354,6 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
}
_binaryImages = report[@"binary_images"];
_breadcrumbs = BSGParseBreadcrumbs(report);
_dsymUUID = [report valueForKeyPath:@"system.app_uuid"];
_deviceAppHash = [report valueForKeyPath:@"system.device_app_hash"];

id userMetadata = [report valueForKeyPath:@"user.metaData"];
Expand All @@ -355,15 +363,11 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
else {
self.metadata = [BugsnagMetadata new];
}

_context = BSGParseContext(report);
_deviceState = BSGParseDeviceState(report);
_device = BSGParseDevice(report);
_app = BSGParseApp(report);
_appState = BSGParseAppState(report[BSGKeySystem],
BSGLoadConfigValue(report, @"appVersion"),
_releaseStage, // Already loaded from config
BSGLoadConfigValue(report, @"codeBundleId"));
_app = [BugsnagAppWithState appWithDictionary:report config:config];
_groupingHash = BSGParseGroupingHash(report);
_overrides = [report valueForKeyPath:@"user.overrides"];
_customException = BSGParseCustomException(report, [_errorClass copy], [_errorMessage copy]);
Expand All @@ -378,7 +382,7 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
_depth = [[report valueForKeyPath:@"user.depth"]
unsignedIntegerValue];
}

// the event was unhandled.
else {
BOOL isSignal = [BSGKeySignal isEqualToString:_errorType];
Expand Down Expand Up @@ -411,7 +415,7 @@ - (instancetype _Nonnull)initWithErrorName:(NSString *_Nonnull)name
_errorMessage = message;
_overrides = [NSDictionary new];
self.metadata = [metadata deepCopy] ?: [BugsnagMetadata new];
_releaseStage = config.releaseStage;
self.releaseStage = config.releaseStage;
_enabledReleaseStages = config.enabledReleaseStages;
// Set context based on current values. May be nil.
_context = [[Bugsnag configuration] context];
Expand Down Expand Up @@ -579,19 +583,7 @@ - (NSDictionary *)toJson {

NSDictionary *device = BSGDictMerge(self.device, self.deviceState);
BSGDictSetSafeObject(event, device, BSGKeyDevice);

NSMutableDictionary *appObj = [NSMutableDictionary new];
[appObj addEntriesFromDictionary:self.app];

for (NSString *key in self.appState) {
BSGDictInsertIfNotNil(appObj, self.appState[key], key);
}

if (self.dsymUUID) {
BSGDictInsertIfNotNil(appObj, @[self.dsymUUID], @"dsymUUIDs");
}

BSGDictSetSafeObject(event, appObj, BSGKeyApp);
BSGDictSetSafeObject(event, [self.app toDict], BSGKeyApp);

BSGDictSetSafeObject(event, [self context], BSGKeyContext);
BSGDictInsertIfNotNil(event, self.groupingHash, BSGKeyGroupingHash);
Expand Down
Loading

0 comments on commit 6638699

Please sign in to comment.