diff --git a/CHANGELOG.md b/CHANGELOG.md index 514569f9f5..525f5d1b44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Added breadcrumb.origin private field (#4358) - Custom redact modifier for SwiftUI (#4362) ### Improvements diff --git a/Sources/Sentry/Public/SentryBreadcrumb.h b/Sources/Sentry/Public/SentryBreadcrumb.h index 8f21b72134..181cf8f9ad 100644 --- a/Sources/Sentry/Public/SentryBreadcrumb.h +++ b/Sources/Sentry/Public/SentryBreadcrumb.h @@ -26,23 +26,23 @@ NS_SWIFT_NAME(Breadcrumb) /** * @c NSDate when the breadcrumb happened */ -@property (nonatomic, strong) NSDate *_Nullable timestamp; +@property (nonatomic, strong, nullable) NSDate *timestamp; /** * Type of breadcrumb, can be e.g.: http, empty, user, navigation * This will be used as icon of the breadcrumb */ -@property (nonatomic, copy) NSString *_Nullable type; +@property (nonatomic, copy, nullable) NSString *type; /** * Message for the breadcrumb */ -@property (nonatomic, copy) NSString *_Nullable message; +@property (nonatomic, copy, nullable) NSString *message; /** * Arbitrary additional data that will be sent with the breadcrumb */ -@property (nonatomic, strong) NSDictionary *_Nullable data; +@property (nonatomic, strong, nullable) NSDictionary *data; /** * Initializer for @c SentryBreadcrumb diff --git a/Sources/Sentry/SentryBreadcrumb.m b/Sources/Sentry/SentryBreadcrumb.m index 6baf8ee894..ad65cff260 100644 --- a/Sources/Sentry/SentryBreadcrumb.m +++ b/Sources/Sentry/SentryBreadcrumb.m @@ -1,4 +1,5 @@ #import "SentryBreadcrumb.h" +#import "SentryBreadcrumb+Private.h" #import "SentryDateUtils.h" #import "SentryLevelMapper.h" #import "SentryNSDictionarySanitize.h" @@ -30,6 +31,8 @@ - (instancetype)initWithDictionary:(NSDictionary *)dictionary self.category = value; } else if ([key isEqualToString:@"type"] && isString) { self.type = value; + } else if ([key isEqualToString:@"origin"] && isString) { + self.origin = value; } else if ([key isEqualToString:@"message"] && isString) { self.message = value; } else if ([key isEqualToString:@"data"] && isDictionary) { @@ -69,6 +72,7 @@ - (instancetype)init [serializedData setValue:sentry_toIso8601String(self.timestamp) forKey:@"timestamp"]; [serializedData setValue:self.category forKey:@"category"]; [serializedData setValue:self.type forKey:@"type"]; + [serializedData setValue:self.origin forKey:@"origin"]; [serializedData setValue:self.message forKey:@"message"]; [serializedData setValue:sentry_sanitize(self.data) forKey:@"data"]; NSDictionary *unknown = self.unknown; @@ -106,6 +110,8 @@ - (BOOL)isEqualToBreadcrumb:(SentryBreadcrumb *)breadcrumb return NO; if (self.type != breadcrumb.type && ![self.type isEqualToString:breadcrumb.type]) return NO; + if (self.origin != breadcrumb.origin && ![self.origin isEqualToString:breadcrumb.origin]) + return NO; if (self.message != breadcrumb.message && ![self.message isEqualToString:breadcrumb.message]) return NO; if (self.data != breadcrumb.data && ![self.data isEqualToDictionary:breadcrumb.data]) @@ -123,6 +129,7 @@ - (NSUInteger)hash hash = hash * 23 + [self.category hash]; hash = hash * 23 + [self.timestamp hash]; hash = hash * 23 + [self.type hash]; + hash = hash * 23 + [self.origin hash]; hash = hash * 23 + [self.message hash]; hash = hash * 23 + [self.data hash]; hash = hash * 23 + [self.unknown hash]; diff --git a/Sources/Sentry/SentryCrashReportConverter.m b/Sources/Sentry/SentryCrashReportConverter.m index 0c5c3424c3..2dbcc00833 100644 --- a/Sources/Sentry/SentryCrashReportConverter.m +++ b/Sources/Sentry/SentryCrashReportConverter.m @@ -1,4 +1,5 @@ #import "SentryCrashReportConverter.h" +#import "SentryBreadcrumb+Private.h" #import "SentryBreadcrumb.h" #import "SentryCrashStackCursor.h" #import "SentryDateUtils.h" @@ -170,6 +171,7 @@ - (SentryUser *_Nullable)convertUser category:storedCrumb[@"category"]]; crumb.message = storedCrumb[@"message"]; crumb.type = storedCrumb[@"type"]; + crumb.origin = storedCrumb[@"origin"]; crumb.timestamp = sentry_fromIso8601String(storedCrumb[@"timestamp"]); crumb.data = storedCrumb[@"data"]; [breadcrumbs addObject:crumb]; diff --git a/Sources/Sentry/include/HybridPublic/SentryBreadcrumb+Private.h b/Sources/Sentry/include/HybridPublic/SentryBreadcrumb+Private.h index 4bfe80f68f..21d2e10e78 100644 --- a/Sources/Sentry/include/HybridPublic/SentryBreadcrumb+Private.h +++ b/Sources/Sentry/include/HybridPublic/SentryBreadcrumb+Private.h @@ -2,10 +2,16 @@ @interface SentryBreadcrumb () +/** + * Origin of the breadcrumb that is used to identify source of the breadcrumb + * For example hybrid SDKs can identify native breadcrumbs from JS or Flutter + */ +@property (nonatomic, copy, nullable) NSString *origin; + /** * Initializes a SentryBreadcrumb from a JSON object. * @param dictionary The dictionary containing breadcrumb data. * @return The SentryBreadcrumb. */ -- (instancetype)initWithDictionary:(NSDictionary *)dictionary; +- (instancetype _Nonnull)initWithDictionary:(NSDictionary *_Nonnull)dictionary; @end diff --git a/Tests/SentryTests/Protocol/SentryBreadcrumbTests.swift b/Tests/SentryTests/Protocol/SentryBreadcrumbTests.swift index 3e8a5effb5..bb78b60918 100644 --- a/Tests/SentryTests/Protocol/SentryBreadcrumbTests.swift +++ b/Tests/SentryTests/Protocol/SentryBreadcrumbTests.swift @@ -8,6 +8,7 @@ class SentryBreadcrumbTests: XCTestCase { let category = "category" let type = "user" + let origin = "origin" let message = "Click something" init() { @@ -18,6 +19,7 @@ class SentryBreadcrumbTests: XCTestCase { breadcrumb.timestamp = date breadcrumb.category = category breadcrumb.type = type + breadcrumb.origin = origin breadcrumb.message = message breadcrumb.data = ["some": ["data": "data", "date": date] as [String: Any]] breadcrumb.setValue(["foo": "bar"], forKey: "unknown") @@ -36,6 +38,7 @@ class SentryBreadcrumbTests: XCTestCase { "timestamp": fixture.dateAs8601String, "category": fixture.category, "type": fixture.type, + "origin": fixture.origin, "message": fixture.message, "data": ["foo": "bar"], "foo": "bar" // Unknown @@ -46,6 +49,7 @@ class SentryBreadcrumbTests: XCTestCase { XCTAssertEqual(breadcrumb.timestamp, fixture.date) XCTAssertEqual(breadcrumb.category, fixture.category) XCTAssertEqual(breadcrumb.type, fixture.type) + XCTAssertEqual(breadcrumb.origin, fixture.origin) XCTAssertEqual(breadcrumb.message, fixture.message) XCTAssertEqual(breadcrumb.data as? [String: String], ["foo": "bar"]) XCTAssertEqual(breadcrumb.value(forKey: "unknown") as? NSDictionary, ["foo": "bar"]) @@ -68,6 +72,16 @@ class SentryBreadcrumbTests: XCTestCase { func testIsNotEqualToOtherClass() { XCTAssertFalse(fixture.breadcrumb.isEqual(1)) } + + func testIsNotEqualToNil() { + XCTAssertFalse(fixture.breadcrumb.isEqual(nil)) + } + + func testIsNotEqualIfOriginDiffers() { + let fixture2 = Fixture() + fixture2.breadcrumb.origin = "origin2" + XCTAssertNotEqual(fixture.breadcrumb, fixture2.breadcrumb) + } func testIsEqualToOtherInstanceWithSameValues() { let fixture2 = Fixture() @@ -79,6 +93,7 @@ class SentryBreadcrumbTests: XCTestCase { testIsNotEqual { breadcrumb in breadcrumb.category = "" } testIsNotEqual { breadcrumb in breadcrumb.timestamp = Date() } testIsNotEqual { breadcrumb in breadcrumb.type = "" } + testIsNotEqual { breadcrumb in breadcrumb.origin = "" } testIsNotEqual { breadcrumb in breadcrumb.message = "" } testIsNotEqual { breadcrumb in breadcrumb.data?.removeAll() } testIsNotEqual { breadcrumb in breadcrumb.setValue(nil, forKey: "unknown") } @@ -99,6 +114,7 @@ class SentryBreadcrumbTests: XCTestCase { crumb.timestamp = nil crumb.category = "" crumb.type = "" + crumb.origin = "" crumb.message = "" crumb.data = nil crumb.setValue(nil, forKey: "unknown") @@ -107,6 +123,7 @@ class SentryBreadcrumbTests: XCTestCase { XCTAssertEqual(fixture.dateAs8601String, actual["timestamp"] as? String) XCTAssertEqual(fixture.category, actual["category"] as? String) XCTAssertEqual(fixture.type, actual["type"] as? String) + XCTAssertEqual(fixture.origin, actual["origin"] as? String) XCTAssertEqual(fixture.message, actual["message"] as? String) XCTAssertEqual(["some": ["data": "data", "date": fixture.dateAs8601String]], actual["data"] as? Dictionary) XCTAssertEqual("bar", actual["foo"] as? String) diff --git a/Tests/SentryTests/SentryInterfacesTests.m b/Tests/SentryTests/SentryInterfacesTests.m index 34d1f9ccc9..581368aedf 100644 --- a/Tests/SentryTests/SentryInterfacesTests.m +++ b/Tests/SentryTests/SentryInterfacesTests.m @@ -1,5 +1,6 @@ #import +#import "SentryBreadcrumb+Private.h" #import "SentryBreadcrumb.h" #import "SentryDateUtils.h" #import "SentryEvent.h" @@ -378,10 +379,12 @@ - (void)testBreadcrumb crumb2.type = @"type"; crumb2.timestamp = date; crumb2.message = @"message"; + crumb2.origin = @"origin"; NSDictionary *serialized2 = @{ @"level" : @"info", @"type" : @"type", @"message" : @"message", + @"origin" : @"origin", @"timestamp" : sentry_toIso8601String(date), @"category" : @"http", @"data" : @ { @"bla" : @"1" }, diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 1f99247788..5b8265c986 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -58,6 +58,7 @@ #import "SentryAutoBreadcrumbTrackingIntegration.h" #import "SentryAutoSessionTrackingIntegration.h" #import "SentryBooleanSerialization.h" +#import "SentryBreadcrumb+Private.h" #import "SentryBreadcrumbDelegate.h" #import "SentryBreadcrumbTracker.h" #import "SentryByteCountFormatter.h" diff --git a/Tests/SentryTests/SentryTests.m b/Tests/SentryTests/SentryTests.m index b4f0684331..74162b2853 100644 --- a/Tests/SentryTests/SentryTests.m +++ b/Tests/SentryTests/SentryTests.m @@ -10,6 +10,7 @@ #import "SentryMeta.h" #import "SentryOptions+HybridSDKs.h" #import "SentrySDK+Private.h" +#import #import @import Sentry; @@ -78,6 +79,7 @@ - (void)testSDKBreadCrumbAdd SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] initWithLevel:kSentryLevelInfo category:@"testCategory"]; crumb.type = @"testType"; + crumb.origin = @"testOrigin"; crumb.message = @"testMessage"; crumb.data = @{ @"testDataKey" : @"testDataVaue" };