Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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.

Expand Down
8 changes: 7 additions & 1 deletion SentryTestUtils/TestClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,13 @@ public class TestClient: SentryClientInternal {
captureExceptionWithSessionInvocations.record((exception, callSessionBlockWithIncrementSessionErrors ? sessionBlock() : nil, scope))
return SentryId()
}


@_spi(Private) public var captureErrorEventWithSessionInvocations = Invocations<(event: Event, session: SentrySession?, scope: Scope)>()
@_spi(Private) public override func captureErrorEvent(event: Event, scope: Scope, incrementSessionErrors sessionBlock: @escaping () -> SentrySession) -> SentryId {
captureErrorEventWithSessionInvocations.record((event, callSessionBlockWithIncrementSessionErrors ? sessionBlock() : nil, 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/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
34 changes: 18 additions & 16 deletions Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,7 @@ - (SentryId *)captureException:(NSException *)exception
incrementSessionErrors:(SentrySession * (^)(void))sessionBlock
{
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 captureErrorEvent:event withScope:scope incrementSessionErrors:sessionBlock];
}

- (SentryEvent *)buildExceptionEvent:(NSException *)exception
Expand Down Expand Up @@ -197,14 +190,7 @@ - (SentryId *)captureError:(NSError *)error
incrementSessionErrors:(SentrySession * (^)(void))sessionBlock
{
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 captureErrorEvent:event withScope:scope incrementSessionErrors:sessionBlock];
}

- (SentryEvent *)buildErrorEvent:(NSError *)error
Expand Down Expand Up @@ -353,6 +339,22 @@ - (SentryId *)captureEvent:(SentryEvent *)event
additionalEnvelopeItems:additionalEnvelopeItems];
}

- (SentryId *)captureErrorEvent:(SentryEvent *)event
withScope:(SentryScope *)scope
incrementSessionErrors:(SentrySession * (^)(void))sessionBlock
{
SentryEvent *preparedEvent = [self prepareEvent:event
withScope:scope
alwaysAttachStacktrace:YES];

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

return SentryId.empty;
}

- (SentryId *)sendEvent:(SentryEvent *)event
withScope:(SentryScope *)scope
alwaysAttachStacktrace:(BOOL)alwaysAttachStacktrace
Expand Down
79 changes: 62 additions & 17 deletions Sources/Sentry/SentryHub.m
Original file line number Diff line number Diff line change
Expand Up @@ -489,19 +489,42 @@ - (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 captureError:error
withScope:scope
incrementSessionErrors:^(void) { return [self incrementSessionErrors]; }];
};

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 captureErrorEvent:event
withScope:scope
incrementSessionErrors:^(void) { return [self incrementSessionErrors]; }];
};

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,38 @@ - (SentryId *)captureException:(NSException *)exception

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

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);
}
}
return SentryId.empty;
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
5 changes: 5 additions & 0 deletions Sources/Sentry/include/SentryClient+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ NS_ASSUME_NONNULL_BEGIN
additionalEnvelopeItems:(NSArray<SentryEnvelopeItem *> *)additionalEnvelopeItems
NS_SWIFT_NAME(capture(event:scope:additionalEnvelopeItems:));

- (SentryId *)captureErrorEvent:(SentryEvent *)event
withScope:(SentryScope *)scope
incrementSessionErrors:(SentrySession * (^)(void))sessionBlock
NS_SWIFT_NAME(captureErrorEvent(event:scope:incrementSessionErrors:));

