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/8] 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/8] 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/8] 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' From 078d671e0b2e8e27d3b9ac44cefe4205396066cb Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Tue, 13 Jun 2023 12:39:19 +0000 Subject: [PATCH 4/8] Format code --- Sources/Sentry/SentrySerialization.m | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/Sentry/SentrySerialization.m b/Sources/Sentry/SentrySerialization.m index d091f4689f6..27fe09500f0 100644 --- a/Sources/Sentry/SentrySerialization.m +++ b/Sources/Sentry/SentrySerialization.m @@ -347,10 +347,9 @@ + (NSDictionary *)deserializeEventEnvelopeItem:(NSData *)eventEnvelopeItemData error:&error]; if (nil != error) { [SentryLog - logWithMessage: - [NSString - stringWithFormat:@"Failed to deserialize envelope item data: %@", - error] + logWithMessage:[NSString + stringWithFormat:@"Failed to deserialize envelope item data: %@", + error] andLevel:kSentryLevelError]; } From 2ce0cbeccbe6a4c16b5960de25c18f045d776988 Mon Sep 17 00:00:00 2001 From: Dhiogo Ramos Brustolin Date: Tue, 13 Jun 2023 15:22:46 +0200 Subject: [PATCH 5/8] Update SentryHubTests.swift --- Tests/SentryTests/SentryHubTests.swift | 224 +++++++++++++------------ 1 file changed, 113 insertions(+), 111 deletions(-) diff --git a/Tests/SentryTests/SentryHubTests.swift b/Tests/SentryTests/SentryHubTests.swift index 5b2c255d0ea..a572f9197b4 100644 --- a/Tests/SentryTests/SentryHubTests.swift +++ b/Tests/SentryTests/SentryHubTests.swift @@ -5,7 +5,7 @@ import XCTest class SentryHubTests: XCTestCase { private static let dsnAsString = TestConstants.dsnAsString(username: "SentryHubTests") - + private class Fixture { let options: Options let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Object does not exist"]) @@ -78,7 +78,7 @@ class SentryHubTests: XCTestCase { fixture.fileManager.deleteTimestampLastInForeground() fixture.fileManager.deleteAllEnvelopes() } - + func testBeforeBreadcrumbWithoutCallbackStoresBreadcrumb() { let hub = fixture.getSut() @@ -107,24 +107,24 @@ class SentryHubTests: XCTestCase { func testBreadcrumbLimitThroughOptionsUsingHubAddBreadcrumb() { let hub = fixture.getSut(withMaxBreadcrumbs: 10) - + for _ in 0...10 { let crumb = Breadcrumb( level: .error, category: "default") hub.add(crumb) } - + assert(withScopeBreadcrumbsCount: 10, with: hub) } func testBreadcrumbLimitThroughOptionsUsingConfigureScope() { let hub = fixture.getSut(withMaxBreadcrumbs: 10) - + for _ in 0...10 { addBreadcrumbThroughConfigureScope(hub) } - + assert(withScopeBreadcrumbsCount: 10, with: hub) } @@ -133,11 +133,11 @@ class SentryHubTests: XCTestCase { SentryLog.configure(true, diagnosticLevel: .error) let hub = fixture.getSut() - + for _ in 0...100 { addBreadcrumbThroughConfigureScope(hub) } - + assert(withScopeBreadcrumbsCount: 100, with: hub) setTestDefaultLogLevel() @@ -145,11 +145,11 @@ class SentryHubTests: XCTestCase { func testBreadcrumbOverDefaultLimit() { let hub = fixture.getSut(withMaxBreadcrumbs: 200) - + for _ in 0...200 { addBreadcrumbThroughConfigureScope(hub) } - + assert(withScopeBreadcrumbsCount: 200, with: hub) } @@ -185,15 +185,15 @@ class SentryHubTests: XCTestCase { func testAddUserToTheScope() throws { let client = SentryClient(options: fixture.options, fileManager: try TestFileManager(options: fixture.options), deleteOldEnvelopeItems: false) let hub = SentryHub(client: client, andScope: Scope()) - + let user = User() user.userId = "123" hub.setUser(user) - + let scopeSerialized = hub.scope.serialize() let scopeUser = scopeSerialized["user"] as? [String: Any?] let scopeUserId = scopeUser?["id"] as? String - + XCTAssertEqual(scopeUserId, "123") } @@ -237,7 +237,7 @@ class SentryHubTests: XCTestCase { XCTAssertEqual(span.operation, fixture.transactionOperation) XCTAssertEqual("manual", tracer.transactionContext.origin) } - + func testStartTransactionWithNameSource() { let span = fixture.getSut().startTransaction(transactionContext: TransactionContext( name: fixture.transactionName, @@ -245,7 +245,7 @@ class SentryHubTests: XCTestCase { operation: fixture.transactionOperation, origin: fixture.traceOrigin )) - + let tracer = span as! SentryTracer XCTAssertEqual(tracer.transactionContext.name, fixture.transactionName) XCTAssertEqual(tracer.transactionContext.nameSource, SentryTransactionNameSource.url) @@ -303,7 +303,7 @@ class SentryHubTests: XCTestCase { options.tracesSampleRate = 0.50 } } - + func testStartTransactionSamplingUsingTracesSampler() { assertSampler(expected: .yes) { options in options.tracesSampler = { _ in return 0.51 } @@ -334,27 +334,27 @@ class SentryHubTests: XCTestCase { let span = hub.startTransaction(name: fixture.transactionName, operation: fixture.transactionOperation) XCTAssertEqual(span.sampled, .no) } - + func testCaptureSampledTransaction_ReturnsEmptyId() { let transaction = sut.startTransaction(transactionContext: TransactionContext(name: fixture.transactionName, operation: fixture.transactionOperation, sampled: .no)) - + let trans = Dynamic(transaction).toTransaction().asAnyObject let id = sut.capture(trans as! Transaction, with: Scope()) id.assertIsEmpty() } - + func testCaptureSampledTransaction_RecordsLostEvent() { let transaction = sut.startTransaction(transactionContext: TransactionContext(name: fixture.transactionName, operation: fixture.transactionOperation, sampled: .no)) - + let trans = Dynamic(transaction).toTransaction().asAnyObject sut.capture(trans as! Transaction, with: Scope()) - + XCTAssertEqual(1, fixture.client.recordLostEvents.count) let lostEvent = fixture.client.recordLostEvents.first XCTAssertEqual(.transaction, lostEvent?.category) XCTAssertEqual(.sampleRate, lostEvent?.reason) } - + func testCaptureMessageWithScope() { fixture.getSut().capture(message: fixture.message, scope: fixture.scope) @@ -403,72 +403,72 @@ class SentryHubTests: XCTestCase { // only session init is sent XCTAssertEqual(1, fixture.client.captureSessionInvocations.count) } - + func testCaptureErrorBeforeSessionStart() { let sut = fixture.getSut() sut.capture(error: fixture.error, scope: fixture.scope).assertIsNotEmpty() sut.startSession() - + XCTAssertEqual(fixture.client.captureErrorWithScopeInvocations.count, 1) XCTAssertEqual(fixture.client.captureSessionInvocations.count, 1) - + if let session = fixture.client.captureSessionInvocations.first { XCTAssertEqual(session.errors, 1) } } - + func testCaptureErrorBeforeSessionStart_DisabledAutoSessionTracking() { fixture.options.enableAutoSessionTracking = false let sut = fixture.getSut() sut.capture(error: fixture.error, scope: fixture.scope).assertIsNotEmpty() sut.startSession() - + XCTAssertEqual(fixture.client.captureErrorWithScopeInvocations.count, 1) XCTAssertEqual(fixture.client.captureSessionInvocations.count, 1) - + if let session = fixture.client.captureSessionInvocations.first { XCTAssertEqual(session.errors, 0) } } - + func testCaptureError_SessionWithDefaultEnvironment() { let sut = fixture.getSut() sut.startSession() sut.capture(error: fixture.error, scope: fixture.scope).assertIsNotEmpty() - + XCTAssertEqual(fixture.client.captureSessionInvocations.count, 1) - + if let session = fixture.client.captureSessionInvocations.first { XCTAssertEqual(session.environment, "production") } } - + func testCaptureError_SessionWithEnvironmentFromOptions() { fixture.options.environment = "test-env" let sut = fixture.getSut() sut.startSession() sut.capture(error: fixture.error, scope: fixture.scope).assertIsNotEmpty() - + XCTAssertEqual(fixture.client.captureSessionInvocations.count, 1) - + if let session = fixture.client.captureSessionInvocations.first { XCTAssertEqual(session.environment, "test-env") } } - + func testCaptureWithoutIncreasingErrorCount() { let sut = fixture.getSut() sut.startSession() fixture.client.callSessionBlockWithIncrementSessionErrors = false sut.capture(error: fixture.error, scope: fixture.scope).assertIsNotEmpty() - + XCTAssertEqual(1, fixture.client.captureErrorWithSessionInvocations.count) if let errorArguments = fixture.client.captureErrorWithSessionInvocations.first { XCTAssertEqual(fixture.error, errorArguments.error as NSError) XCTAssertNil(errorArguments.session) XCTAssertEqual(fixture.scope, errorArguments.scope) } - + // only session init is sent XCTAssertEqual(1, fixture.client.captureSessionInvocations.count) } @@ -521,20 +521,20 @@ class SentryHubTests: XCTestCase { // only session init is sent XCTAssertEqual(1, fixture.client.captureSessionInvocations.count) } - + func testCaptureExceptionWithoutIncreasingErrorCount() { let sut = fixture.getSut() sut.startSession() fixture.client.callSessionBlockWithIncrementSessionErrors = false sut.capture(exception: fixture.exception, scope: fixture.scope).assertIsNotEmpty() - + XCTAssertEqual(1, fixture.client.captureExceptionWithSessionInvocations.count) if let exceptionArguments = fixture.client.captureExceptionWithSessionInvocations.first { XCTAssertEqual(fixture.exception, exceptionArguments.exception) XCTAssertNil(exceptionArguments.session) XCTAssertEqual(fixture.scope, exceptionArguments.scope) } - + // only session init is sent XCTAssertEqual(1, fixture.client.captureSessionInvocations.count) } @@ -594,7 +594,7 @@ class SentryHubTests: XCTestCase { givenCrashedSession() assertNoCrashedSessionSent() - + sut.captureCrash(fixture.event) assertEventSentWithSession() @@ -607,7 +607,7 @@ class SentryHubTests: XCTestCase { func testCaptureCrashEvent_CrashedSessionDoesNotExist() { sut.startSession() // there is already an existing session sut.captureCrash(fixture.event) - + assertNoCrashedSessionSent() assertCrashEventSent() } @@ -632,7 +632,7 @@ class SentryHubTests: XCTestCase { func testCaptureCrashEvent_SessionExistsButAutoSessionTrackingDisabled() { givenAutoSessionTrackingDisabled() givenCrashedSession() - + sut.captureCrash(fixture.event) assertCrashEventSent() @@ -706,7 +706,7 @@ class SentryHubTests: XCTestCase { assertNoEnvelopesCaptured() } - + func testCaptureEnvelope_WithSession() { let envelope = SentryEnvelope(session: SentrySession(releaseName: "")) sut.capture(envelope) @@ -714,85 +714,87 @@ class SentryHubTests: XCTestCase { XCTAssertEqual(1, fixture.client.captureEnvelopeInvocations.count) 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 = 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) - } - + sut.startSession() + + fixture.currentDateProvider.setDate(date: Date(timeIntervalSince1970: 2)) + + let beginSession = sut.session + + let event = TestData.event + event.level = .error + event.exceptions = [TestData.exception] + event.exceptions?.first?.mechanism?.handled = 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] + + XCTAssertEqual(json["timestamp"] as? String, "1970-01-01T00:00:02.000Z") + 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 let sut = fixture.getSut(fixture.options) - + let testTTDTracker = TestTimeToDisplayTracker() - + Dynamic(SentryUIViewControllerPerformanceTracker.shared).currentTTDTracker = testTTDTracker - + sut.reportFullyDisplayed() - + XCTAssertTrue(testTTDTracker.registerFullDisplayCalled) - + } - + func test_reportFullyDisplayed_enableTimeToFullDisplay_NO() { fixture.options.enableTimeToFullDisplay = false let sut = fixture.getSut(fixture.options) - + let testTTDTracker = TestTimeToDisplayTracker() - + Dynamic(SentryUIViewControllerPerformanceTracker.shared).currentTTDTracker = testTTDTracker - + sut.reportFullyDisplayed() - + XCTAssertFalse(testTTDTracker.registerFullDisplayCalled) } #endif - + private func addBreadcrumbThroughConfigureScope(_ hub: SentryHub) { hub.configureScope({ scope in scope.addBreadcrumb(self.fixture.crumb) }) } - + private func captureConcurrentWithSession(count: Int, _ capture: @escaping (SentryHub) -> Void) { let sut = fixture.getSut() sut.startSession() - + let queue = fixture.queue let group = DispatchGroup() for _ in 0.. Void) { options(fixture.options) - + let hub = fixture.getSut() Dynamic(hub).tracesSampler.random = fixture.random - + let span = hub.startTransaction(name: fixture.transactionName, operation: fixture.transactionOperation) - + XCTAssertEqual(expected, span.sampled) } } #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) class TestTimeToDisplayTracker: SentryTimeToDisplayTracker { - + init() { super.init(for: UIViewController(), framesTracker: SentryFramesTracker.sharedInstance(), waitForFullDisplay: false) } - + var registerFullDisplayCalled = false override func reportFullyDisplayed() { registerFullDisplayCalled = true } - + } #endif From edca2c688f3f3e569dbac73f5f4dfd7a5043a186 Mon Sep 17 00:00:00 2001 From: Dhiogo Ramos Brustolin Date: Tue, 13 Jun 2023 16:45:18 +0200 Subject: [PATCH 6/8] Update SentryHub.m --- Sources/Sentry/SentryHub.m | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index b22c7706cf9..f8ed56e9d4b 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -593,23 +593,26 @@ - (SentryEnvelope *)updateSessionState:(SentryEnvelope *)envelope { BOOL handled = YES; if ([self envelopeContainsEventWithErrorOrHigher:envelope.items wasHandled:&handled]) { - SentrySession *currentSession = [self incrementSessionErrors]; - - if (currentSession != nil) { + SentrySession *currentSession; + @synchronized (_sessionLock) { + currentSession = handled ? [self incrementSessionErrors] : [_session copy]; + if (currentSession == nil) { + return envelope; + } if (!handled) { [currentSession endSessionCrashedWithTimestamp:[_currentDateProvider date]]; - - _session = [[SentrySession alloc] initWithReleaseName:_client.options.releaseName]; - _session.environment = _client.options.environment; - [self.scope applyToSession:_session]; + //Setting _session to nil so starSession dont capture it again + _session = nil; + [self startSession]; } - - // 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]; } + + // 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; } From aab4503b2c19712928d5b442c9f307e621a41dfc Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Tue, 13 Jun 2023 14:46:29 +0000 Subject: [PATCH 7/8] Format code --- Sources/Sentry/SentryHub.m | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index f8ed56e9d4b..a15c480d113 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -594,14 +594,14 @@ - (SentryEnvelope *)updateSessionState:(SentryEnvelope *)envelope BOOL handled = YES; if ([self envelopeContainsEventWithErrorOrHigher:envelope.items wasHandled:&handled]) { SentrySession *currentSession; - @synchronized (_sessionLock) { + @synchronized(_sessionLock) { currentSession = handled ? [self incrementSessionErrors] : [_session copy]; if (currentSession == nil) { return envelope; } if (!handled) { [currentSession endSessionCrashedWithTimestamp:[_currentDateProvider date]]; - //Setting _session to nil so starSession dont capture it again + // Setting _session to nil so starSession dont capture it again _session = nil; [self startSession]; } @@ -609,10 +609,9 @@ - (SentryEnvelope *)updateSessionState:(SentryEnvelope *)envelope // Create a new envelope with the session update NSMutableArray *itemsToSend = - [[NSMutableArray alloc] initWithArray:envelope.items]; + [[NSMutableArray alloc] initWithArray:envelope.items]; [itemsToSend addObject:[[SentryEnvelopeItem alloc] initWithSession:currentSession]]; return [[SentryEnvelope alloc] initWithHeader:envelope.header items:itemsToSend]; - } return envelope; } From 99177cfcd5d188c9436e74940f231efabe89db3a Mon Sep 17 00:00:00 2001 From: Dhiogo Ramos Brustolin Date: Wed, 14 Jun 2023 12:07:10 +0200 Subject: [PATCH 8/8] Update SentryHubTests.swift --- Tests/SentryTests/SentryHubTests.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Tests/SentryTests/SentryHubTests.swift b/Tests/SentryTests/SentryHubTests.swift index a572f9197b4..3fe7fd81fab 100644 --- a/Tests/SentryTests/SentryHubTests.swift +++ b/Tests/SentryTests/SentryHubTests.swift @@ -720,17 +720,12 @@ class SentryHubTests: XCTestCase { fixture.currentDateProvider.setDate(date: Date(timeIntervalSince1970: 2)) - let beginSession = sut.session - let event = TestData.event event.level = .error event.exceptions = [TestData.exception] event.exceptions?.first?.mechanism?.handled = 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" })