Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: track network connectivity changes with breadcrumbs #3232

Merged
merged 14 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Record changes to network connectivity in breadcrumbs (#3232)

## 8.10.0

### Features
Expand Down
17 changes: 17 additions & 0 deletions Sources/Sentry/SentryBreadcrumbTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
#import "SentryBreadcrumbDelegate.h"
#import "SentryClient.h"
#import "SentryDefines.h"
#import "SentryDependencyContainer.h"
#import "SentryHub.h"
#import "SentryLog.h"
#import "SentryReachability.h"
#import "SentrySDK+Private.h"
#import "SentryScope.h"
#import "SentrySwift.h"
Expand Down Expand Up @@ -45,6 +47,7 @@
_delegate = delegate;
[self addEnabledCrumb];
[self trackApplicationUIKitNotifications];
[self trackNetworkConnectivityChanges];
}

- (void)startSwizzle
Expand Down Expand Up @@ -120,6 +123,20 @@
#endif
}

- (void)trackNetworkConnectivityChanges
{
[SentryDependencyContainer.sharedInstance.reachability
monitorURL:[NSURL URLWithString:@"https://sentry.io"]
usingCallback:^(BOOL connected, NSString *_Nonnull typeDescription) {
SentryBreadcrumb *crumb =
[[SentryBreadcrumb alloc] initWithLevel:kSentryLevelInfo
category:@"device.connectivity"];
crumb.type = @"connectivity";
crumb.data = [NSDictionary dictionaryWithObject:typeDescription forKey:@"connectivity"];
[self.delegate addBreadcrumb:crumb];

Check warning on line 136 in Sources/Sentry/SentryBreadcrumbTracker.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryBreadcrumbTracker.m#L131-L136

Added lines #L131 - L136 were not covered by tests
}];
}

- (void)addBreadcrumbWithType:(NSString *)type
withCategory:(NSString *)category
withLevel:(SentryLevel)level
Expand Down
18 changes: 18 additions & 0 deletions Sources/Sentry/SentryDependencyContainer.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
# import "SentryFramesTracker.h"
#endif // SENTRY_HAS_UIKIT

#if !TARGET_OS_WATCH
# import "SentryReachability.h"
#endif // !TARGET_OS_WATCH

@implementation SentryDependencyContainer

static SentryDependencyContainer *instance;
Expand Down Expand Up @@ -311,4 +315,18 @@ - (SentryMXManager *)metricKitManager

#endif // SENTRY_HAS_METRIC_KIT

#if !TARGET_OS_WATCH
- (SentryReachability *)reachability
{
if (_reachability == nil) {
@synchronized(sentryDependencyContainerLock) {
if (_reachability == nil) {
_reachability = [[SentryReachability alloc] init];
}
}
}
return _reachability;
}
#endif // !TARGET_OS_WATCH

@end
6 changes: 3 additions & 3 deletions Sources/Sentry/SentryReachability.m
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
static SCNetworkReachabilityFlags sentry_current_reachability_state
= kSCNetworkReachabilityFlagsUninitialized;

static NSString *const SentryConnectivityCellular = @"cellular";
static NSString *const SentryConnectivityWiFi = @"wifi";
static NSString *const SentryConnectivityNone = @"none";
NSString *const SentryConnectivityCellular = @"cellular";
NSString *const SentryConnectivityWiFi = @"wifi";
NSString *const SentryConnectivityNone = @"none";

