From fa15335b9bbb397ec5aad78f9b2206e17b00ce85 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Fri, 28 Jun 2024 18:15:57 +0300 Subject: [PATCH] Setup Sentry instrumentation on top of the existing Signposter (#2985) * Move Sentry setup outside of the BugReportService * Setup Sentry SwiftUI tracing for the homeScreen and roomScreen roots * Setup Sentry instrumentation on top of the existing Signposter * Various tweaks --- ElementX.xcodeproj/project.pbxproj | 34 +++++--- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Sources/Application/AppCoordinator.swift | 85 ++++++++++++++++--- .../Mocks/Generated/GeneratedMocks.swift | 76 +---------------- .../Screens/HomeScreen/View/HomeScreen.swift | 1 + .../HomeScreen/View/HomeScreenContent.swift | 2 + .../Screens/RoomScreen/View/RoomScreen.swift | 1 + .../Services/Analytics/AnalyticsService.swift | 2 +- .../Services/Analytics/Signposter.swift | 39 ++++++++- .../Services/BugReport/BugReportService.swift | 66 ++------------ .../BugReport/BugReportServiceProtocol.swift | 9 +- ElementX/SupportingFiles/target.yml | 3 + UnitTests/Sources/BugReportServiceTests.swift | 2 - project.yml | 2 +- 14 files changed, 149 insertions(+), 177 deletions(-) diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 06f67946e..b15380b94 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -226,14 +226,14 @@ 368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; }; 369BF960E52BBEE61F8A5BD1 /* BlockedUsersScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */; }; 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; }; - 36AD4DD4C798E22584ED3200 /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = A05AF81DDD14AD58CB0E1B9B /* Version */; }; - 36CD6E11B37396E14F032CB6 /* Emojibase in Frameworks */ = {isa = PBXBuildFile; productRef = C05729B1684C331F5FFE9232 /* Emojibase */; }; + 36AD4DD4C798E22584ED3200 /* SentrySwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 75361A9D8A3C5501EADB225D /* SentrySwiftUI */; }; + 36CD6E11B37396E14F032CB6 /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = A05AF81DDD14AD58CB0E1B9B /* Version */; }; 36DE961B784087D5E18EF9BA /* LogViewerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A07692536D66E3DA32C4964 /* LogViewerScreen.swift */; }; 370AF5BFCD4384DD455479B6 /* ElementCallWidgetDriverProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */; }; 377980ABF16525114E72DDE2 /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = 2B9ACE4FCACB5A8812154424 /* Version */; }; 37906355E207DB5703754675 /* AppLockSetupBiometricsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F893F4A111CB7BA5C96949 /* AppLockSetupBiometricsScreenViewModel.swift */; }; 37D789F24199B32E3FD1AA7B /* FileRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 216F0DDC98F2A2C162D09C28 /* FileRoomTimelineItemContent.swift */; }; - 37E47F5101C0C036289D3807 /* DSWaveformImageViews in Frameworks */ = {isa = PBXBuildFile; productRef = 2A4106A0A96DC4C273128AA5 /* DSWaveformImageViews */; }; + 37E47F5101C0C036289D3807 /* SwiftOGG in Frameworks */ = {isa = PBXBuildFile; productRef = 391D11F92DFC91666AA1503F /* SwiftOGG */; }; 384D6B9A7DFD7260139D6852 /* UITestsNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBEB8D9F4940E161B18FE4BC /* UITestsNotificationCenter.swift */; }; 38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */; }; 386720B603F87D156DB01FB2 /* VoiceMessageMediaManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40076C770A5FB83325252973 /* VoiceMessageMediaManager.swift */; }; @@ -285,10 +285,11 @@ 44121202B4A260C98BF615A7 /* RoomMembersListScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */; }; 44BDD670FF9095ACE240A3A2 /* VoiceMessageMediaManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC4F10BDD56FA77FEC742333 /* VoiceMessageMediaManagerTests.swift */; }; 44DA28B1E1F9C97C5795F7B3 /* AppLockSetupUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8A1BBEF7318CA6B6ACCF4AE /* AppLockSetupUITests.swift */; }; - 44F0E1B576C7599DF8022071 /* Prefire in Frameworks */ = {isa = PBXBuildFile; productRef = 2629CF48B33643CD5F69C612 /* Prefire */; }; + 44F0E1B576C7599DF8022071 /* WysiwygComposer in Frameworks */ = {isa = PBXBuildFile; productRef = CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */; }; 454311EAC17D778E19F46592 /* NotificationPermissionsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91868EB98818044E6FEBE532 /* NotificationPermissionsScreenCoordinator.swift */; }; 454F8DDC4442C0DE54094902 /* LABiometryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F219838588C62198E726E3 /* LABiometryType.swift */; }; 4557192F5B15A8D9BB920232 /* AdvancedSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E492690C8B27A892C194CC4 /* AdvancedSettingsScreenCoordinator.swift */; }; + 4610C57A4785FFF5E67F0C6D /* DSWaveformImageViews in Frameworks */ = {isa = PBXBuildFile; productRef = 2A4106A0A96DC4C273128AA5 /* DSWaveformImageViews */; }; 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; }; 4681820102DAC8BA586357D4 /* VoiceMessageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D7926A5684E18196B538 /* VoiceMessageCache.swift */; }; 46A183C6125A669AEB005699 /* UserProfileScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F134D2D91DFF732FB75B2CB7 /* UserProfileScreenViewModelProtocol.swift */; }; @@ -679,7 +680,7 @@ A009BDFB0A6816D4C392ADCB /* SettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */; }; A021827B528F1EDC9101CA58 /* AppCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */; }; A0A0D2A9564BDA3FDE2E360F /* FormattedBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */; }; - A0D7E5BD0298A97DCBDCE40B /* WysiwygComposer in Frameworks */ = {isa = PBXBuildFile; productRef = CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */; }; + A0D7E5BD0298A97DCBDCE40B /* Emojibase in Frameworks */ = {isa = PBXBuildFile; productRef = C05729B1684C331F5FFE9232 /* Emojibase */; }; A10D6CCDE2010C09EEA1A593 /* HomeScreenRoomList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */; }; A14A9419105A1CD42F0511C4 /* UserIndicatorModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43005941B3A2C9671E23C85 /* UserIndicatorModalView.swift */; }; A17FAD2EBC53E17B5FD384DB /* InviteUsersScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */; }; @@ -719,7 +720,7 @@ A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; }; A896998A6784DB6F16E912F4 /* MockMediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */; }; A8FA7671948E3DF27F320026 /* BugReportFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */; }; - A93661C962B12942C08864B6 /* SwiftOGG in Frameworks */ = {isa = PBXBuildFile; productRef = 391D11F92DFC91666AA1503F /* SwiftOGG */; }; + A93661C962B12942C08864B6 /* Prefire in Frameworks */ = {isa = PBXBuildFile; productRef = 2629CF48B33643CD5F69C612 /* Prefire */; }; A9482B967FC85DA611514D35 /* VoiceMessageRoomPlaybackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCD41CD67DB5DA0D436BFE9 /* VoiceMessageRoomPlaybackView.swift */; }; A969147E0EEE0E27EE226570 /* MediaUploadPreviewScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */; }; A975D60EA49F6AF73308809F /* RoomMembersListScreenMemberCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC03209FDE8CE0810617BFFF /* RoomMembersListScreenMemberCell.swift */; }; @@ -2260,12 +2261,13 @@ EAC6FE2CD4F50A43068ADCD8 /* SwiftState in Frameworks */, 754602A7B2AAD443C4228ED4 /* GZIP in Frameworks */, B0CB16349B96262AA65A04AF /* Sentry in Frameworks */, - 36AD4DD4C798E22584ED3200 /* Version in Frameworks */, - 36CD6E11B37396E14F032CB6 /* Emojibase in Frameworks */, - A0D7E5BD0298A97DCBDCE40B /* WysiwygComposer in Frameworks */, - 44F0E1B576C7599DF8022071 /* Prefire in Frameworks */, - A93661C962B12942C08864B6 /* SwiftOGG in Frameworks */, - 37E47F5101C0C036289D3807 /* DSWaveformImageViews in Frameworks */, + 36AD4DD4C798E22584ED3200 /* SentrySwiftUI in Frameworks */, + 36CD6E11B37396E14F032CB6 /* Version in Frameworks */, + A0D7E5BD0298A97DCBDCE40B /* Emojibase in Frameworks */, + 44F0E1B576C7599DF8022071 /* WysiwygComposer in Frameworks */, + A93661C962B12942C08864B6 /* Prefire in Frameworks */, + 37E47F5101C0C036289D3807 /* SwiftOGG in Frameworks */, + 4610C57A4785FFF5E67F0C6D /* DSWaveformImageViews in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5333,6 +5335,7 @@ 9573B94B1C86C6DF751AF3FD /* SwiftState */, 997C7385E1A07E061D7E2100 /* GZIP */, 7731767AE437BA3BD2CC14A8 /* Sentry */, + 75361A9D8A3C5501EADB225D /* SentrySwiftUI */, A05AF81DDD14AD58CB0E1B9B /* Version */, C05729B1684C331F5FFE9232 /* Emojibase */, CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */, @@ -7450,7 +7453,7 @@ repositoryURL = "https://github.com/getsentry/sentry-cocoa"; requirement = { kind = upToNextMinorVersion; - minimumVersion = 8.13.0; + minimumVersion = 8.30.0; }; }; AC3475112CA40C2C6E78D1EB /* XCRemoteSwiftPackageReference "matrix-analytics-events" */ = { @@ -7684,6 +7687,11 @@ package = A08925A9D5E3770DEB9D8509 /* XCRemoteSwiftPackageReference "sentry-cocoa" */; productName = Sentry; }; + 75361A9D8A3C5501EADB225D /* SentrySwiftUI */ = { + isa = XCSwiftPackageProductDependency; + package = A08925A9D5E3770DEB9D8509 /* XCRemoteSwiftPackageReference "sentry-cocoa" */; + productName = SentrySwiftUI; + }; 7731767AE437BA3BD2CC14A8 /* Sentry */ = { isa = XCSwiftPackageProductDependency; package = A08925A9D5E3770DEB9D8509 /* XCRemoteSwiftPackageReference "sentry-cocoa" */; diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 52fe73d6c..abf5fd354 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -193,8 +193,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/getsentry/sentry-cocoa", "state" : { - "revision" : "0a915b93ff3abee1a9752448e47808334d306980", - "version" : "8.13.0" + "revision" : "8fd4e804f2e72e0b9c1b189ce4e8349c4d10b6a2", + "version" : "8.30.0" } }, { diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 1cb8d331c..362fa1477 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -19,6 +19,7 @@ import BackgroundTasks import Combine import Intents import MatrixRustSDK +import Sentry import SwiftUI import Version @@ -96,17 +97,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg Self.setupServiceLocator(appSettings: appSettings) - ServiceLocator.shared.analytics.isRunningPublisher - .removeDuplicates() - .sink { isRunning in - if isRunning { - ServiceLocator.shared.bugReportService.start() - } else { - ServiceLocator.shared.bugReportService.stop() - } - } - .store(in: &cancellables) - ServiceLocator.shared.analytics.startIfEnabled() stateMachine = AppCoordinatorStateMachine() @@ -150,6 +140,17 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg registerBackgroundAppRefresh() + ServiceLocator.shared.analytics.isRunningPublisher + .removeDuplicates() + .sink { [weak self] isRunning in + if isRunning { + self?.setupSentry() + } else { + self?.teardownSentry() + } + } + .store(in: &cancellables) + elementCallService.actions .receive(on: DispatchQueue.main) .sink { [weak self] action in @@ -344,7 +345,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg ServiceLocator.shared.register(appSettings: appSettings) ServiceLocator.shared.register(networkMonitor: NetworkMonitor()) ServiceLocator.shared.register(bugReportService: BugReportService(withBaseURL: appSettings.bugReportServiceBaseURL, - sentryURL: appSettings.bugReportSentryURL, applicationId: appSettings.bugReportApplicationId, sdkGitSHA: sdkGitSha(), maxUploadSize: appSettings.bugReportMaxUploadSize)) @@ -733,6 +733,62 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg } } + private func setupSentry() { + SentrySDK.start { [weak self] options in + #if DEBUG + options.enabled = false + #endif + + options.dsn = self?.appSettings.bugReportSentryURL.absoluteString + + // 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 + } + + MXLog.info("SentrySDK started") + } + } + + private func teardownSentry() { + SentrySDK.close() + MXLog.info("SentrySDK stopped") + } + // MARK: Toasts and loading indicators private static let loadingIndicatorIdentifier = "\(AppCoordinator.self)-Loading" @@ -762,7 +818,10 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg private func startSync() { guard let userSession else { return } - ServiceLocator.shared.analytics.signpost.beginFirstSync() + // FIXME: replace this with `user_id_server_name` from https://github.com/matrix-org/matrix-rust-sdk/pull/3617 + let serverName = String(userSession.clientProxy.userID.split(separator: ":").last ?? "Unknown") + + ServiceLocator.shared.analytics.signpost.beginFirstSync(serverName: serverName) userSession.clientProxy.startSync() guard clientProxyObserver == nil else { diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index b9be784f0..8484ca246 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -1815,87 +1815,13 @@ class AudioSessionMock: AudioSessionProtocol { } } class BugReportServiceMock: BugReportServiceProtocol { - var isRunning: Bool { - get { return underlyingIsRunning } - set(value) { underlyingIsRunning = value } - } - var underlyingIsRunning: Bool! var crashedLastRun: Bool { get { return underlyingCrashedLastRun } set(value) { underlyingCrashedLastRun = value } } var underlyingCrashedLastRun: Bool! + var lastCrashEventID: String? - //MARK: - start - - var startUnderlyingCallsCount = 0 - var startCallsCount: Int { - get { - if Thread.isMainThread { - return startUnderlyingCallsCount - } else { - var returnValue: Int? = nil - DispatchQueue.main.sync { - returnValue = startUnderlyingCallsCount - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - startUnderlyingCallsCount = newValue - } else { - DispatchQueue.main.sync { - startUnderlyingCallsCount = newValue - } - } - } - } - var startCalled: Bool { - return startCallsCount > 0 - } - var startClosure: (() -> Void)? - - func start() { - startCallsCount += 1 - startClosure?() - } - //MARK: - stop - - var stopUnderlyingCallsCount = 0 - var stopCallsCount: Int { - get { - if Thread.isMainThread { - return stopUnderlyingCallsCount - } else { - var returnValue: Int? = nil - DispatchQueue.main.sync { - returnValue = stopUnderlyingCallsCount - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - stopUnderlyingCallsCount = newValue - } else { - DispatchQueue.main.sync { - stopUnderlyingCallsCount = newValue - } - } - } - } - var stopCalled: Bool { - return stopCallsCount > 0 - } - var stopClosure: (() -> Void)? - - func stop() { - stopCallsCount += 1 - stopClosure?() - } //MARK: - submitBugReport var submitBugReportProgressListenerUnderlyingCallsCount = 0 diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index 4cd3a488a..f556269ff 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -75,6 +75,7 @@ struct HomeScreen: View { gradientView.alpha = 1 } } + .sentryTrace("\(Self.self)") } // MARK: - Private diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift index a5cda6361..497e34d5e 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenContent.swift @@ -15,6 +15,7 @@ // import Compound +import SentrySwiftUI import SwiftUI struct HomeScreenContent: View { @@ -29,6 +30,7 @@ struct HomeScreenContent: View { migrationView default: roomList + .sentryTrace("\(Self.self)") } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index e398ed89b..e41cdf5fe 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -81,6 +81,7 @@ struct RoomScreen: View { context.send(viewAction: .handlePasteOrDrop(provider: provider)) return true } + .sentryTrace("\(Self.self)") } private var timeline: some View { diff --git a/ElementX/Sources/Services/Analytics/AnalyticsService.swift b/ElementX/Sources/Services/Analytics/AnalyticsService.swift index 2caf474da..59cabedce 100644 --- a/ElementX/Sources/Services/Analytics/AnalyticsService.swift +++ b/ElementX/Sources/Services/Analytics/AnalyticsService.swift @@ -38,7 +38,7 @@ class AnalyticsService { /// 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. - let signpost = Signposter() + let signpost = Signposter(isDevelopmentBuild: AppSettings.isDevelopmentBuild) /// Whether or not the object is enabled and sending events to the server. private let isRunningSubject: CurrentValueSubject = .init(false) diff --git a/ElementX/Sources/Services/Analytics/Signposter.swift b/ElementX/Sources/Services/Analytics/Signposter.swift index c429975e5..8bfa18339 100644 --- a/ElementX/Sources/Services/Analytics/Signposter.swift +++ b/ElementX/Sources/Services/Analytics/Signposter.swift @@ -15,6 +15,7 @@ // import OSLog +import Sentry /// A simple wrapper around OSSignposter for easy performance testing. class Signposter { @@ -24,22 +25,37 @@ class Signposter { private let logger = Logger(subsystem: subsystem, category: category) /// Signpost name constants. - enum Name { + private enum Name { static let login: StaticString = "Login" static let firstSync: StaticString = "FirstSync" static let firstRooms: StaticString = "FirstRooms" static let roomFlow: StaticString = "RoomFlow" + + static let appStartup = "AppStartup" + static let appStarted = "AppStarted" + + static let homeserver = "homeserver" + static let isDevelopmentBuild = "isDevelopmentBuild" } static let subsystem = "ElementX" static let category = "PerformanceTests" + private var appStartupSpan: Span + + init(isDevelopmentBuild: Bool) { + appStartupSpan = SentrySDK.startTransaction(name: Name.appStartup, operation: Name.appStarted) + appStartupSpan.setData(value: isDevelopmentBuild, key: Name.isDevelopmentBuild) + } + // MARK: - Login private var loginState: OSSignpostIntervalState? + private var loginSpan: Span? func beginLogin() { loginState = signposter.beginInterval(Name.login) + loginSpan = appStartupSpan.startChild(operation: "\(Name.login)") } func endLogin() { @@ -49,37 +65,56 @@ class Signposter { } signposter.endInterval(Name.login, loginState) + loginSpan?.finish() + self.loginState = nil + loginSpan = nil } // MARK: - FirstSync private var firstSyncState: OSSignpostIntervalState? + private var firstSyncSpan: Span? - func beginFirstSync() { + func beginFirstSync(serverName: String) { + appStartupSpan.setTag(value: serverName, key: Name.homeserver) + firstSyncState = signposter.beginInterval(Name.firstSync) + firstSyncSpan = appStartupSpan.startChild(operation: "\(Name.firstSync)") } func endFirstSync() { guard let firstSyncState else { return } signposter.endInterval(Name.firstSync, firstSyncState) + firstSyncSpan?.finish() + self.firstSyncState = nil + firstSyncSpan = nil } // MARK: - FirstRooms private var firstRoomsState: OSSignpostIntervalState? + private var firstRoomsSpan: Span? func beginFirstRooms() { firstRoomsState = signposter.beginInterval(Name.firstRooms) + firstRoomsSpan = appStartupSpan.startChild(operation: "\(Name.firstRooms)") } func endFirstRooms() { + defer { + appStartupSpan.finish() + } + guard let firstRoomsState else { return } signposter.endInterval(Name.firstRooms, firstRoomsState) + firstRoomsSpan?.finish() + self.firstRoomsState = nil + firstRoomsSpan = nil } // MARK: - Room Flow diff --git a/ElementX/Sources/Services/BugReport/BugReportService.swift b/ElementX/Sources/Services/BugReport/BugReportService.swift index 252b08458..c8d0b0e7f 100644 --- a/ElementX/Sources/Services/BugReport/BugReportService.swift +++ b/ElementX/Sources/Services/BugReport/BugReportService.swift @@ -22,23 +22,21 @@ import UIKit class BugReportService: NSObject, BugReportServiceProtocol { private let baseURL: URL - private let sentryURL: URL private let applicationId: String private let sdkGitSHA: String private let maxUploadSize: Int private let session: URLSession - private var lastCrashEventId: String? private let progressSubject = PassthroughSubject() private var cancellables = Set() + var lastCrashEventID: String? + init(withBaseURL baseURL: URL, - sentryURL: URL, applicationId: String, sdkGitSHA: String, maxUploadSize: Int, session: URLSession = .shared) { self.baseURL = baseURL - self.sentryURL = sentryURL self.applicationId = applicationId self.sdkGitSHA = sdkGitSHA self.maxUploadSize = maxUploadSize @@ -47,64 +45,10 @@ class BugReportService: NSObject, BugReportServiceProtocol { } // MARK: - BugReportServiceProtocol - - var isRunning: Bool { - SentrySDK.isEnabled - } var crashedLastRun: Bool { SentrySDK.crashedLastRun } - - func start() { - guard !isRunning else { return } - - SentrySDK.start { options in - #if DEBUG - options.enabled = false - #endif - - options.dsn = self.sentryURL.absoluteString - - // 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 - - // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring. - // We recommend adjusting this value in production. - options.tracesSampleRate = 1.0 - - // 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 = { [weak self] event in - MXLog.error("Sentry detected a crash in the previous run: \(event.eventId.sentryIdString)") - self?.lastCrashEventId = event.eventId.sentryIdString - } - } - MXLog.info("Started.") - } - - func stop() { - guard isRunning else { return } - SentrySDK.close() - MXLog.info("Stopped.") - } // swiftlint:disable:next cyclomatic_complexity func submitBugReport(_ bugReport: BugReport, @@ -140,8 +84,8 @@ class BugReportService: NSObject, BugReportServiceProtocol { } } - if let crashEventId = lastCrashEventId { - params.append(MultipartFormData(key: "crash_report", type: .text(value: ""))) + if let crashEventID = lastCrashEventID { + params.append(MultipartFormData(key: "crash_report", type: .text(value: ""))) } for url in bugReport.files { @@ -194,7 +138,7 @@ class BugReportService: NSObject, BugReportServiceProtocol { let uploadResponse = try decoder.decode(SubmitBugReportResponse.self, from: data) if !uploadResponse.reportUrl.isEmpty { - lastCrashEventId = nil + lastCrashEventID = nil } MXLog.info("Feedback submitted.") diff --git a/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift b/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift index 4139523b5..a56057a0a 100644 --- a/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift +++ b/ElementX/Sources/Services/BugReport/BugReportServiceProtocol.swift @@ -52,15 +52,10 @@ enum BugReportServiceError: LocalizedError { } // sourcery: AutoMockable -protocol BugReportServiceProtocol { - // periphery: ignore - var isRunning: Bool { get } - +protocol BugReportServiceProtocol: AnyObject { var crashedLastRun: Bool { get } - func start() - - func stop() + var lastCrashEventID: String? { get set } func submitBugReport(_ bugReport: BugReport, progressListener: CurrentValueSubject) async -> Result diff --git a/ElementX/SupportingFiles/target.yml b/ElementX/SupportingFiles/target.yml index b307e71dc..bdf3347e8 100644 --- a/ElementX/SupportingFiles/target.yml +++ b/ElementX/SupportingFiles/target.yml @@ -196,6 +196,9 @@ targets: - package: SwiftState - package: GZIP - package: Sentry + products: + - Sentry + - SentrySwiftUI - package: Version - package: Emojibase - package: WysiwygComposer diff --git a/UnitTests/Sources/BugReportServiceTests.swift b/UnitTests/Sources/BugReportServiceTests.swift index 59415bc65..415fb80a0 100644 --- a/UnitTests/Sources/BugReportServiceTests.swift +++ b/UnitTests/Sources/BugReportServiceTests.swift @@ -51,7 +51,6 @@ class BugReportServiceTests: XCTestCase { func testInitialStateWithRealService() throws { let service = BugReportService(withBaseURL: "https://www.example.com", - sentryURL: "https://1234@sentry.com/1234", applicationId: "mock_app_id", sdkGitSHA: "1234", maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize, @@ -61,7 +60,6 @@ class BugReportServiceTests: XCTestCase { @MainActor func testSubmitBugReportWithRealService() async throws { let service = BugReportService(withBaseURL: "https://www.example.com", - sentryURL: "https://1234@sentry.com/1234", applicationId: "mock_app_id", sdkGitSHA: "1234", maxUploadSize: ServiceLocator.shared.settings.bugReportMaxUploadSize, diff --git a/project.yml b/project.yml index 113338107..80ff09535 100644 --- a/project.yml +++ b/project.yml @@ -111,7 +111,7 @@ packages: minorVersion: 2.8.0 Sentry: url: https://github.com/getsentry/sentry-cocoa - minorVersion: 8.13.0 + minorVersion: 8.30.0 SnapshotTesting: url: https://github.com/pointfreeco/swift-snapshot-testing minorVersion: 1.16.1