Skip to content

Commit

Permalink
feat: Add metadata accessors to BugsnagEvent
Browse files Browse the repository at this point in the history
Also added a BaseUnitTest to collect common setup patterns.
  • Loading branch information
robinmacharg authored and kattrali committed Feb 25, 2020
1 parent 1652b85 commit a6ea938
Show file tree
Hide file tree
Showing 12 changed files with 454 additions and 66 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ Bugsnag Notifiers on other platforms.
had usage aligned with this change.
[#459](https://github.com/bugsnag/bugsnag-cocoa/pull/459)

* Add metadata accessor methods to `BugsnagEvent`
[#465](https://github.com/bugsnag/bugsnag-cocoa/pull/465)

## Bug fixes

* Fix possible report corruption when using `notify()` from multiple threads
Expand Down
12 changes: 12 additions & 0 deletions OSX/Bugsnag.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
00D7AC9C23E97F8100FBE4A7 /* BugsnagEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D7AC9A23E97F8100FBE4A7 /* BugsnagEvent.m */; };
00D7AC9D23E97F8100FBE4A7 /* BugsnagEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 00D7AC9B23E97F8100FBE4A7 /* BugsnagEvent.h */; settings = {ATTRIBUTES = (Public, ); }; };
00D7ACA423E9854C00FBE4A7 /* BugsnagEventTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00D7ACA223E984B300FBE4A7 /* BugsnagEventTests.m */; };
00F9393323FC168F008C7073 /* BugsnagBaseUnitTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 00F9393223FC168F008C7073 /* BugsnagBaseUnitTest.m */; };
00F9393C23FD2D9B008C7073 /* BugsnagTestsDummyClass.m in Sources */ = {isa = PBXBuildFile; fileRef = 00F9393A23FD2D9B008C7073 /* BugsnagTestsDummyClass.m */; };
4B406C1822CAD96400464D1D /* BugsnagCollectionsBSGDictMergeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B406C1622CAD96400464D1D /* BugsnagCollectionsBSGDictMergeTest.m */; };
4B406C1922CAD96400464D1D /* BugsnagCollectionsBSGDictSetSafeObjectTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B406C1722CAD96400464D1D /* BugsnagCollectionsBSGDictSetSafeObjectTest.m */; };
4B775FD422CBE02F004839C5 /* BugsnagCollectionsBSGDictInsertIfNotNilTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B775FD222CBE02A004839C5 /* BugsnagCollectionsBSGDictInsertIfNotNilTest.m */; };
Expand Down Expand Up @@ -188,6 +190,10 @@
00D7AC9A23E97F8100FBE4A7 /* BugsnagEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagEvent.m; path = ../Source/BugsnagEvent.m; sourceTree = "<group>"; };
00D7AC9B23E97F8100FBE4A7 /* BugsnagEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagEvent.h; path = ../Source/BugsnagEvent.h; sourceTree = "<group>"; };
00D7ACA223E984B300FBE4A7 /* BugsnagEventTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagEventTests.m; sourceTree = "<group>"; };
00F9393123FC168F008C7073 /* BugsnagBaseUnitTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BugsnagBaseUnitTest.h; sourceTree = "<group>"; };
00F9393223FC168F008C7073 /* BugsnagBaseUnitTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagBaseUnitTest.m; sourceTree = "<group>"; };
00F9393A23FD2D9B008C7073 /* BugsnagTestsDummyClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagTestsDummyClass.m; sourceTree = "<group>"; };
00F9393B23FD2D9B008C7073 /* BugsnagTestsDummyClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BugsnagTestsDummyClass.h; sourceTree = "<group>"; };
4B406C1622CAD96400464D1D /* BugsnagCollectionsBSGDictMergeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagCollectionsBSGDictMergeTest.m; sourceTree = "<group>"; };
4B406C1722CAD96400464D1D /* BugsnagCollectionsBSGDictSetSafeObjectTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagCollectionsBSGDictSetSafeObjectTest.m; sourceTree = "<group>"; };
4B775FD222CBE02A004839C5 /* BugsnagCollectionsBSGDictInsertIfNotNilTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagCollectionsBSGDictInsertIfNotNilTest.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -465,6 +471,10 @@
8A2C8FAF1C6BC1F700846019 /* Tests */ = {
isa = PBXGroup;
children = (
00F9393B23FD2D9B008C7073 /* BugsnagTestsDummyClass.h */,
00F9393A23FD2D9B008C7073 /* BugsnagTestsDummyClass.m */,
00F9393123FC168F008C7073 /* BugsnagBaseUnitTest.h */,
00F9393223FC168F008C7073 /* BugsnagBaseUnitTest.m */,
00D7ACA223E984B300FBE4A7 /* BugsnagEventTests.m */,
8AD1E3F823EDDD3F0044F919 /* BSGConnectivityTest.m */,
4B406C1622CAD96400464D1D /* BugsnagCollectionsBSGDictMergeTest.m */,
Expand Down Expand Up @@ -922,6 +932,7 @@
4B775FD422CBE02F004839C5 /* BugsnagCollectionsBSGDictInsertIfNotNilTest.m in Sources */,
E7CE78C91FD94E77001D07E0 /* KSSignalInfo_Tests.m in Sources */,
E7CE78C61FD94E77001D07E0 /* KSMach_Tests.m in Sources */,
00F9393C23FD2D9B008C7073 /* BugsnagTestsDummyClass.m in Sources */,
E7CE78D61FD94E9E001D07E0 /* FileBasedTestCase.m in Sources */,
E7CE78D51FD94E93001D07E0 /* XCTestCase+KSCrash.m in Sources */,
E7CE78CF1FD94E77001D07E0 /* NSError+SimpleConstructor_Tests.m in Sources */,
Expand All @@ -941,6 +952,7 @@
8AD1E3FA23EDDD4F0044F919 /* BSGConnectivityTest.m in Sources */,
E7CE78C41FD94E77001D07E0 /* KSJSONCodec_Tests.m in Sources */,
E7CE78C01FD94E77001D07E0 /* KSCrashSentry_Tests.m in Sources */,
00F9393323FC168F008C7073 /* BugsnagBaseUnitTest.m in Sources */,
E7CE78BE1FD94E77001D07E0 /* KSCrashSentry_NSException_Tests.m in Sources */,
E7CE78C21FD94E77001D07E0 /* KSDynamicLinker_Tests.m in Sources */,
E7CE78CA1FD94E77001D07E0 /* KSString_Tests.m in Sources */,
Expand Down
79 changes: 64 additions & 15 deletions Source/BugsnagEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ NSString *_Nonnull BSGFormatSeverity(BSGSeverity severity);

@interface BugsnagEvent : NSObject

// -----------------------------------------------------------------------------
// MARK: - Initialisation
// -----------------------------------------------------------------------------

/**
* Create a new crash report from a JSON crash report generated by
* BugsnagCrashSentry
Expand Down Expand Up @@ -83,6 +87,10 @@ initWithErrorName:(NSString *_Nonnull)name
handledState:(BugsnagHandledState *_Nonnull)handledState
session:(BugsnagSession *_Nullable)session;

// -----------------------------------------------------------------------------
// MARK: - Misc. Methods
// -----------------------------------------------------------------------------

/**
* Serialize a crash report as a JSON payload
*
Expand Down Expand Up @@ -111,28 +119,74 @@ __deprecated_msg("Use toJson: instead.");
- (void)attachCustomStacktrace:(NSArray *_Nonnull)frames
withType:(NSString *_Nonnull)type;

/**
* Returns the enhanced error message for the thread, or nil if none exists.
*/
- (NSString *_Nullable)enhancedErrorMessageForThread:(NSDictionary *_Nullable)thread __deprecated;

// -----------------------------------------------------------------------------
// MARK: - Metadata
// -----------------------------------------------------------------------------

/**
* Add metadata to a report to a tab. If the tab does not exist, it will be
* added.
*
* @param metadata The key/value pairs to add
* @param tabName The name of the report section
* @param sectionName The name of the report section
*/
- (void)addMetadata:(NSDictionary *_Nonnull)metadata
toTabWithName:(NSString *_Nonnull)tabName;
toSectionNamed:(NSString *_Nonnull)sectionName;

/**
* Add or remove a value from report metadata. If value is nil, the existing
value
* Add or remove a value from report metadata. If value is nil, the existing value
* will be removed.
*
* @param key The key name
* @param value The value to set
* @param sectionName The name of the metadata section
*/
- (void)addMetadataToSectionNamed:(NSString *_Nonnull)sectionName
key:(NSString *_Nonnull)key
value:(id _Nullable)value;

/**
* Return a piece of metadata in a named section if it exists, or nil.
*
* @param sectionName The name of the metadata section
* @param key The key
* @returns An arbitrary object if it exists for the key or nil.
*/
- (id _Nullable)getMetadataInSection:(NSString *_Nonnull)sectionName
withKey:(NSString *_Nullable)key;

/**
* Return a named metadata section if it exists, or nil.
*
* @param sectionName The name of the metadata section
* @returns A dictionary of metadata if the section exists, or nil.
*/
- (NSDictionary *_Nullable)getMetadataInSection:(NSString *_Nonnull)sectionName;

@param attributeName The key name
@param value The value to set
@param tabName The name of the report section
/**
* Remove a named metadata section, if it exists.
*
* @param sectionName The name of the section to remove
*/
- (void)addAttribute:(NSString *_Nonnull)attributeName
withValue:(id _Nullable)value
toTabWithName:(NSString *_Nonnull)tabName;
- (void)clearMetadataSection:(NSString *_Nonnull)sectionName;

/**
* Remove a named metadata value if the key exists in the named section.
*
* @param sectionName The name of the section to remove
* @param key The key to remove
*/
- (void)clearMetadataInSection:(NSString *_Nonnull)sectionName
withKey:(NSString *_Nonnull)key;

// -----------------------------------------------------------------------------
// MARK: - Properties
// -----------------------------------------------------------------------------

/**
* The release stages used to notify at the time this report is captured
Expand Down Expand Up @@ -219,9 +273,4 @@ __deprecated_msg("Use toJson: instead.");
*/
@property (readonly, nonatomic, getter=isIncomplete) BOOL incomplete;

/**
* Returns the enhanced error message for the thread, or nil if none exists.
*/
- (NSString *_Nullable)enhancedErrorMessageForThread:(NSDictionary *_Nullable)thread __deprecated;

@end
108 changes: 75 additions & 33 deletions Source/BugsnagEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
return self;
}

// MARK: - Metadata

@synthesize metadata = _metadata;

- (NSDictionary *)metadata {
Expand All @@ -368,20 +370,82 @@ - (void)setMetadata:(NSDictionary *)metadata {
}
}

- (void)addMetadata:(NSDictionary *_Nonnull)tabData
toTabWithName:(NSString *_Nonnull)tabName {
NSDictionary *cleanedData = BSGSanitizeDict(tabData);
if ([cleanedData count] == 0) {
bsg_log_err(@"Failed to add metadata: Values not convertible to JSON");
return;
- (void)addMetadata:(NSDictionary *_Nonnull)metadata
toSectionNamed:(NSString *_Nonnull)sectionName
{
@synchronized (self) {
NSDictionary *cleanedData = BSGSanitizeDict(metadata);
if ([cleanedData count] == 0) {
bsg_log_err(@"Failed to add metadata: Values not convertible to JSON");
return;
}
NSMutableDictionary *allMetadata = [self.metadata mutableCopy];
NSMutableDictionary *allTabData =
allMetadata[sectionName] ?: [NSMutableDictionary new];
allMetadata[sectionName] = [cleanedData BSG_mergedInto:allTabData];
self.metadata = allMetadata;
}
}

- (void)addMetadataToSectionNamed:(NSString *_Nonnull)sectionName
key:(NSString *_Nonnull)key
value:(id _Nullable)value
{
@synchronized (self) {
NSMutableDictionary *allMetadata = [self.metadata mutableCopy];
NSMutableDictionary *allTabData =
[allMetadata[sectionName] mutableCopy] ?: [NSMutableDictionary new];
if (value) {
id cleanedValue = BSGSanitizeObject(value);
if (!cleanedValue) {
bsg_log_err(@"Failed to add metadata: Value of type %@ is not "
@"convertible to JSON",
[value class]);
return;
}
allTabData[key] = cleanedValue;
} else {
[allTabData removeObjectForKey:key];
}
allMetadata[sectionName] = allTabData;
self.metadata = allMetadata;
}
}

- (id _Nullable)getMetadataInSection:(NSString *_Nonnull)sectionName
withKey:(NSString *_Nullable)key
{
@synchronized (self) {
return [[[self metadata] objectForKey:sectionName] objectForKey:key];
}
NSMutableDictionary *allMetadata = [self.metadata mutableCopy];
NSMutableDictionary *allTabData =
allMetadata[tabName] ?: [NSMutableDictionary new];
allMetadata[tabName] = [cleanedData BSG_mergedInto:allTabData];
self.metadata = allMetadata;
}

- (NSDictionary *_Nullable)getMetadataInSection:(NSString *_Nonnull)sectionName
{
@synchronized (self) {
return [[self metadata] objectForKey:sectionName];
}
}

- (void)clearMetadataSection:(NSString *_Nonnull)sectionName
{
@synchronized (self) {
NSMutableDictionary *copy = [[self metadata] mutableCopy];
[copy removeObjectForKey:sectionName];
_metadata = copy;
}
}

- (void)clearMetadataInSection:(NSString *_Nonnull)sectionName
withKey:(NSString *_Nonnull)key
{
@synchronized (self) {
[[[self metadata] objectForKey:sectionName] removeObjectForKey:key];
}
}

// MARK: - apiKey

@synthesize apiKey = _apiKey;

- (NSString *)apiKey {
Expand All @@ -404,28 +468,6 @@ - (void)setApiKey:(NSString *)apiKey {
}
}

- (void)addAttribute:(NSString *)attributeName
withValue:(id)value
toTabWithName:(NSString *)tabName {
NSMutableDictionary *allMetadata = [self.metadata mutableCopy];
NSMutableDictionary *allTabData =
[allMetadata[tabName] mutableCopy] ?: [NSMutableDictionary new];
if (value) {
id cleanedValue = BSGSanitizeObject(value);
if (!cleanedValue) {
bsg_log_err(@"Failed to add metadata: Value of type %@ is not "
@"convertible to JSON",
[value class]);
return;
}
allTabData[attributeName] = cleanedValue;
} else {
[allTabData removeObjectForKey:attributeName];
}
allMetadata[tabName] = allTabData;
self.metadata = allMetadata;
}

- (BOOL)shouldBeSent {
return [self.notifyReleaseStages containsObject:self.releaseStage] ||
(self.notifyReleaseStages.count == 0 &&
Expand Down
19 changes: 19 additions & 0 deletions Tests/BugsnagBaseUnitTest.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// BugsnagBaseUnitTest.h
// Bugsnag
//
// Created by Robin Macharg on 13/02/2020.
// Copyright © 2020 Bugsnag. All rights reserved.
//
// A Unit Test base class that provides useful utility methods.

#ifndef BugsnagBaseUnitTest_h
#define BugsnagBaseUnitTest_h

@interface BugsnagBaseUnitTest : XCTestCase

-(void)setUpBugsnagWillCallNotify:(bool)willNotify;

@end

#endif /* BugsnagBaseUnitTest_h */
42 changes: 42 additions & 0 deletions Tests/BugsnagBaseUnitTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// BugsnagBaseUnitTest.m
// Tests
//
// Created by Robin Macharg on 13/02/2020.
// Copyright © 2020 Bugsnag. All rights reserved.
//

#import "Bugsnag.h"
#import "BugsnagConfiguration.h"
#import "BugsnagTestConstants.h"
#import <XCTest/XCTest.h>

@interface BugsnagBaseUnitTest : XCTestCase

@end

@implementation BugsnagBaseUnitTest

/**
* A boilerplate helper method to setup Bugsnag
* If [Bugsnag notify] is to be called during unit testing it should either:
*
* - discard events before sending or
* - send to an arbitrary non-functional endpoint.
*
* We take the former approach.
*/
-(void)setUpBugsnagWillCallNotify:(bool)willNotify {
NSError *error;
BugsnagConfiguration *configuration = [[BugsnagConfiguration alloc] initWithApiKey:DUMMY_APIKEY_32CHAR_1 error:&error];
if (willNotify) {
[configuration addBeforeSendBlock:^bool(NSDictionary * _Nonnull rawEventData,
BugsnagEvent * _Nonnull reports)
{
return false;
}];
}
[Bugsnag startBugsnagWithConfiguration:configuration];
}

@end
Loading

0 comments on commit a6ea938

Please sign in to comment.