- (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
Original file line number Diff line number Diff line change
Expand Up @@ -1013,11 +1013,9 @@ class SentryNetworkTrackerTests: XCTestCase {

sut.urlSessionTask(task, setState: .completed)

guard let envelope = self.fixture.hub.capturedEventsWithScopes.first else {
XCTFail("Expected to capture 1 event")
return
}
let sentryRequest = try XCTUnwrap(envelope.event.request)
XCTAssertEqual(self.fixture.hub.capturedErrorEvents.count, 1, "Expected only one error event to be captured")
let capturedErrorEvent = try XCTUnwrap(self.fixture.hub.capturedErrorEvents.first)
let sentryRequest = try XCTUnwrap(capturedErrorEvent.request)

XCTAssertEqual(sentryRequest.url, "https://www.domain.com/api")
XCTAssertEqual(sentryRequest.method, "GET")
Expand Down Expand Up @@ -1050,13 +1048,11 @@ class SentryNetworkTrackerTests: XCTestCase {

sut.urlSessionTask(task, setState: .completed)

let envelope = try XCTUnwrap(
fixture.hub.capturedEventsWithScopes.first,
"Expected to capture 1 event"
)

XCTAssertEqual(self.fixture.hub.capturedErrorEvents.count, 1, "Expected only one error event to be captured")
let capturedErrorEvent = try XCTUnwrap(fixture.hub.capturedErrorEvents.first)

let graphQLContext = try XCTUnwrap(
envelope.event.context?["graphql"],
capturedErrorEvent.context?["graphql"],
"Expected 'graphql' object in context"
)

Expand All @@ -1080,11 +1076,10 @@ class SentryNetworkTrackerTests: XCTestCase {
task.setResponse(try createResponse(code: 500))
sut.urlSessionTask(task, setState: .completed)

guard let envelope = self.fixture.hub.capturedEventsWithScopes.first else {
XCTFail("Expected to capture 1 event")
return
}
let sentryRequest = try XCTUnwrap(envelope.event.request)
XCTAssertEqual(self.fixture.hub.capturedErrorEvents.count, 1, "Expected only one error event to be captured")
let capturedErrorEvent = try XCTUnwrap(self.fixture.hub.capturedErrorEvents.first)

let sentryRequest = try XCTUnwrap(capturedErrorEvent.request)

XCTAssertEqual(sentryRequest.url, "https://[Filtered]:[Filtered]@www.domain.com/api")
XCTAssertEqual(sentryRequest.headers, ["VALID_HEADER": "value"])
Expand All @@ -1104,11 +1099,11 @@ class SentryNetworkTrackerTests: XCTestCase {

sut.urlSessionTask(task, setState: .completed)

guard let envelope = self.fixture.hub.capturedEventsWithScopes.first else {
XCTFail("Expected to capture 1 event")
return
}
let sentryResponse = try XCTUnwrap(envelope.event.context?["response"])
XCTAssertEqual(self.fixture.hub.capturedErrorEvents.count, 1, "Expected only one error event to be captured")

let capturedErrorEvent = try XCTUnwrap(self.fixture.hub.capturedErrorEvents.first)

let sentryResponse = try XCTUnwrap(capturedErrorEvent.context?["response"])

XCTAssertEqual(sentryResponse["status_code"] as? NSNumber, 500)
XCTAssertEqual(sentryResponse["headers"] as? [String: String], ["test": "test"])
Expand All @@ -1129,11 +1124,10 @@ class SentryNetworkTrackerTests: XCTestCase {
task.setResponse(response)
sut.urlSessionTask(task, setState: .completed)

guard let envelope = self.fixture.hub.capturedEventsWithScopes.first else {
XCTFail("Expected to capture 1 event")
return
}
let sentryResponse = try XCTUnwrap(envelope.event.context?["response"])
XCTAssertEqual(self.fixture.hub.capturedErrorEvents.count, 1, "Expected only one error event to be captured")
let capturedErrorEvent = try XCTUnwrap(self.fixture.hub.capturedErrorEvents.first)

let sentryResponse = try XCTUnwrap(capturedErrorEvent.context?["response"])

XCTAssertEqual(sentryResponse["headers"] as? [String: String], ["VALID_HEADER": "value"])
}
Expand All @@ -1145,9 +1139,10 @@ class SentryNetworkTrackerTests: XCTestCase {

sut.urlSessionTask(task, setState: .completed)

let envelope = try XCTUnwrap(self.fixture.hub.capturedEventsWithScopes.first)

let exceptions = try XCTUnwrap(envelope.event.exceptions)
XCTAssertEqual(self.fixture.hub.capturedErrorEvents.count, 1, "Expected only one error event to be captured")
let capturedErrorEvent = try XCTUnwrap(self.fixture.hub.capturedErrorEvents.first)

let exceptions = try XCTUnwrap(capturedErrorEvent.exceptions)
XCTAssertEqual(exceptions.count, 1)
let exception = try XCTUnwrap(exceptions.first)

Expand Down
43 changes: 42 additions & 1 deletion Tests/SentryTests/SentryHubTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,48 @@ class SentryHubTests: XCTestCase {
)
}
}


func testCaptureErrorEvent_WithSession() {
let sut = fixture.getSut()
sut.startSession()

let event = fixture.event
sut.captureErrorEvent(event: event).assertIsNotEmpty()

XCTAssertEqual(1, fixture.client.captureErrorEventWithSessionInvocations.count)
if let eventArguments = fixture.client.captureErrorEventWithSessionInvocations.first {
XCTAssertEqual(event, eventArguments.event)

XCTAssertEqual(1, eventArguments.session?.errors)
XCTAssertEqual(SentrySessionStatus.ok, eventArguments.session?.status)

XCTAssertEqual(sut.scope, eventArguments.scope)
}

// only session init is sent
XCTAssertEqual(1, fixture.client.captureSessionInvocations.count)
}

func testCaptureErrorEvent_WithoutIncreasingErrorCount() {
let sut = fixture.getSut()
sut.startSession()

fixture.client.callSessionBlockWithIncrementSessionErrors = false

let event = fixture.event
sut.captureErrorEvent(event: event).assertIsNotEmpty()

XCTAssertEqual(1, fixture.client.captureErrorEventWithSessionInvocations.count)
if let eventArguments = fixture.client.captureErrorEventWithSessionInvocations.first {
XCTAssertEqual(event, eventArguments.event)
XCTAssertNil(eventArguments.session)
XCTAssertEqual(sut.scope, eventArguments.scope)
}

// only session init is sent
XCTAssertEqual(1, fixture.client.captureSessionInvocations.count)
}

func testCaptureClientIsNil_ReturnsEmptySentryId() {
sut.bindClient(nil)

Expand Down
Loading