diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 2a7664d40..c275e192d 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -649,6 +649,7 @@ 6B3AB95A6993646A5081BFF9 /* TestablePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43F773904F87FF5ADFE4DD1 /* TestablePreview.swift */; }; 6B61F5B27412ED4BC2F9769C /* test_audio.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 66B96842BF5F8ACA1AC84C55 /* test_audio.mp3 */; }; 6B80C24A52411EAF10E06E96 /* SecurityAndPrivacyScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBADF8010C813D905C172CE /* SecurityAndPrivacyScreenModels.swift */; }; + 6BA9214F31A24C7A84287A56 /* SentryEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 337AD37403015231CAE3E80B /* SentryEvent.swift */; }; 6BAE34CFA9821709CFE61E50 /* DeveloperOptionsScreenHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F60FDB3AEFC60830BD289FE /* DeveloperOptionsScreenHook.swift */; }; 6BEB10499149444909EBE42F /* TrackedUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10D751743B20B314D84D79FE /* TrackedUserDefaults.swift */; }; 6C34237AFB808E38FC8776B9 /* RoomStateEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */; }; @@ -1942,6 +1943,7 @@ 330AF4D121C3396F7A14B21D /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/SAS.strings; sourceTree = ""; }; 3339B1DDB1341E833D2555BC /* AVMetadataMachineReadableCodeObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVMetadataMachineReadableCodeObject.swift; sourceTree = ""; }; 33752AE856E93CE62412B7A1 /* LiveLocationManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveLocationManagerProtocol.swift; sourceTree = ""; }; + 337AD37403015231CAE3E80B /* SentryEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEvent.swift; sourceTree = ""; }; 33AE897D86784CCA5E4E9227 /* ElementCallService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallService.swift; sourceTree = ""; }; 33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; 3423C39324CF851A72EC9976 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/SAS.strings; sourceTree = ""; }; @@ -4296,6 +4298,7 @@ 7310D8DFE01AF45F0689C3AA /* Publisher.swift */, DADECBBB672497BCD4822468 /* Result.swift */, 584A61D9C459FAFEF038A7C0 /* Section.swift */, + 337AD37403015231CAE3E80B /* SentryEvent.swift */, DF17EA323AD0205A6AB621AA /* Snapshotting.swift */, 40B21E611DADDEF00307E7AC /* String.swift */, A40C19719687984FD9478FBE /* Task.swift */, @@ -8942,6 +8945,7 @@ 7C545FFEC9930F7247352593 /* SecurityAndPrivacyScreenViewModel.swift in Sources */, 0D617A152D099D94271D3BA8 /* SecurityAndPrivacyScreenViewModelProtocol.swift in Sources */, A588572ED0EB18D947B32A5E /* SendInviteConfirmationView.swift in Sources */, + 6BA9214F31A24C7A84287A56 /* SentryEvent.swift in Sources */, 08547E55DD3686A84550996D /* SeparatorMediaEventsTimelineView.swift in Sources */, 14E99D27628B1A6F0CB46FEA /* SeparatorRoomTimelineItem.swift in Sources */, 5341D48F833E3E30F16FA2A3 /* SeparatorRoomTimelineView.swift in Sources */, diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 84cbfb144..7363dbeca 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -1019,6 +1019,14 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg bugReportService.lastCrashEventID = event.eventId.sentryIdString } + // Mirror every crash into our own logs (which ship with rageshakes) before it's sent to Sentry. + options.beforeSend = { event in + if let crashLog = event.crashLog { + MXLog.error("Sentry crash event \(event.eventId.sentryIdString):\n\(crashLog)") + } + return event + } + // Any ongoing transactions will no longer be valid after calling SentrySDK.start so lets // remove them and start over, otherwise the app will crash if finishTransaction is used. analytics.signpost.resetTransactions() diff --git a/ElementX/Sources/Other/Extensions/SentryEvent.swift b/ElementX/Sources/Other/Extensions/SentryEvent.swift new file mode 100644 index 000000000..b15d10cfe --- /dev/null +++ b/ElementX/Sources/Other/Extensions/SentryEvent.swift @@ -0,0 +1,34 @@ +// +// Copyright 2025 Element Creations Ltd. +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +// Please see LICENSE files in the repository root for full details. +// + +import MatrixRustSDK +import Sentry +import UIKit + +extension Event { + /// A human readable crash log matching the format used by the legacy app. + /// `nil` for non-crash events (handled errors, transactions, etc.). + var crashLog: String? { + guard let exceptions, + exceptions.contains(where: { $0.mechanism?.handled?.boolValue == false }) else { + return nil + } + + let reason = exceptions.map { "\($0.type ?? "Unknown"): \($0.value ?? "")" }.joined(separator: "\n") + let infoPlist = InfoPlistReader.main + let device = UIDevice.current + + return """ + \(reason) + Application: \(infoPlist.bundleExecutable) (\(infoPlist.bundleIdentifier)) + Application version: \(infoPlist.bundleShortVersionString) (\(infoPlist.bundleVersion)) + Matrix SDK version: \(sdkGitSha()) + \(device.model) \(device.systemVersion) + """ + } +}