Skip to content

Commit 70fc7c8

Browse files
authored
feat: auto-populate HTTP hints for network events (#8967)
* feat: add Hints API for beforeSend callbacks Add a Hint class that provides metadata about the origin of an event (original error/exception, attachments, key-value data) flowing alongside events through the capture pipeline. New beforeSendWithHint and beforeBreadcrumbWithHint callbacks take precedence over their existing counterparts. The WithHint variants are deprecated and will be removed in v10 when the hint parameter is added to beforeSend/beforeBreadcrumb directly. * docs: add changelog entry for Hints API * Update changelog * ref: address hints API review feedback Guard Hint state with SentryMutex instead of NSLock, and deprecate the WithHint callback setters via @available and DEPRECATED_MSG_ATTRIBUTE, since the hint parameter moves into beforeSend/beforeBreadcrumb in the next major version. Pre-populate hint.attachments with the scope attachments before beforeSendWithHint runs and treat the hint list as authoritative afterwards, so the callback can remove attachments as well as add them. Add concurrency tests for Hint and attachment add/remove coverage for the client. * docs: add changelog entry for capture hints * feat: add hint to public capture methods Thread user-provided SentryHint through SentrySDK -> SentryHub -> SentryClient capture methods, matching sentry-java's pattern for beforeSend callbacks. * feat: auto-populate HTTP hints for network events Attach URLRequest and HTTPURLResponse to hints for network breadcrumbs and HTTP client error events so beforeSendWithHint and beforeBreadcrumbWithHint callbacks can access the original HTTP context. * Fix changelog entry * Run generate api * feat: expose HTTP hint properties on SentryObjCHint Add urlRequest and httpResponse to SentryObjCHint so Objective-C consumers can access auto-populated HTTP metadata in beforeSend and beforeBreadcrumb callbacks.
1 parent 974e670 commit 70fc7c8

20 files changed

Lines changed: 610 additions & 5 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Add a `device.event` breadcrumb (`SYSTEM_CLOCK_CHANGE`) when the system clock changes, for example due to a manual time change or NTP sync (#8946)
88
- Add Hints API with `beforeSendWithHint` and `beforeBreadcrumbWithHint` callbacks (#8942)
99
- Add hint parameter to public capture methods on `SentrySDK` (#8955)
10+
- Auto-populate HTTP request and response on hints for network breadcrumbs and HTTP client errors (#8967)
1011

1112
### Fixes
1213

SentryTestUtils/Sources/TestHub.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,21 @@ public class TestHub: SentryTestHubWrapper {
5757
}
5858

5959
@_spi(Private) public var capturedErrorEvents = Invocations<Event>()
60+
@_spi(Private) public var capturedErrorHints = Invocations<Hint>()
6061
public override func captureErrorEvent(event: Event) -> SentryId {
6162
self.capturedErrorEvents.record((event))
6263

6364
return event.eventId
6465
}
6566

67+
public override func captureErrorEvent(_ event: Event, withHint hint: Any?) -> SentryId {
68+
self.capturedErrorEvents.record(event)
69+
if let hint = hint as? Hint {
70+
self.capturedErrorHints.record(hint)
71+
}
72+
return event.eventId
73+
}
74+
6675
public var capturedTransactionsWithScope = Invocations<(transaction: [String: Any], scope: Scope)>()
6776
public override func capture(_ transaction: Transaction, with scope: Scope) {
6877
capturedTransactionsWithScope.record((transaction.serialize(), scope))

Sources/Sentry/SentryHub.m

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,20 @@ - (SentryId *)captureErrorEvent:(SentryEvent *)event
670670
return SentryId.empty;
671671
}
672672

673+
- (SentryId *)captureErrorEvent:(SentryEvent *)event withHint:(id _Nullable)hint
674+
{
675+
SentryScope *scope = self.scope;
676+
SentryClientInternal *client = self.client;
677+
678+
if (client != nil) {
679+
SentryHint *resolvedHint = hint ?: [[SentryHint alloc] init];
680+
return [client captureEventIncrementingSessionErrorCount:event
681+
withScope:scope
682+
hint:resolvedHint];
683+
}
684+
return SentryId.empty;
685+
}
686+
673687
- (void)captureFeedback:(SentryFeedback *)feedback
674688
{
675689
SentryClientInternal *client = self.client;
@@ -692,15 +706,20 @@ - (void)captureSerializedFeedback:(NSDictionary *)serializedFeedback
692706
}
693707

694708
- (void)addBreadcrumb:(SentryBreadcrumb *)crumb
709+
{
710+
[self addBreadcrumb:crumb withHint:nil];
711+
}
712+
713+
- (void)addBreadcrumb:(SentryBreadcrumb *)crumb withHint:(id _Nullable)hint
695714
{
696715
SentryOptions *options = [[self client] options];
697716
if (options.maxBreadcrumbs < 1) {
698717
return;
699718
}
700719
SentryBreadcrumb *_Nullable nullableCrumb = crumb;
701720
if (options.beforeBreadcrumbWithHint != nil) {
702-
SentryHint *hint = [[SentryHint alloc] init];
703-
nullableCrumb = options.beforeBreadcrumbWithHint(crumb, hint);
721+
SentryHint *resolvedHint = hint ?: [[SentryHint alloc] init];
722+
nullableCrumb = options.beforeBreadcrumbWithHint(crumb, resolvedHint);
704723
} else {
705724
SentryBeforeBreadcrumbCallback callback = [options beforeBreadcrumb];
706725
if (callback != nil) {

Sources/Sentry/SentrySDKInternal.m

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,11 @@ + (SentryFeedbackAPI *)feedback
498498
#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT
499499

500500
+ (void)addBreadcrumb:(SentryBreadcrumb *)crumb
501+
{
502+
[self addBreadcrumb:crumb withHint:nil];
503+
}
504+
505+
+ (void)addBreadcrumb:(SentryBreadcrumb *)crumb withHint:(id _Nullable)hint
501506
{
502507
if (![SentrySDKInternal isEnabled]) {
503508
// We must log with level fatal because only fatal messages get logged even when the SDK
@@ -510,7 +515,7 @@ + (void)addBreadcrumb:(SentryBreadcrumb *)crumb
510515
@"the SDK before adding breadcrumbs.");
511516
}
512517

513-
[SentrySDKInternal.currentHub addBreadcrumb:crumb];
518+
[SentrySDKInternal.currentHub addBreadcrumb:crumb withHint:hint];
514519
}
515520

516521
+ (void)configureScope:(void (^)(SentryScope *scope))callback

Sources/Sentry/include/SentryClient+Private.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ NS_ASSUME_NONNULL_BEGIN
6161
- (SentryId *)captureEventIncrementingSessionErrorCount:(SentryEvent *)event
6262
withScope:(SentryScope *)scope;
6363

64+
- (SentryId *)captureEventIncrementingSessionErrorCount:(SentryEvent *)event
65+
withScope:(SentryScope *)scope
66+
hint:(SentryHint *)hint;
67+
6468
- (SentryId *)captureError:(NSError *)error
6569
withScope:(SentryScope *)scope
6670
attachAllThreads:(nullable NSNumber *)attachAllThreads

Sources/Sentry/include/SentryHub+Private.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ NS_ASSUME_NONNULL_BEGIN
7777

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

80+
- (SentryId *)captureErrorEvent:(SentryEvent *)event
81+
withHint:(SENTRY_SWIFT_MIGRATION_ID(SentryHint)_Nullable)hint;
82+
83+
- (void)addBreadcrumb:(SentryBreadcrumb *)crumb
84+
withHint:(SENTRY_SWIFT_MIGRATION_ID(SentryHint)_Nullable)hint;
85+
8086
- (SentryId *)captureError:(NSError *)error
8187
withScope:(SentryScope *)scope
8288
attachAllThreads:(nullable NSNumber *)attachAllThreads;

Sources/Sentry/include/SentrySDKInternal.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,9 @@ SENTRY_NO_INIT
310310
*/
311311
+ (void)addBreadcrumb:(SentryBreadcrumb *)crumb NS_SWIFT_NAME(addBreadcrumb(_:));
312312

313+
+ (void)addBreadcrumb:(SentryBreadcrumb *)crumb
314+
withHint:(SENTRY_SWIFT_MIGRATION_ID(SentryHint)_Nullable)hint;
315+
313316
/**
314317
* Use this method to modify the current Scope of the current Hub. The SDK uses the Scope to attach
315318
* contextual data to events.

Sources/SentryObjC/Public/SentryObjCHint.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ NS_ASSUME_NONNULL_BEGIN
2424
/// The original @c NSException that triggered the event capture, if any.
2525
@property (nonatomic, strong, nullable) NSException *originalException;
2626

27+
/// The @c NSURLRequest associated with the event or breadcrumb, if it originated from a network
28+
/// operation.
29+
@property (nonatomic, strong, nullable) NSURLRequest *urlRequest;
30+
31+
/// The @c NSHTTPURLResponse associated with the event or breadcrumb, if it originated from a
32+
/// network operation.
33+
@property (nonatomic, strong, nullable) NSHTTPURLResponse *httpResponse;
34+
2735
/**
2836
* The attachments that will be sent alongside the event.
2937
*

Sources/SentryObjCCompat/SentryObjCHint.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ import Foundation
3535
set { wrapped.originalException = newValue }
3636
}
3737

38+
@objc public var urlRequest: URLRequest? {
39+
get { wrapped.urlRequest }
40+
set { wrapped.urlRequest = newValue }
41+
}
42+
43+
@objc public var httpResponse: HTTPURLResponse? {
44+
get { wrapped.httpResponse }
45+
set { wrapped.httpResponse = newValue }
46+
}
47+
3848
@objc public var attachments: [SentryObjCAttachment] {
3949
get { wrapped.attachments.map { SentryObjCAttachment($0) } }
4050
set { wrapped.attachments = newValue.map(\.wrapped) }

Sources/Swift/Networking/SentryNetworkTracker.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,12 @@ final class SentryDefaultNetworkTracker<Dependencies: SentryDefaultNetworkTracke
490490
}
491491
event.context = context
492492

493-
_ = hub.captureErrorEvent(event: event)
493+
let hint = Hint()
494+
hint.urlRequest = currentRequest
495+
if let httpResponse = sessionTask.response as? HTTPURLResponse {
496+
hint.httpResponse = httpResponse
497+
}
498+
_ = hub.captureErrorEvent(event: event, hint: hint)
494499
}
495500

496501
private func containsStatusCode(_ statusCode: Int, options: Options) -> Bool {
@@ -573,7 +578,13 @@ final class SentryDefaultNetworkTracker<Dependencies: SentryDefaultNetworkTracke
573578

574579
let breadcrumb = Breadcrumb(level: level, category: "http", data: data)
575580
breadcrumb.type = "http"
576-
SentrySDKInternal.addBreadcrumb(breadcrumb)
581+
582+
let hint = Hint()
583+
hint.urlRequest = currentRequest
584+
if let httpResponse = sessionTask.response as? HTTPURLResponse {
585+
hint.httpResponse = httpResponse
586+
}
587+
SentrySDKInternal.add(breadcrumb, withHint: hint)
577588
}
578589

579590
// MARK: - Span status

0 commit comments

Comments
 (0)