/**
* Check whether the connectivity change should be noted or ignored.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
@class SentryScreenshot;
@class SentryUIApplication;
@class SentryViewHierarchy;
#endif
#endif // SENTRY_HAS_UIKIT

#if !TARGET_OS_WATCH
@class SentryReachability;
#endif // !TARGET_OS_WATCH

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -62,6 +66,10 @@ SENTRY_NO_INIT
@property (nonatomic, strong) SentryUIApplication *application;
#endif

#if !TARGET_OS_WATCH
@property (nonatomic, strong) SentryReachability *reachability;
#endif // !TARGET_OS_WATCH

- (SentryANRTracker *)getANRTracker:(NSTimeInterval)timeout;

#if SENTRY_HAS_METRIC_KIT
Expand Down
5 changes: 5 additions & 0 deletions Sources/Sentry/include/SentryReachability.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
// THE SOFTWARE.
//

#import "SentryDefines.h"
#import <Foundation/Foundation.h>

#if !TARGET_OS_WATCH
Expand All @@ -38,6 +39,10 @@ NSString *SentryConnectivityFlagRepresentation(SCNetworkReachabilityFlags flags)

BOOL SentryConnectivityShouldReportChange(SCNetworkReachabilityFlags flags);

SENTRY_EXTERN NSString *const SentryConnectivityCellular;
SENTRY_EXTERN NSString *const SentryConnectivityWiFi;
SENTRY_EXTERN NSString *const SentryConnectivityNone;

/**
* Function signature to connectivity monitoring callback of @c SentryReachability
* @param connected @c YES if the monitored URL is reachable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,31 @@ class SentryBreadcrumbTrackerTests: XCTestCase {
XCTAssertEqual(0, dict?.count)
}

func testNetworkConnectivityChangeBreadcrumbs() throws {
let testReachability = TestSentryReachability()
SentryDependencyContainer.sharedInstance().reachability = testReachability
let sut = SentryBreadcrumbTracker(swizzleWrapper: SentrySwizzleWrapper.sharedInstance)
sut.start(with: delegate)
let states = [SentryConnectivityCellular,
SentryConnectivityWiFi,
SentryConnectivityNone,
SentryConnectivityWiFi,
SentryConnectivityCellular,
SentryConnectivityWiFi
]
states.forEach {
testReachability.setReachabilityState(state: $0)
}
sut.stop()
XCTAssertEqual(delegate.addCrumbInvocations.count, states.count + 1) // 1 breadcrumb for the tracker start
try states.enumerated().forEach {
let crumb = delegate.addCrumbInvocations.invocations[$0.offset + 1]
XCTAssertEqual(crumb.type, "connectivity")
XCTAssertEqual(crumb.category, "device.connectivity")
XCTAssertEqual(try XCTUnwrap(crumb.data?["connectivity"] as? String), $0.element)
}
}

func testSwizzlingStarted_ViewControllerAppears_AddsUILifeCycleBreadcrumb() {
let scope = Scope()
let client = TestClient(options: Options())
Expand Down
18 changes: 9 additions & 9 deletions Tests/SentryTests/Networking/SentryReachabilityTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@ - (void)tearDown

- (void)testConnectivityRepresentations
{
XCTAssertEqualObjects(@"none", SentryConnectivityFlagRepresentation(0));
XCTAssertEqualObjects(
@"none", SentryConnectivityFlagRepresentation(kSCNetworkReachabilityFlagsIsDirect));
XCTAssertEqualObjects(SentryConnectivityNone, SentryConnectivityFlagRepresentation(0));
XCTAssertEqualObjects(SentryConnectivityNone,
SentryConnectivityFlagRepresentation(kSCNetworkReachabilityFlagsIsDirect));
# if SENTRY_HAS_UIKIT
// kSCNetworkReachabilityFlagsIsWWAN does not exist on macOS
XCTAssertEqualObjects(
@"none", SentryConnectivityFlagRepresentation(kSCNetworkReachabilityFlagsIsWWAN));
XCTAssertEqualObjects(@"cellular",
XCTAssertEqualObjects(SentryConnectivityNone,
SentryConnectivityFlagRepresentation(kSCNetworkReachabilityFlagsIsWWAN));
XCTAssertEqualObjects(SentryConnectivityCellular,
SentryConnectivityFlagRepresentation(
kSCNetworkReachabilityFlagsIsWWAN | kSCNetworkReachabilityFlagsReachable));
# endif // SENTRY_HAS_UIKIT
XCTAssertEqualObjects(
@"wifi", SentryConnectivityFlagRepresentation(kSCNetworkReachabilityFlagsReachable));
XCTAssertEqualObjects(@"wifi",
XCTAssertEqualObjects(SentryConnectivityWiFi,
SentryConnectivityFlagRepresentation(kSCNetworkReachabilityFlagsReachable));
XCTAssertEqualObjects(SentryConnectivityWiFi,
SentryConnectivityFlagRepresentation(
kSCNetworkReachabilityFlagsReachable | kSCNetworkReachabilityFlagsIsDirect));
}
Expand Down
6 changes: 5 additions & 1 deletion Tests/SentryTests/Networking/TestSentryReachability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
self.block = block
}

func setReachabilityState(state: String) {
block?(state != SentryConnectivityNone, state)
}

Check warning on line 14 in Tests/SentryTests/Networking/TestSentryReachability.swift

View check run for this annotation

Codecov / codecov/patch

Tests/SentryTests/Networking/TestSentryReachability.swift#L13-L14

Added lines #L13 - L14 were not covered by tests

func triggerNetworkReachable() {
block?(true, "wifi")
block?(true, SentryConnectivityWiFi)
}

var stopMonitoringInvocations = Invocations<Void>()
Expand Down
Loading