Skip to content

Commit

Permalink
Upload exception message to Sentry (#2886)
Browse files Browse the repository at this point in the history
  • Loading branch information
mallexxx authored Jul 12, 2024
1 parent 9ed5054 commit c2ad42a
Show file tree
Hide file tree
Showing 15 changed files with 123 additions and 87 deletions.
8 changes: 1 addition & 7 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,6 @@
3706FC57293F65D500E42796 /* TabPreviewWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC82C5F258B6CB5009B6B42 /* TabPreviewWindowController.swift */; };
3706FC58293F65D500E42796 /* NSSizeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4E325D6BA9C007F5990 /* NSSizeExtension.swift */; };
3706FC59293F65D500E42796 /* Fire.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6820EA25503D6A005ED0D5 /* Fire.swift */; };
3706FC5A293F65D500E42796 /* RandomAccessCollectionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AAAC3D26048F690029438D /* RandomAccessCollectionExtension.swift */; };
3706FC5B293F65D500E42796 /* NSOutlineViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292AE26670F5300AD2C21 /* NSOutlineViewExtensions.swift */; };
3706FC5C293F65D500E42796 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA585D81248FD31100E9A3E2 /* AppDelegate.swift */; };
3706FC5D293F65D500E42796 /* ContentOverlayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1E819D27C8874900FF0E60 /* ContentOverlayViewController.swift */; };
Expand Down Expand Up @@ -2344,7 +2343,6 @@
B6AA64732994B43300D99CD6 /* FutureExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */; };
B6AA64742994B43300D99CD6 /* FutureExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */; };
B6AAAC2D260330580029438D /* PublishedAfter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AAAC2C260330580029438D /* PublishedAfter.swift */; };
B6AAAC3E26048F690029438D /* RandomAccessCollectionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AAAC3D26048F690029438D /* RandomAccessCollectionExtension.swift */; };
B6ABC5962B4861D4008343B9 /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */; };
B6ABC5972B4861D4008343B9 /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */; };
B6ABD0CA2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */; };
Expand Down Expand Up @@ -4073,7 +4071,6 @@
B6A9E46A2614618A0067D1B9 /* OperatingSystemVersionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperatingSystemVersionExtension.swift; sourceTree = "<group>"; };
B6AA64722994B43300D99CD6 /* FutureExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FutureExtensionTests.swift; sourceTree = "<group>"; };
B6AAAC2C260330580029438D /* PublishedAfter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedAfter.swift; sourceTree = "<group>"; };
B6AAAC3D26048F690029438D /* RandomAccessCollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomAccessCollectionExtension.swift; sourceTree = "<group>"; };
B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableTextField.swift; sourceTree = "<group>"; };
B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityScopedFileURLController.swift; sourceTree = "<group>"; };
B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSURL+sandboxExtensionRetainCount.m"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -7697,7 +7694,6 @@
B684592125C93BE000DC17B6 /* Publisher.asVoid.swift */,
B68C2FB127706E6A00BF2C7D /* ProcessExtension.swift */,
B684592625C93C0500DC17B6 /* Publishers.NestedObjectChanges.swift */,
B6AAAC3D26048F690029438D /* RandomAccessCollectionExtension.swift */,
4BB88B4925B7B690006F6B06 /* SequenceExtensions.swift */,
1DFAB51C2A8982A600A0F7F6 /* SetExtension.swift */,
B65783E625F8AAFB00D8DB33 /* String+Punycode.swift */,
Expand Down Expand Up @@ -10471,7 +10467,6 @@
1D9A37682BD8EA8800EBC58D /* DockPositionProvider.swift in Sources */,
3706FC58293F65D500E42796 /* NSSizeExtension.swift in Sources */,
3706FC59293F65D500E42796 /* Fire.swift in Sources */,
3706FC5A293F65D500E42796 /* RandomAccessCollectionExtension.swift in Sources */,
7BEC20432B0F505F00243D3E /* AddBookmarkPopoverView.swift in Sources */,
3706FC5B293F65D500E42796 /* NSOutlineViewExtensions.swift in Sources */,
3706FC5C293F65D500E42796 /* AppDelegate.swift in Sources */,
Expand Down Expand Up @@ -11894,7 +11889,6 @@
9F56CFA92B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift in Sources */,
37445F9C2A1569F00029F789 /* SyncBookmarksAdapter.swift in Sources */,
C1E961EF2B87AA29001760E1 /* AutofillActionBuilder.swift in Sources */,
B6AAAC3E26048F690029438D /* RandomAccessCollectionExtension.swift in Sources */,
4B9292AF26670F5300AD2C21 /* NSOutlineViewExtensions.swift in Sources */,
9F56CFAD2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift in Sources */,
1D4071AE2BD64267002D4537 /* DockCustomizer.swift in Sources */,
Expand Down Expand Up @@ -13316,7 +13310,7 @@
repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 169.1.1;
version = 170.0.0;
};
};
9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/BrowserServicesKit",
"state" : {
"revision" : "284748afd9c9f1b1962391857f67ff4735e1313f",
"version" : "169.1.1"
"revision" : "33ceded0295158678da10d8ed685e64d3ad1a0d6",
"version" : "170.0.0"
}
},
{
Expand Down Expand Up @@ -75,7 +75,7 @@
{
"identity" : "lottie-spm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/airbnb/lottie-spm",
"location" : "https://github.com/airbnb/lottie-spm.git",
"state" : {
"revision" : "1d29eccc24cc8b75bff9f6804155112c0ffc9605",
"version" : "4.4.3"
Expand Down
23 changes: 21 additions & 2 deletions DuckDuckGo/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,25 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
@UserDefaultsWrapper(key: .firstLaunchDate, defaultValue: Date.monthAgo)
static var firstLaunchDate: Date

@UserDefaultsWrapper
private var didCrashDuringCrashHandlersSetUp: Bool

static var isNewUser: Bool {
return firstLaunchDate >= Date.weekAgo
}

override init() {
// will not add crash handlers and will fire pixel on applicationDidFinishLaunching if didCrashDuringCrashHandlersSetUp == true
let didCrashDuringCrashHandlersSetUp = UserDefaultsWrapper(key: .didCrashDuringCrashHandlersSetUp, defaultValue: false)
_didCrashDuringCrashHandlersSetUp = didCrashDuringCrashHandlersSetUp
if case .normal = NSApplication.runType,
!didCrashDuringCrashHandlersSetUp.wrappedValue {

didCrashDuringCrashHandlersSetUp.wrappedValue = true
CrashLogMessageExtractor.setUp()
didCrashDuringCrashHandlersSetUp.wrappedValue = false
}

do {
let encryptionKey = NSApplication.runType.requiresEnvironment ? try keyStore.readKey() : nil
fileStore = EncryptedFileStore(encryptionKey: encryptionKey)
Expand Down Expand Up @@ -334,13 +348,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
applyPreferredTheme()

#if APPSTORE
crashCollection.start { pixelParameters, payloads, completion in
crashCollection.startAttachingCrashLogMessages { pixelParameters, payloads, completion in
pixelParameters.forEach { _ in PixelKit.fire(GeneralPixel.crash) }
guard let lastPayload = payloads.last else {
return
}
DispatchQueue.main.async {
CrashReportPromptPresenter().showPrompt(for: lastPayload, userDidAllowToReport: completion)
CrashReportPromptPresenter().showPrompt(for: CrashDataPayload(data: lastPayload), userDidAllowToReport: completion)
}
}
#else
Expand Down Expand Up @@ -374,6 +388,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
setUpAutofillPixelReporter()

remoteMessagingClient?.startRefreshingRemoteMessages()

if didCrashDuringCrashHandlersSetUp {
PixelKit.fire(GeneralPixel.crashOnCrashHandlersSetUp)
didCrashDuringCrashHandlersSetUp = false
}
}

private func fireFailedCompilationsPixelIfNeeded() {
Expand Down
5 changes: 5 additions & 0 deletions DuckDuckGo/Application/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,9 @@ final class Application: NSApplication {
fatalError("\(Self.self): Bad initializer")
}

@objc(_crashOnException:)
func crash(on exception: NSException) {
NSGetUncaughtExceptionHandler()?(exception)
}

}
32 changes: 0 additions & 32 deletions DuckDuckGo/Common/Extensions/RandomAccessCollectionExtension.swift

This file was deleted.

17 changes: 0 additions & 17 deletions DuckDuckGo/Common/Extensions/StringExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,6 @@ extension String {
}
}

var utf8data: Data {
data(using: .utf8)!
}

var isBlank: Bool {
self.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
Expand Down Expand Up @@ -113,19 +109,6 @@ extension String {
return fileName
}

var pathExtension: String {
(self as NSString).pathExtension
}

func appendingPathComponent(_ component: String) -> String {
(self as NSString).appendingPathComponent(component)
}

func appendingPathExtension(_ pathExtension: String?) -> String {
guard let pathExtension, !pathExtension.isEmpty else { return self }
return self + "." + pathExtension
}

// MARK: - Mutating

@inlinable mutating func prepend(_ string: String) {
Expand Down
1 change: 1 addition & 0 deletions DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public struct UserDefaultsWrapper<T> {
case saveAsPreferredFileType = "saveAs.selected.filetype"

case lastCrashReportCheckDate = "last.crash.report.check.date"
case didCrashDuringCrashHandlersSetUp = "browser.didCrashDuringCrashHandlersSetUp"

case fireInfoPresentedOnce = "fire.info.presented.once"
case appTerminationHandledCorrectly = "app.termination.handled.correctly"
Expand Down
85 changes: 66 additions & 19 deletions DuckDuckGo/CrashReports/Model/CrashReport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
// limitations under the License.
//

import Common
import Crashes
import Foundation
import MetricKit

Expand All @@ -32,73 +34,118 @@ protocol CrashReport: CrashReportPresenting {

}

struct LegacyCrashReport: CrashReport {
final class LegacyCrashReport: CrashReport {

static let fileExtension = "crash"

static let headerItemsToFilter = [
private static let headerItemsToFilter = [
"Anonymous UUID:",
"Sleep/Wake UUID:"
]
private static let pidRegex = regex(#"^Process:.*\[(\d+)\]$"#)
private static let timestampRegex = regex(#"Date\/Time:\s+(.+)\s*$"#)
private static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS Z"
return formatter
}()

let url: URL

var content: String? {
try? String(contentsOf: url)
init(url: URL) {
self.url = url
}

lazy var content: String? = {
guard var fileContents = try? String(contentsOf: url)
.components(separatedBy: "\n")
.filter({ line in
for headerItemToFilter in Self.headerItemsToFilter where line.hasPrefix(headerItemToFilter) {
return false
}
return true
})
.joined(separator: "\n")
}
.joined(separator: "\n") else { return nil }

// prepend crash log message if loaded
let pid = fileContents.firstMatch(of: Self.pidRegex)?.range(at: 1, in: fileContents).flatMap { pid_t(fileContents[$0]) }
let timestamp = fileContents.firstMatch(of: Self.timestampRegex)?.range(at: 1, in: fileContents).flatMap {
Self.dateFormatter.date(from: String(fileContents[$0]))
}
if let diagnostic = try? CrashLogMessageExtractor().crashDiagnostic(for: timestamp, pid: pid)?.diagnosticData(), !diagnostic.isEmpty,
let message = try? JSONEncoder().encode(diagnostic).utf8String()?.replacingOccurrences(of: "\n", with: "\\n") {
fileContents = "Message: " + message + "\n" + fileContents
}

return fileContents
}()

var contentData: Data? {
content?.data(using: .utf8)
}

}

struct JSONCrashReport: CrashReport {
final class JSONCrashReport: CrashReport {

static let fileExtension = "ips"

static let headerItemsToFilter = [
private static let headerItemsToFilter = [
"sleepWakeUUID",
"deviceIdentifierForVendor",
"rolloutId"
]
private static let pidRegex = regex(#""pid"\s*:\s*(\d+)(?:,|$)"#)
private static let timestampRegex = regex(#""timestamp"\s*:\s*"([^"]+)""#)
private static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SS Z"
return formatter
}()

let url: URL

var content: String? {
guard var fileContents = try? String(contentsOf: url) else {
return nil
}
init(url: URL) {
self.url = url
}

lazy var content: String? = {
guard var fileContents = try? String(contentsOf: self.url) else { return nil }

for itemToFilter in Self.headerItemsToFilter {
let patternToReplace = "\"\(itemToFilter)\"\\s*:\\s*\"[^\"]*\""
let redactedKeyValuePair = "\"\(itemToFilter)\":\"<removed>\""

fileContents = fileContents.replacingOccurrences(of: patternToReplace,
with: redactedKeyValuePair,
options: .regularExpression)
fileContents = fileContents.replacingOccurrences(of: patternToReplace, with: redactedKeyValuePair, options: .regularExpression)
}

// append crash log message and stack trace if loaded
let pid = fileContents.firstMatch(of: Self.pidRegex)?.range(at: 1, in: fileContents).flatMap { pid_t(fileContents[$0]) }
let timestamp = fileContents.firstMatch(of: Self.timestampRegex)?.range(at: 1, in: fileContents).flatMap {
Self.dateFormatter.date(from: String(fileContents[$0]))
}
if let diagnostic = try? CrashLogMessageExtractor().crashDiagnostic(for: timestamp, pid: pid)?.diagnosticData(), !diagnostic.isEmpty,
let json = try? JSONEncoder().encode(diagnostic).utf8String()?.trimmingCharacters(in: CharacterSet(charactersIn: "{}")),
let openBraceIdx = fileContents.firstIndex(of: "{") {
// insert `"message": "…", "stackTrace": […],` json part after the first `{` in the report
fileContents.insert(contentsOf: json + ",", at: fileContents.index(after: openBraceIdx))
}

return fileContents
}
}()

var contentData: Data? {
content?.data(using: .utf8)
}

}

@available(macOS 12.0, *)
extension MXDiagnosticPayload: CrashReportPresenting {
struct CrashDataPayload: CrashReportPresenting {
let data: Data

var content: String? {
jsonRepresentation().utf8String()
data.utf8String()
}
}
6 changes: 5 additions & 1 deletion DuckDuckGo/Menus/MainMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,11 @@ final class MainMenu: NSMenu {
.submenu(NetworkProtectionDebugMenu())
}

NSMenuItem(title: "Trigger Fatal Error", action: #selector(MainViewController.triggerFatalError))
NSMenuItem(title: "Simulate crash") {
NSMenuItem(title: "fatalError", action: #selector(MainViewController.triggerFatalError))
NSMenuItem(title: "NSException", action: #selector(MainViewController.crashOnException))
NSMenuItem(title: "C++ exception", action: #selector(MainViewController.crashOnCxxException))
}

let isInternalTestingWrapper = UserDefaultsWrapper(key: .subscriptionInternalTesting, defaultValue: false)
let subscriptionAppGroup = Bundle.main.appGroup(bundle: .subs)
Expand Down
Loading

0 comments on commit c2ad42a

Please sign in to comment.