Skip to content
Draft
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
- Change `value` and `type` of `SentryException` to be nullable (#6563)
- Change the default trace context status to "ok" instead of "undefined" (#6611)
- Remove `getHash` from SentryDsn (#6605)
- [HTTP Client errors](https://docs.sentry.io/platforms/apple/guides/ios/configuration/http-client-errors/) now mark sessions as errored (#6633)
- The precompiled XCFramework is now built with Xcode 16. To submit to the App Store, [Apple now requires Xcode 16](https://developer.apple.com/news/upcoming-requirements/?id=02212025a).
If you need a precompiled XCFramework built with Xcode 15, continue using Sentry SDK 8.x.x.
- Set `SentryException.type` to `nil` when `NSException` has no `reason` (#6653). The backend then can provide a proper message when there is no reason.
Expand Down
19 changes: 14 additions & 5 deletions SentryTestUtils/Sources/TestClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,19 +94,28 @@ public class TestClient: SentryClientInternal {
@_spi(Private)
public var captureErrorWithSessionInvocations = Invocations<(error: Error, session: SentrySession?, scope: Scope)>()
@_spi(Private)
public override func captureError(_ error: Error, with scope: Scope, incrementSessionErrors sessionBlock: @escaping () -> SentrySession) -> SentryId {
captureErrorWithSessionInvocations.record((error, callSessionBlockWithIncrementSessionErrors ? sessionBlock() : nil, scope))
public override func captureErrorIncrementingSessionErrorCount(_ error: Error, with scope: Scope) -> SentryId {
let session = callSessionBlockWithIncrementSessionErrors ? sessionDelegate?.incrementSessionErrors() : nil
captureErrorWithSessionInvocations.record((error, session, scope))
return SentryId()
}

@_spi(Private)
public var captureExceptionWithSessionInvocations = Invocations<(exception: NSException, session: SentrySession?, scope: Scope)>()
@_spi(Private)
public override func capture(_ exception: NSException, with scope: Scope, incrementSessionErrors sessionBlock: @escaping () -> SentrySession) -> SentryId {
captureExceptionWithSessionInvocations.record((exception, callSessionBlockWithIncrementSessionErrors ? sessionBlock() : nil, scope))
public override func captureExceptionIncrementingSessionErrorCount(_ exception: NSException, with scope: Scope) -> SentryId {
let session = callSessionBlockWithIncrementSessionErrors ? sessionDelegate?.incrementSessionErrors() : nil
captureExceptionWithSessionInvocations.record((exception, session, scope))
return SentryId()
}


@_spi(Private) public var captureErrorEventWithSessionInvocations = Invocations<(event: Event, session: SentrySession?, scope: Scope)>()
@_spi(Private) public override func captureErrorEventIncrementingSessionErrorCount(_ event: Event, with scope: Scope) -> SentryId {
let session = callSessionBlockWithIncrementSessionErrors ? sessionDelegate?.incrementSessionErrors() : nil
captureErrorEventWithSessionInvocations.record((event, session, scope))
return SentryId()
}

public var captureFatalEventInvocations = Invocations<(event: Event, scope: Scope)>()
public override func captureFatalEvent(_ event: Event, with scope: Scope) -> SentryId {
captureFatalEventInvocations.record((event, scope))
Expand Down
7 changes: 7 additions & 0 deletions SentryTestUtils/Sources/TestHub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ public class TestHub: SentryHubInternal {
return event.eventId
}

@_spi(Private) public var capturedErrorEvents = Invocations<Event>()
public override func captureErrorEvent(event: Event) -> SentryId {
self.capturedErrorEvents.record((event))

return event.eventId
}

public var capturedTransactionsWithScope = Invocations<(transaction: [String: Any], scope: Scope)>()
public override func capture(_ transaction: Transaction, with scope: Scope) {
capturedTransactionsWithScope.record((transaction.serialize(), scope))
Expand Down
47 changes: 25 additions & 22 deletions Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -146,19 +146,11 @@
return [self sendEvent:event withScope:scope alwaysAttachStacktrace:YES];
}

- (SentryId *)captureException:(NSException *)exception
withScope:(SentryScope *)scope
incrementSessionErrors:(SentrySession * (^)(void))sessionBlock
- (SentryId *)captureExceptionIncrementingSessionErrorCount:(NSException *)exception
withScope:(SentryScope *)scope
{
SentryEvent *event = [self buildExceptionEvent:exception];
event = [self prepareEvent:event withScope:scope alwaysAttachStacktrace:YES];

if (event != nil) {
SentrySession *session = sessionBlock();
return [self sendEvent:event withSession:session withScope:scope];
}

return SentryId.empty;
return [self captureErrorEventIncrementingSessionErrorCount:event withScope:scope];
}

- (SentryEvent *)buildExceptionEvent:(NSException *)exception
Expand Down Expand Up @@ -192,19 +184,11 @@
return [self sendEvent:event withScope:scope alwaysAttachStacktrace:YES];
}

- (SentryId *)captureError:(NSError *)error
withScope:(SentryScope *)scope
incrementSessionErrors:(SentrySession * (^)(void))sessionBlock
- (SentryId *)captureErrorIncrementingSessionErrorCount:(NSError *)error
withScope:(SentryScope *)scope
{
SentryEvent *event = [self buildErrorEvent:error];
event = [self prepareEvent:event withScope:scope alwaysAttachStacktrace:YES];

if (event != nil) {
SentrySession *session = sessionBlock();
return [self sendEvent:event withSession:session withScope:scope];
}

return SentryId.empty;
return [self captureErrorEventIncrementingSessionErrorCount:event withScope:scope];
}

- (SentryEvent *)buildErrorEvent:(NSError *)error
Expand Down Expand Up @@ -353,6 +337,25 @@
additionalEnvelopeItems:additionalEnvelopeItems];
}

- (SentryId *)captureErrorEventIncrementingSessionErrorCount:(SentryEvent *)event
withScope:(SentryScope *)scope
{
SentryEvent *preparedEvent = [self prepareEvent:event
withScope:scope
alwaysAttachStacktrace:YES];

if (preparedEvent != nil) {
SentrySession *session = nil;
id<SentrySessionDelegate> delegate = self.sessionDelegate;
if (delegate != nil) {
session = [delegate incrementSessionErrors];
}
return [self sendEvent:preparedEvent withSession:session withScope:scope];

Check warning on line 353 in Sources/Sentry/SentryClient.m

View workflow job for this annotation

GitHub Actions / Lint

nil passed to a callee that requires a non-null 2nd parameter [nullability.NullPassedToNonnull]
}

return SentryId.empty;
}

- (SentryId *)sendEvent:(SentryEvent *)event
withScope:(SentryScope *)scope
alwaysAttachStacktrace:(BOOL)alwaysAttachStacktrace
Expand Down
89 changes: 71 additions & 18 deletions Sources/Sentry/SentryHub.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

NS_ASSUME_NONNULL_BEGIN

@interface SentryHubInternal ()
@interface SentryHubInternal () <SentrySessionDelegate>

@property (nullable, atomic, strong) SentryClientInternal *client;
@property (nullable, nonatomic, strong) SentryScope *scope;
Expand Down Expand Up @@ -70,6 +70,10 @@ - (instancetype)initWithClient:(nullable SentryClientInternal *)client
_installedIntegrationNames = [[NSMutableSet alloc] init];
_errorsBeforeSession = 0;

if (_client != nil) {
_client.sessionDelegate = self;
}

if (_scope) {
[_crashWrapper enrichScope:SENTRY_UNWRAP_NULLABLE(SentryScope, _scope)];
}
Expand Down Expand Up @@ -489,19 +493,38 @@ - (SentryId *)captureError:(NSError *)error

- (SentryId *)captureError:(NSError *)error withScope:(SentryScope *)scope
{
SentrySession *currentSession = _session;
SentryClientInternal *client = self.client;
if (client != nil) {
if (currentSession != nil) {
return [client captureError:error
withScope:scope
incrementSessionErrors:^(void) { return [self incrementSessionErrors]; }];
} else {
_errorsBeforeSession++;
return [client captureError:error withScope:scope];
}
}
return SentryId.empty;
SentryId * (^captureClientBlock)(SentryClientInternal *)
= ^SentryId *(SentryClientInternal *clientParam) {
return [clientParam captureErrorIncrementingSessionErrorCount:error withScope:scope];
};

SentryId * (^captureClientBlockSessionNil)(SentryClientInternal *)
= ^SentryId *(SentryClientInternal *clientParam) {
return [clientParam captureError:error withScope:scope];
};

return [self captureEventIncrementingSessionErrors:scope
captureClientBlock:captureClientBlock
captureClientSessionNilBlock:captureClientBlockSessionNil];
}

- (SentryId *)captureErrorEvent:(SentryEvent *)event
{
SentryScope *scope = self.scope;

SentryId * (^captureClientBlock)(SentryClientInternal *) = ^SentryId *(
SentryClientInternal *clientParam) {
return [clientParam captureErrorEventIncrementingSessionErrorCount:event withScope:scope];
};

SentryId * (^captureClientBlockSessionNil)(SentryClientInternal *)
= ^SentryId *(SentryClientInternal *clientParam) {
return [clientParam captureEvent:event withScope:scope];
};

return [self captureEventIncrementingSessionErrors:scope
captureClientBlock:captureClientBlock
captureClientSessionNilBlock:captureClientBlockSessionNil];
}

- (SentryId *)captureException:(NSException *)exception
Expand All @@ -511,16 +534,37 @@ - (SentryId *)captureException:(NSException *)exception

- (SentryId *)captureException:(NSException *)exception withScope:(SentryScope *)scope
{
SentryId * (^captureClientBlock)(SentryClientInternal *)
= ^SentryId *(SentryClientInternal *clientParam) {
return [clientParam captureExceptionIncrementingSessionErrorCount:exception
withScope:scope];
};

SentryId * (^captureClientBlockSessionNil)(SentryClientInternal *)
= ^SentryId *(SentryClientInternal *clientParam) {
return [clientParam captureException:exception withScope:scope];
};

return [self captureEventIncrementingSessionErrors:scope
captureClientBlock:captureClientBlock
captureClientSessionNilBlock:captureClientBlockSessionNil];
}

- (SentryId *)captureEventIncrementingSessionErrors:(SentryScope *)scope
captureClientBlock:(SentryId * (^)(
SentryClientInternal *))captureClientBlock
captureClientSessionNilBlock:
(SentryId * (^)(SentryClientInternal *))captureClientSessionNilBlock
{

SentrySession *currentSession = _session;
SentryClientInternal *client = self.client;
if (client != nil) {
if (currentSession != nil) {
return [client captureException:exception
withScope:scope
incrementSessionErrors:^(void) { return [self incrementSessionErrors]; }];
return captureClientBlock(client);
} else {
_errorsBeforeSession++;
return [client captureException:exception withScope:scope];
return captureClientSessionNilBlock(client);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Inconsistent Error Capture Without Active Session Block

The refactoring uses blocks for event capture, which goes against a prior agreement. More critically, in captureErrorEvent:, when no session is active, the captureClientBlockSessionNil block calls captureEvent:withScope:. This causes error events to miss stack traces, creating inconsistent behavior compared to other error capture methods and expected error event data.

Fix in Cursor Fix in Web

}
}
return SentryId.empty;
Expand Down Expand Up @@ -572,7 +616,16 @@ - (nullable SentryClientInternal *)getClient

- (void)bindClient:(nullable SentryClientInternal *)client
{
SentryClientInternal *currentClient = self.client;
if (currentClient != nil) {
currentClient.sessionDelegate = nil;
}

self.client = client;

if (client != nil) {
client.sessionDelegate = self;
}
}

- (SentryScope *)scope
Expand Down
2 changes: 1 addition & 1 deletion Sources/Sentry/SentryNetworkTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ - (void)captureFailedRequests:(NSURLSessionTask *)sessionTask

event.context = context;

[SentrySDK captureEvent:event];
[SentrySDKInternal.currentHub captureErrorEvent:event];
}

- (BOOL)containsStatusCode:(NSInteger)statusCode
Expand Down
20 changes: 14 additions & 6 deletions Sources/Sentry/include/SentryClient+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
@class SentrySession;
@class SentryDefaultThreadInspector;

@protocol SentrySessionDelegate <NSObject>

- (nullable SentrySession *)incrementSessionErrors;

@end

NS_ASSUME_NONNULL_BEGIN

@protocol SentryClientAttachmentProcessor <NSObject>
Expand All @@ -27,14 +33,13 @@ NS_ASSUME_NONNULL_BEGIN
NSMutableArray<id<SentryClientAttachmentProcessor>> *attachmentProcessors;
@property (nonatomic, strong) SentryDefaultThreadInspector *threadInspector;
@property (nonatomic, strong) SentryFileManager *fileManager;
@property (nonatomic, weak, nullable) id<SentrySessionDelegate> sessionDelegate;

- (SentryId *)captureError:(NSError *)error
withScope:(SentryScope *)scope
incrementSessionErrors:(SentrySession * (^)(void))sessionBlock;
- (SentryId *)captureErrorIncrementingSessionErrorCount:(NSError *)error
withScope:(SentryScope *)scope;

- (SentryId *)captureException:(NSException *)exception
withScope:(SentryScope *)scope
incrementSessionErrors:(SentrySession * (^)(void))sessionBlock;
- (SentryId *)captureExceptionIncrementingSessionErrorCount:(NSException *)exception
withScope:(SentryScope *)scope;

- (SentryId *)captureFatalEvent:(SentryEvent *)event withScope:(SentryScope *)scope;

Expand All @@ -56,6 +61,9 @@ NS_ASSUME_NONNULL_BEGIN
additionalEnvelopeItems:(NSArray<SentryEnvelopeItem *> *)additionalEnvelopeItems
NS_SWIFT_NAME(capture(event:scope:additionalEnvelopeItems:));

- (SentryId *)captureErrorEventIncrementingSessionErrorCount:(SentryEvent *)event
withScope:(SentryScope *)scope;

- (void)captureReplayEvent:(SentryReplayEvent *)replayEvent
replayRecording:(SentryReplayRecording *)replayRecording
video:(NSURL *)videoURL
Expand Down
2 changes: 2 additions & 0 deletions Sources/Sentry/include/SentryHub+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ NS_ASSUME_NONNULL_BEGIN
additionalEnvelopeItems:(NSArray<SentryEnvelopeItem *> *)additionalEnvelopeItems
NS_SWIFT_NAME(capture(event:scope:additionalEnvelopeItems:));

- (SentryId *)captureErrorEvent:(SentryEvent *)event NS_SWIFT_NAME(captureErrorEvent(event:));

- (void)captureSerializedFeedback:(NSDictionary *)serializedFeedback
withEventId:(NSString *)feedbackEventId
attachments:(NSArray<SentryAttachment *> *)feedbackAttachments;
Expand Down
Loading
Loading