Simplify how we setup Sentry to make sure it's configured before any spans get created - they don't get reported otherwise

This commit is contained in:
Stefan Ceriu 2024-07-17 14:47:51 +03:00 committed by Stefan Ceriu
parent 95d53f1edf
commit 65dd6f3496
4 changed files with 73 additions and 81 deletions

View File

@ -96,10 +96,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
navigationRootCoordinator = NavigationRootCoordinator() navigationRootCoordinator = NavigationRootCoordinator()
Self.setupServiceLocator(appSettings: appSettings, appHooks: appHooks)
ServiceLocator.shared.analytics.startIfEnabled()
stateMachine = AppCoordinatorStateMachine() stateMachine = AppCoordinatorStateMachine()
navigationRootCoordinator.setRootCoordinator(SplashScreenCoordinator()) navigationRootCoordinator.setRootCoordinator(SplashScreenCoordinator())
@ -116,6 +112,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
notificationManager = NotificationManager(notificationCenter: UNUserNotificationCenter.current(), notificationManager = NotificationManager(notificationCenter: UNUserNotificationCenter.current(),
appSettings: appSettings) appSettings: appSettings)
Self.setupSentry(appSettings: appSettings)
Self.setupServiceLocator(appSettings: appSettings, appHooks: appHooks)
ServiceLocator.shared.analytics.startIfEnabled()
windowManager.delegate = self windowManager.delegate = self
notificationManager.delegate = self notificationManager.delegate = self
@ -141,14 +143,10 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
registerBackgroundAppRefresh() registerBackgroundAppRefresh()
ServiceLocator.shared.analytics.isRunningPublisher appSettings.$analyticsConsentState
.removeDuplicates() .dropFirst() // Called above before configuring the ServiceLocator
.sink { [weak self] isRunning in .sink { _ in
if isRunning { Self.setupSentry(appSettings: appSettings)
self?.setupSentry()
} else {
self?.teardownSentry()
}
} }
.store(in: &cancellables) .store(in: &cancellables)
@ -743,55 +741,63 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
} }
} }
private func setupSentry() { private static func setupSentry(appSettings: AppSettings) {
SentrySDK.start { [weak self] options in let options: Options = .init()
#if DEBUG
options.enabled = false #if DEBUG
#endif options.enabled = false
#else
options.dsn = self?.appSettings.bugReportSentryURL.absoluteString options.enabled = appSettings.analyticsConsentState == .optedIn
#endif
// Sentry swizzling shows up quite often as the heaviest stack trace when profiling
// We don't need any of the features it powers (see docs) options.dsn = appSettings.bugReportSentryURL.absoluteString
options.enableSwizzling = false
if AppSettings.isDevelopmentBuild {
// WatchdogTermination is currently the top issue but we've had zero complaints options.environment = "development"
// so it might very well just all be false positives
options.enableWatchdogTerminationTracking = false
// Disabled as it seems to report a lot of false positives
options.enableAppHangTracking = false
// Most of the network requests are made Rust side, this is useless
options.enableNetworkBreadcrumbs = false
// Doesn't seem to work at all well with SwiftUI
options.enableAutoBreadcrumbTracking = false
// Experimental. Stitches stack traces of asynchronous code together
options.swiftAsyncStacktraces = true
// Uniform sample rate: 1.0 captures 100% of transactions
// In Production you will probably want a smaller number such as 0.5 for 50%
if AppSettings.isDevelopmentBuild {
options.sampleRate = 1.0
options.tracesSampleRate = 1.0
options.profilesSampleRate = 1.0
} else {
options.sampleRate = 0.5
options.tracesSampleRate = 0.5
options.profilesSampleRate = 0.5
}
// This callback is only executed once during the entire run of the program to avoid
// multiple callbacks if there are multiple crash events to send (see method documentation)
options.onCrashedLastRun = { event in
MXLog.error("Sentry detected a crash in the previous run: \(event.eventId.sentryIdString)")
ServiceLocator.shared.bugReportService.lastCrashEventID = event.eventId.sentryIdString
}
MXLog.info("SentrySDK started")
} }
// Sentry swizzling shows up quite often as the heaviest stack trace when profiling
// We don't need any of the features it powers (see docs)
options.enableSwizzling = false
// WatchdogTermination is currently the top issue but we've had zero complaints
// so it might very well just all be false positives
options.enableWatchdogTerminationTracking = false
// Disabled as it seems to report a lot of false positives
options.enableAppHangTracking = false
// Most of the network requests are made Rust side, this is useless
options.enableNetworkBreadcrumbs = false
// Doesn't seem to work at all well with SwiftUI
options.enableAutoBreadcrumbTracking = false
// Experimental. Stitches stack traces of asynchronous code together
options.swiftAsyncStacktraces = true
// Uniform sample rate: 1.0 captures 100% of transactions
// In Production you will probably want a smaller number such as 0.5 for 50%
if AppSettings.isDevelopmentBuild {
options.sampleRate = 1.0
options.tracesSampleRate = 1.0
options.profilesSampleRate = 1.0
} else {
options.sampleRate = 0.5
options.tracesSampleRate = 0.5
options.profilesSampleRate = 0.5
}
// This callback is only executed once during the entire run of the program to avoid
// multiple callbacks if there are multiple crash events to send (see method documentation)
options.onCrashedLastRun = { event in
MXLog.error("Sentry detected a crash in the previous run: \(event.eventId.sentryIdString)")
ServiceLocator.shared.bugReportService.lastCrashEventID = event.eventId.sentryIdString
}
SentrySDK.start(options: options)
MXLog.info("SentrySDK started")
} }
private func teardownSentry() { private func teardownSentry() {

View File

@ -38,19 +38,11 @@ class AnalyticsService {
/// A signpost client for performance testing the app. This client doesn't respect the /// A signpost client for performance testing the app. This client doesn't respect the
/// `isRunning` state or behave any differently when `start`/`reset` are called. /// `isRunning` state or behave any differently when `start`/`reset` are called.
let signpost = Signposter(isDevelopmentBuild: AppSettings.isDevelopmentBuild) let signpost = Signposter()
/// Whether or not the object is enabled and sending events to the server.
private let isRunningSubject: CurrentValueSubject<Bool, Never> = .init(false)
var isRunningPublisher: CurrentValuePublisher<Bool, Never> {
isRunningSubject.asCurrentValuePublisher()
}
init(client: AnalyticsClientProtocol, appSettings: AppSettings) { init(client: AnalyticsClientProtocol, appSettings: AppSettings) {
self.client = client self.client = client
self.appSettings = appSettings self.appSettings = appSettings
isRunningSubject.send(client.isRunning)
} }
/// Whether to show the user the analytics opt in prompt. /// Whether to show the user the analytics opt in prompt.
@ -76,21 +68,19 @@ class AnalyticsService {
// The order is important here. PostHog ignores the reset if stopped. // The order is important here. PostHog ignores the reset if stopped.
reset() reset()
client.stop() client.stop()
isRunningSubject.send(false)
MXLog.info("Stopped.") MXLog.info("Stopped.")
} }
/// Starts the analytics client if the user has opted in, otherwise does nothing. /// Starts the analytics client if the user has opted in, otherwise does nothing.
func startIfEnabled() { func startIfEnabled() {
guard isEnabled, !isRunningPublisher.value else { return } guard isEnabled, !client.isRunning else { return }
client.start(analyticsConfiguration: appSettings.analyticsConfiguration) client.start(analyticsConfiguration: appSettings.analyticsConfiguration)
// Sanity check in case something went wrong. // Sanity check in case something went wrong.
guard client.isRunning else { return } guard client.isRunning else { return }
isRunningSubject.send(true)
MXLog.info("Started.") MXLog.info("Started.")
} }

View File

@ -35,7 +35,6 @@ class Signposter {
static let appStarted = "AppStarted" static let appStarted = "AppStarted"
static let homeserver = "homeserver" static let homeserver = "homeserver"
static let isDevelopmentBuild = "isDevelopmentBuild"
} }
static let subsystem = "ElementX" static let subsystem = "ElementX"
@ -43,9 +42,8 @@ class Signposter {
private var appStartupSpan: Span private var appStartupSpan: Span
init(isDevelopmentBuild: Bool) { init() {
appStartupSpan = SentrySDK.startTransaction(name: Name.appStartup, operation: Name.appStarted) appStartupSpan = SentrySDK.startTransaction(name: Name.appStartup, operation: Name.appStarted)
appStartupSpan.setData(value: isDevelopmentBuild, key: Name.isDevelopmentBuild)
} }
// MARK: - Login // MARK: - Login
@ -55,7 +53,7 @@ class Signposter {
func beginLogin() { func beginLogin() {
loginState = signposter.beginInterval(Name.login) loginState = signposter.beginInterval(Name.login)
loginSpan = appStartupSpan.startChild(operation: "\(Name.login)") loginSpan = appStartupSpan.startChild(operation: "\(Name.login)", description: "\(Name.login)")
} }
func endLogin() { func endLogin() {
@ -80,7 +78,7 @@ class Signposter {
appStartupSpan.setTag(value: serverName, key: Name.homeserver) appStartupSpan.setTag(value: serverName, key: Name.homeserver)
firstSyncState = signposter.beginInterval(Name.firstSync) firstSyncState = signposter.beginInterval(Name.firstSync)
firstSyncSpan = appStartupSpan.startChild(operation: "\(Name.firstSync)") firstSyncSpan = appStartupSpan.startChild(operation: "\(Name.firstSync)", description: "\(Name.firstSync)")
} }
func endFirstSync() { func endFirstSync() {
@ -100,7 +98,7 @@ class Signposter {
func beginFirstRooms() { func beginFirstRooms() {
firstRoomsState = signposter.beginInterval(Name.firstRooms) firstRoomsState = signposter.beginInterval(Name.firstRooms)
firstRoomsSpan = appStartupSpan.startChild(operation: "\(Name.firstRooms)") firstRoomsSpan = appStartupSpan.startChild(operation: "\(Name.firstRooms)", description: "\(Name.firstRooms)")
} }
func endFirstRooms() { func endFirstRooms() {

View File

@ -76,7 +76,6 @@ class AnalyticsTests: XCTestCase {
// Given a fresh install of the app Analytics should be disabled // Given a fresh install of the app Analytics should be disabled
XCTAssertEqual(appSettings.analyticsConsentState, .unknown) XCTAssertEqual(appSettings.analyticsConsentState, .unknown)
XCTAssertFalse(ServiceLocator.shared.analytics.isEnabled) XCTAssertFalse(ServiceLocator.shared.analytics.isEnabled)
XCTAssertFalse(ServiceLocator.shared.analytics.isRunningPublisher.value)
XCTAssertFalse(analyticsClient.startAnalyticsConfigurationCalled) XCTAssertFalse(analyticsClient.startAnalyticsConfigurationCalled)
} }
@ -87,7 +86,6 @@ class AnalyticsTests: XCTestCase {
// Then analytics should be disabled // Then analytics should be disabled
XCTAssertEqual(appSettings.analyticsConsentState, .optedOut) XCTAssertEqual(appSettings.analyticsConsentState, .optedOut)
XCTAssertFalse(ServiceLocator.shared.analytics.isEnabled) XCTAssertFalse(ServiceLocator.shared.analytics.isEnabled)
XCTAssertFalse(ServiceLocator.shared.analytics.isRunningPublisher.value)
XCTAssertFalse(analyticsClient.isRunning) XCTAssertFalse(analyticsClient.isRunning)
// Analytics client should have been stopped // Analytics client should have been stopped
XCTAssertTrue(analyticsClient.stopCalled) XCTAssertTrue(analyticsClient.stopCalled)