Skip to content

Commit

Permalink
fix(sessions): storeEnvelope updates session when passed unhandled …
Browse files Browse the repository at this point in the history
…event (#4073)
  • Loading branch information
krystofwoldrich authored Jun 20, 2024
1 parent ceb2092 commit 8001609
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- Add pause and resume AppHangTracking API (#4077). You can now pause and resume app hang tracking with `SentrySDK.pauseAppHangTracking()` and `SentrySDK.resumeAppHangTracking()`.

### Fixes

- `storeEnvelope` ends session for unhandled errors (#4073)

## 8.29.1

### Fixes
Expand Down
14 changes: 9 additions & 5 deletions SentryTestUtils/TestHub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ import Foundation

public class TestHub: SentryHub {

var startSessionInvocations: Int = 0
var closeCachedSessionInvocations: Int = 0
var endSessionTimestamp: Date?
var closeCachedSessionTimestamp: Date?
public var startSessionInvocations: Int = 0
public var closeCachedSessionInvocations: Int = 0
public var endSessionTimestamp: Date?
public var closeCachedSessionTimestamp: Date?

public override func startSession() {
startSessionInvocations += 1
}


public func setTestSession() {
self.session = SentrySession(releaseName: "Test Release", distinctId: "123")
}

public override func closeCachedSession(withTimestamp timestamp: Date?) {
closeCachedSessionTimestamp = timestamp
closeCachedSessionInvocations += 1
Expand Down
53 changes: 43 additions & 10 deletions Sources/Sentry/SentryHub.m
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,9 @@ - (void)captureSession:(nullable SentrySession *)session
SentryClient *client = _client;

if (client.options.diagnosticLevel == kSentryLevelDebug) {
NSData *sessionData = [NSJSONSerialization dataWithJSONObject:[session serialize]
options:0
error:nil];
NSString *sessionString = [[NSString alloc] initWithData:sessionData
encoding:NSUTF8StringEncoding];
[SentryLog
logWithMessage:[NSString stringWithFormat:@"Capturing session with status: %@",
sessionString]
[self createSessionDebugString:session]]
andLevel:kSentryLevelDebug];
}
[client captureSession:session];
Expand Down Expand Up @@ -630,17 +625,35 @@ - (void)setUser:(nullable SentryUser *)user
}
}

/**
* Needed by hybrid SDKs as react-native to synchronously store an envelope to disk.
*/
- (void)storeEnvelope:(SentryEnvelope *)envelope
{
SentryClient *client = _client;
if (client == nil) {
return;
}

// Envelopes are stored only when crash occurs. We should not start a new session when
// the app is about to crash.
[client storeEnvelope:[self updateSessionState:envelope startNewSession:NO]];
}

- (void)captureEnvelope:(SentryEnvelope *)envelope
{
SentryClient *client = _client;
if (client == nil) {
return;
}

[client captureEnvelope:[self updateSessionState:envelope]];
// If captured envelope cointains not handled errors, these are not going to crash the app and
// we should create new session.
[client captureEnvelope:[self updateSessionState:envelope startNewSession:YES]];
}

- (SentryEnvelope *)updateSessionState:(SentryEnvelope *)envelope
startNewSession:(BOOL)startNewSession
{
BOOL handled = YES;
if ([self envelopeContainsEventWithErrorOrHigher:envelope.items wasHandled:&handled]) {
Expand All @@ -654,9 +667,17 @@ - (SentryEnvelope *)updateSessionState:(SentryEnvelope *)envelope
[currentSession
endSessionCrashedWithTimestamp:[SentryDependencyContainer.sharedInstance
.dateProvider date]];
// Setting _session to nil so startSession doesn't capture it again
_session = nil;
[self startSession];
if (_client.options.diagnosticLevel == kSentryLevelDebug) {
[SentryLog
logWithMessage:[NSString stringWithFormat:@"Ending session with status: %@",
[self createSessionDebugString:currentSession]]
andLevel:kSentryLevelDebug];
}
if (startNewSession) {
// Setting _session to nil so startSession doesn't capture it again
_session = nil;
[self startSession];
}
}
}

Expand Down Expand Up @@ -715,6 +736,18 @@ - (void)reportFullyDisplayed
#endif // SENTRY_HAS_UIKIT
}

- (NSString *)createSessionDebugString:(SentrySession *)session
{
if (session == nil) {
return @"Session is nil.";
}

NSData *sessionData = [NSJSONSerialization dataWithJSONObject:[session serialize]
options:0
error:nil];
return [[NSString alloc] initWithData:sessionData encoding:NSUTF8StringEncoding];
}

- (void)flush:(NSTimeInterval)timeout
{
[_metrics flush];
Expand Down
4 changes: 1 addition & 3 deletions Sources/Sentry/SentrySDK.m
Original file line number Diff line number Diff line change
Expand Up @@ -384,9 +384,7 @@ + (void)captureEnvelope:(SentryEnvelope *)envelope
*/
+ (void)storeEnvelope:(SentryEnvelope *)envelope
{
if (nil != [SentrySDK.currentHub getClient]) {
[[SentrySDK.currentHub getClient] storeEnvelope:envelope];
}
[SentrySDK.currentHub storeEnvelope:envelope];
}

+ (void)captureUserFeedback:(SentryUserFeedback *)userFeedback
Expand Down
1 change: 1 addition & 0 deletions Sources/Sentry/include/SentryHub+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ SentryHub ()
withScope:(SentryScope *)scope
additionalEnvelopeItems:(NSArray<SentryEnvelopeItem *> *)additionalEnvelopeItems;

- (void)storeEnvelope:(SentryEnvelope *)envelope;
- (void)captureEnvelope:(SentryEnvelope *)envelope;

- (nullable id<SentryIntegrationProtocol>)getInstalledIntegration:(Class)integrationClass;
Expand Down
50 changes: 50 additions & 0 deletions Tests/SentryTests/PrivateSentrySDKOnlyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,27 @@ class PrivateSentrySDKOnlyTests: XCTestCase {
XCTAssertEqual(1, client?.storedEnvelopeInvocations.count)
XCTAssertEqual(envelope, client?.storedEnvelopeInvocations.first)
}

func testStoreEnvelopeWithUndhandled_MarksSessionAsCrashedAndDoesNotStartNewSession() {
let client = TestClient(options: Options())
let hub = TestHub(client: client, andScope: nil)
SentrySDK.setCurrentHub(hub)
hub.setTestSession()
let sessionToBeCrashed = hub.session

let envelope = getUnhandledExceptionEnvelope()
PrivateSentrySDKOnly.store(envelope)

let storedEnvelope = client?.storedEnvelopeInvocations.first
let attachedSessionData = storedEnvelope!.items.last!.data
let attachedSession = try! JSONSerialization.jsonObject(with: attachedSessionData) as! [String: Any]

XCTAssertEqual(0, hub.startSessionInvocations)
// Assert crashed session was attached to the envelope
XCTAssertEqual(sessionToBeCrashed!.sessionId.uuidString, attachedSession["sid"] as! String)
XCTAssertEqual("crashed", attachedSession["status"] as! String)
}

func testCaptureEnvelope() {
let client = TestClient(options: Options())
SentrySDK.setCurrentHub(TestHub(client: client, andScope: nil))
Expand All @@ -29,6 +49,27 @@ class PrivateSentrySDKOnlyTests: XCTestCase {
XCTAssertEqual(1, client?.captureEnvelopeInvocations.count)
XCTAssertEqual(envelope, client?.captureEnvelopeInvocations.first)
}

func testCaptureEnvelopeWithUndhandled_MarksSessionAsCrashedAndStartsNewSession() {
let client = TestClient(options: Options())
let hub = TestHub(client: client, andScope: nil)
SentrySDK.setCurrentHub(hub)
hub.setTestSession()
let sessionToBeCrashed = hub.session

let envelope = getUnhandledExceptionEnvelope()
PrivateSentrySDKOnly.capture(envelope)

let capturedEnvelope = client?.captureEnvelopeInvocations.first
let attachedSessionData = capturedEnvelope!.items.last!.data
let attachedSession = try! JSONSerialization.jsonObject(with: attachedSessionData) as! [String: Any]

// Assert new session was started
XCTAssertEqual(1, hub.startSessionInvocations)
// Assert crashed session was attached to the envelope
XCTAssertEqual(sessionToBeCrashed!.sessionId.uuidString, attachedSession["sid"] as! String)
XCTAssertEqual("crashed", attachedSession["status"] as! String)
}

func testSetSdkName() {
let originalName = PrivateSentrySDKOnly.getSdkName()
Expand Down Expand Up @@ -343,4 +384,13 @@ class PrivateSentrySDKOnlyTests: XCTestCase {
}
}
#endif

func getUnhandledExceptionEnvelope() -> SentryEnvelope {
let event = Event()
event.message = SentryMessage(formatted: "Test Event with unhandled exception")
event.level = .error
event.exceptions = [TestData.exception]
event.exceptions?.first?.mechanism?.handled = false
return SentryEnvelope(event: event)
}
}

0 comments on commit 8001609

Please sign in to comment.