From 69c1637bf0265e7d080a9e6d96f60d252673f41b Mon Sep 17 00:00:00 2001 From: Dhiogo Ramos Brustolin Date: Fri, 9 Jun 2023 12:02:03 +0200 Subject: [PATCH 1/3] feat: Close session for unhandled error This reverts commit ce4236ecdb86c704204e48734f72ff0845c0615d. --- Sources/Sentry/SentryHub.m | 38 ++++++++++++++++--- Sources/Sentry/SentrySerialization.m | 18 +++++++++ Sources/Sentry/include/SentryHub+Private.h | 3 +- Sources/Sentry/include/SentrySerialization.h | 5 +++ Tests/SentryTests/SentryHubTests.swift | 39 ++++++++++++++++++++ 5 files changed, 97 insertions(+), 6 deletions(-) diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index c0d9974ebdd..7b264e49668 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -9,6 +9,7 @@ #import "SentryEvent+Private.h" #import "SentryFileManager.h" #import "SentryId.h" +#import "SentryLevelMapper.h" #import "SentryLog.h" #import "SentryNSTimerWrapper.h" #import "SentryPerformanceTracker.h" @@ -590,37 +591,64 @@ - (void)captureEnvelope:(SentryEnvelope *)envelope - (SentryEnvelope *)updateSessionState:(SentryEnvelope *)envelope { - if ([self envelopeContainsEventWithErrorOrHigher:envelope.items]) { + BOOL handled = YES; + if ([self envelopeContainsEventWithErrorOrHigher:envelope.items wasHandled:&handled]) { SentrySession *currentSession = [self incrementSessionErrors]; if (currentSession != nil) { + if (!handled) { + [currentSession endSessionCrashedWithTimestamp:[_currentDateProvider date]]; + + _session = [[SentrySession alloc] initWithReleaseName:_client.options.releaseName]; + _session.environment = _client.options.environment; + [self.scope applyToSession:_session]; + } + // Create a new envelope with the session update NSMutableArray *itemsToSend = [[NSMutableArray alloc] initWithArray:envelope.items]; [itemsToSend addObject:[[SentryEnvelopeItem alloc] initWithSession:currentSession]]; - return [[SentryEnvelope alloc] initWithHeader:envelope.header items:itemsToSend]; } } - return envelope; } - (BOOL)envelopeContainsEventWithErrorOrHigher:(NSArray *)items + wasHandled:(BOOL *)handled; { for (SentryEnvelopeItem *item in items) { if ([item.header.type isEqualToString:SentryEnvelopeItemTypeEvent]) { // If there is no level the default is error - SentryLevel level = [SentrySerialization levelFromData:item.data]; + NSDictionary *eventJson = [SentrySerialization eventEnvelopeItemJson:item.data]; + if (eventJson == nil) { + return NO; + } + + SentryLevel level = sentryLevelForString(eventJson[@"level"]); if (level >= kSentryLevelError) { + *handled = [self envelopeEventItemContainsUnhandledError:eventJson]; return YES; } } } - return NO; } +- (BOOL)envelopeEventItemContainsUnhandledError:(NSDictionary *)eventDictionary +{ + NSArray *exceptions = eventDictionary[@"exception"][@"values"]; + for (NSDictionary *exception in exceptions) { + NSDictionary *mechanism = exception[@"mechanism"]; + NSNumber *handled = mechanism[@"handled"]; + + if ([handled boolValue] == NO) { + return NO; + } + } + return YES; +} + - (void)reportFullyDisplayed { #if SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/SentrySerialization.m b/Sources/Sentry/SentrySerialization.m index abebeaf9fa4..9d9c0652a21 100644 --- a/Sources/Sentry/SentrySerialization.m +++ b/Sources/Sentry/SentrySerialization.m @@ -339,6 +339,24 @@ + (SentryAppState *_Nullable)appStateWithData:(NSData *)data return [[SentryAppState alloc] initWithJSONObject:appSateDictionary]; } ++ (NSDictionary *)eventEnvelopeItemJson:(NSData *)eventEnvelopeItemData +{ + NSError *error = nil; + NSDictionary *eventDictionary = [NSJSONSerialization JSONObjectWithData:eventEnvelopeItemData + options:0 + error:&error]; + if (nil != error) { + [SentryLog + logWithMessage: + [NSString + stringWithFormat:@"Failed to retrieve event level from envelope item data: %@", + error] + andLevel:kSentryLevelError]; + } + + return eventDictionary; +} + + (SentryLevel)levelFromData:(NSData *)eventEnvelopeItemData { NSError *error = nil; diff --git a/Sources/Sentry/include/SentryHub+Private.h b/Sources/Sentry/include/SentryHub+Private.h index 622911d03cc..025b3a12a1c 100644 --- a/Sources/Sentry/include/SentryHub+Private.h +++ b/Sources/Sentry/include/SentryHub+Private.h @@ -2,7 +2,7 @@ #import "SentryTracer.h" @class SentryEnvelopeItem, SentryId, SentryScope, SentryTransaction, SentryDispatchQueueWrapper, - SentryEnvelope, SentryNSTimerWrapper; + SentryEnvelope, SentryNSTimerWrapper, SentrySession; NS_ASSUME_NONNULL_BEGIN @@ -11,6 +11,7 @@ SentryHub (Private) @property (nonatomic, strong) NSArray> *installedIntegrations; @property (nonatomic, strong) NSSet *installedIntegrationNames; +@property (nullable, nonatomic, strong) SentrySession *session; - (void)addInstalledIntegration:(id)integration name:(NSString *)name; - (void)removeAllIntegrations; diff --git a/Sources/Sentry/include/SentrySerialization.h b/Sources/Sentry/include/SentrySerialization.h index 4c83c1ef110..0160b9f6b41 100644 --- a/Sources/Sentry/include/SentrySerialization.h +++ b/Sources/Sentry/include/SentrySerialization.h @@ -24,6 +24,11 @@ static int const SENTRY_BAGGAGE_MAX_SIZE = 8192; + (SentryAppState *_Nullable)appStateWithData:(NSData *)sessionData; +/** + * Retrieves the json object from an event envelope item data. + */ ++ (NSDictionary *)eventEnvelopeItemJson:(NSData *)eventEnvelopeItemData; + /** * Extract the level from data of an envelopte item containing an event. Default is the 'error' * level, see https://develop.sentry.dev/sdk/event-payloads/#optional-attributes diff --git a/Tests/SentryTests/SentryHubTests.swift b/Tests/SentryTests/SentryHubTests.swift index ab7ed2f0b04..9ddfbf894a5 100644 --- a/Tests/SentryTests/SentryHubTests.swift +++ b/Tests/SentryTests/SentryHubTests.swift @@ -715,6 +715,45 @@ class SentryHubTests: XCTestCase { XCTAssertEqual(envelope, fixture.client.captureEnvelopeInvocations.first) } + func testCaptureEnvelope_WithUnhandledException() { + sut.startSession() + + let beginSession = sut.session + + let event = TestData.event + event.level = .error + event.exceptions = [TestData.exception] + event.exceptions?.first?.mechanism?.handled = NSNumber(booleanLiteral: false) + sut.capture(SentryEnvelope(event: event)) + + let endSession = sut.session + XCTAssertNotEqual(beginSession, endSession) + + //Check whether session was finished as crashed + let envelope = fixture.client.captureEnvelopeInvocations.first + let sessionEnvelopeItem = envelope?.items.first(where: { $0.header.type == "session" }) + + let json = (try! JSONSerialization.jsonObject(with: sessionEnvelopeItem!.data)) as! [String: Any] + + XCTAssertNotNil(json["timestamp"]) + XCTAssertEqual(json["status"] as? String, "crashed") + } + + func testCaptureEnvelope_WithHandledException() { + sut.startSession() + + let beginSession = sut.session + + let event = TestData.event + event.level = .error + event.exceptions = [TestData.exception] + sut.capture(SentryEnvelope(event: event)) + + let endSession = sut.session + + XCTAssertEqual(beginSession, endSession) + } + #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) func test_reportFullyDisplayed_enableTimeToFullDisplay_YES() { fixture.options.enableTimeToFullDisplay = true From a187d02e7180308dcc105c42af50c10ec856b1f3 Mon Sep 17 00:00:00 2001 From: Dhiogo Ramos Brustolin Date: Fri, 9 Jun 2023 12:13:30 +0200 Subject: [PATCH 2/3] Update SentryHubTests.swift --- Tests/SentryTests/SentryHubTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SentryTests/SentryHubTests.swift b/Tests/SentryTests/SentryHubTests.swift index 9ddfbf894a5..5b2c255d0ea 100644 --- a/Tests/SentryTests/SentryHubTests.swift +++ b/Tests/SentryTests/SentryHubTests.swift @@ -723,7 +723,7 @@ class SentryHubTests: XCTestCase { let event = TestData.event event.level = .error event.exceptions = [TestData.exception] - event.exceptions?.first?.mechanism?.handled = NSNumber(booleanLiteral: false) + event.exceptions?.first?.mechanism?.handled = false sut.capture(SentryEnvelope(event: event)) let endSession = sut.session From 7a33aa96361687f671165d64c60619687edd3d2a Mon Sep 17 00:00:00 2001 From: Dhiogo Ramos Brustolin Date: Tue, 13 Jun 2023 14:39:01 +0200 Subject: [PATCH 3/3] suggestions --- Sources/Sentry/SentryHub.m | 6 +++--- Sources/Sentry/SentrySerialization.m | 4 ++-- Sources/Sentry/include/SentrySerialization.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index 7b264e49668..b22c7706cf9 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -620,14 +620,14 @@ - (BOOL)envelopeContainsEventWithErrorOrHigher:(NSArray *) for (SentryEnvelopeItem *item in items) { if ([item.header.type isEqualToString:SentryEnvelopeItemTypeEvent]) { // If there is no level the default is error - NSDictionary *eventJson = [SentrySerialization eventEnvelopeItemJson:item.data]; + NSDictionary *eventJson = [SentrySerialization deserializeEventEnvelopeItem:item.data]; if (eventJson == nil) { return NO; } SentryLevel level = sentryLevelForString(eventJson[@"level"]); if (level >= kSentryLevelError) { - *handled = [self envelopeEventItemContainsUnhandledError:eventJson]; + *handled = [self eventContainsUnhandledError:eventJson]; return YES; } } @@ -635,7 +635,7 @@ - (BOOL)envelopeContainsEventWithErrorOrHigher:(NSArray *) return NO; } -- (BOOL)envelopeEventItemContainsUnhandledError:(NSDictionary *)eventDictionary +- (BOOL)eventContainsUnhandledError:(NSDictionary *)eventDictionary { NSArray *exceptions = eventDictionary[@"exception"][@"values"]; for (NSDictionary *exception in exceptions) { diff --git a/Sources/Sentry/SentrySerialization.m b/Sources/Sentry/SentrySerialization.m index 9d9c0652a21..d091f4689f6 100644 --- a/Sources/Sentry/SentrySerialization.m +++ b/Sources/Sentry/SentrySerialization.m @@ -339,7 +339,7 @@ + (SentryAppState *_Nullable)appStateWithData:(NSData *)data return [[SentryAppState alloc] initWithJSONObject:appSateDictionary]; } -+ (NSDictionary *)eventEnvelopeItemJson:(NSData *)eventEnvelopeItemData ++ (NSDictionary *)deserializeEventEnvelopeItem:(NSData *)eventEnvelopeItemData { NSError *error = nil; NSDictionary *eventDictionary = [NSJSONSerialization JSONObjectWithData:eventEnvelopeItemData @@ -349,7 +349,7 @@ + (NSDictionary *)eventEnvelopeItemJson:(NSData *)eventEnvelopeItemData [SentryLog logWithMessage: [NSString - stringWithFormat:@"Failed to retrieve event level from envelope item data: %@", + stringWithFormat:@"Failed to deserialize envelope item data: %@", error] andLevel:kSentryLevelError]; } diff --git a/Sources/Sentry/include/SentrySerialization.h b/Sources/Sentry/include/SentrySerialization.h index 0160b9f6b41..fbfcec32e4d 100644 --- a/Sources/Sentry/include/SentrySerialization.h +++ b/Sources/Sentry/include/SentrySerialization.h @@ -27,7 +27,7 @@ static int const SENTRY_BAGGAGE_MAX_SIZE = 8192; /** * Retrieves the json object from an event envelope item data. */ -+ (NSDictionary *)eventEnvelopeItemJson:(NSData *)eventEnvelopeItemData; ++ (NSDictionary *)deserializeEventEnvelopeItem:(NSData *)eventEnvelopeItemData; /** * Extract the level from data of an envelopte item containing an event. Default is the 'error'