Integrate PLCrashReporter. Fixes #2524

This commit is contained in:
Maurice Parker 2020-12-11 18:09:36 -06:00
parent 43a01c5501
commit 3022f78434
4 changed files with 50 additions and 85 deletions

View File

@ -16,6 +16,7 @@ import RSCore
import RSCoreResources
import Secrets
import OSLog
import CrashReporter
// If we're not going to import Sparkle, provide dummy protocols to make it easy
// for AppDelegate to comply
@ -101,12 +102,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
private let appMovementMonitor = RSAppMovementMonitor()
#if !MAC_APP_STORE && !TEST
private var softwareUpdater: SPUUpdater!
private var crashReporter: PLCrashReporter!
#endif
override init() {
NSWindow.allowsAutomaticWindowTabbing = false
super.init()
#if !MAC_APP_STORE
let crashReporterConfig = PLCrashReporterConfig.defaultConfiguration()
crashReporter = PLCrashReporter(configuration: crashReporterConfig)
crashReporter.enable()
#endif
SecretsManager.provider = Secrets()
AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!)
FeedProviderManager.shared.delegate = ExtensionPointManager.shared
@ -257,9 +265,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
}
#if !MAC_APP_STORE
DispatchQueue.main.async {
CrashReporter.check(appName: "NetNewsWire")
}
DispatchQueue.main.async {
CrashReporter.check(crashReporter: self.crashReporter)
}
#endif
}

View File

@ -8,39 +8,17 @@
import Foundation
import RSWeb
import CrashReporter
// Based originally on Uli Kusterer's UKCrashReporter: http://www.zathras.de/angelweb/blog-ukcrashreporter-oh-one.htm
// Then based on the crash reporter included in NetNewsWire 3 and NetNewsWire Lite 4.
// Displays a window that shows the crash log  gives the user the chance to add data.
// (Or just decide not to send it.)
// This code is not included in the MAS build.
// At some point this code should probably move into RSCore, so Rainier and any other
// future apps can use it.
struct CrashLog {
let path: String
let modificationDate: Date
let content: String
let contentHash: String
init?(path: String, modificationDate: Date) {
guard let s = try? NSString(contentsOfFile: path, usedEncoding: nil) as String, !s.isEmpty else {
return nil
}
self.content = s
self.contentHash = s.md5String
self.path = path
self.modificationDate = modificationDate
}
}
struct CrashReporter {
struct DefaultsKey {
static let lastSeenCrashLogDateKey = "LastSeenCrashLogDate"
static let hashOfLastSeenCrashLogKey = "LastSeenCrashLogHash"
static let sendCrashLogsAutomaticallyKey = "SendCrashLogsAutomatically"
}
@ -48,51 +26,19 @@ struct CrashReporter {
/// Look in ~/Library/Logs/DiagnosticReports/ for a new crash log for this app.
/// Show a crash log reporter window if found.
static func check(appName: String) {
let folder = ("~/Library/Logs/DiagnosticReports/" as NSString).expandingTildeInPath
guard let filenames = try? FileManager.default.contentsOfDirectory(atPath: folder) else {
return
}
let crashSuffix = ".crash"
let lowerAppName = appName.lowercased()
let lastSeenCrashLogDate: Date = {
let lastSeenCrashLogDouble = UserDefaults.standard.double(forKey: DefaultsKey.lastSeenCrashLogDateKey)
return Date(timeIntervalSince1970: lastSeenCrashLogDouble)
}()
var mostRecentFilePath: String? = nil
var mostRecentFileDate = Date.distantPast
for filename in filenames {
if !filename.lowercased().hasPrefix(lowerAppName) || !filename.hasSuffix(crashSuffix) {
continue
}
let path = (folder as NSString).appendingPathComponent(filename)
let fileAttributes = (try? FileManager.default.attributesOfItem(atPath: path)) ?? [FileAttributeKey: Any]()
if let fileModificationDate = fileAttributes[.modificationDate] as? Date {
if fileModificationDate > lastSeenCrashLogDate && fileModificationDate > mostRecentFileDate { // Ignore if previously seen
mostRecentFileDate = fileModificationDate
mostRecentFilePath = path
}
}
}
guard let crashLogPath = mostRecentFilePath, let crashLog = CrashLog(path: crashLogPath, modificationDate: mostRecentFileDate) else {
return
}
if hasSeen(crashLog) {
return
}
remember(crashLog)
static func check(crashReporter: PLCrashReporter) {
guard crashReporter.hasPendingCrashReport(),
let crashData = crashReporter.loadPendingCrashReportData(),
let crashReport = try? PLCrashReport(data: crashData),
let crashLogText = PLCrashReportTextFormatter.stringValue(for: crashReport, with: PLCrashReportTextFormatiOS) else { return }
if shouldSendCrashLogsAutomatically() {
sendCrashLogText(crashLog.content)
}
else {
runCrashReporterWindow(crashLog)
sendCrashLogText(crashLogText)
} else {
runCrashReporterWindow(crashLogText)
}
crashReporter.purgePendingCrashReport()
}
static func sendCrashLogText(_ crashLogText: String) {
@ -113,29 +59,14 @@ struct CrashReporter {
}
}
static func runCrashReporterWindow(_ crashLog: CrashLog) {
crashReportWindowController = CrashReportWindowController(crashLogText: crashLog.content)
static func runCrashReporterWindow(_ crashLogText: String) {
crashReportWindowController = CrashReportWindowController(crashLogText: crashLogText)
crashReportWindowController!.showWindow(self)
}
}
private extension CrashReporter {
static func hasSeen(_ crashLog: CrashLog) -> Bool {
// No need to compare dates, because thats done in the file loop.
// Check to see if weve already reported this exact crash log.
guard let hashOfLastSeenCrashLog = UserDefaults.standard.string(forKey: DefaultsKey.hashOfLastSeenCrashLogKey) else {
return false
}
return hashOfLastSeenCrashLog == crashLog.contentHash
}
static func remember(_ crashLog: CrashLog) {
// Save the modification date and hash, so we dont send duplicates.
UserDefaults.standard.set(crashLog.contentHash, forKey: DefaultsKey.hashOfLastSeenCrashLogKey)
UserDefaults.standard.set(crashLog.modificationDate.timeIntervalSince1970, forKey: DefaultsKey.lastSeenCrashLogDateKey)
}
static func shouldSendCrashLogsAutomatically() -> Bool {
return UserDefaults.standard.bool(forKey: DefaultsKey.sendCrashLogsAutomaticallyKey)
}

View File

@ -400,6 +400,7 @@
5194736F24BBB937001A2939 /* HiddenModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194736D24BBB937001A2939 /* HiddenModifier.swift */; };
5194737124BBCAF4001A2939 /* TimelineSortOrderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5194737024BBCAF4001A2939 /* TimelineSortOrderView.swift */; };
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; };
519CA8E525841DB700EB079A /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = 519CA8E425841DB700EB079A /* CrashReporter */; };
519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; };
519ED456244828C3007F8E94 /* AddExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */; };
519ED47A24482AEB007F8E94 /* EnableExtensionPointViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */; };
@ -2101,6 +2102,7 @@
51E4DAED2425F6940091EB5B /* CloudKit.framework in Frameworks */,
514C16E124D2EF38009A3AFA /* RSCoreResources in Frameworks */,
514C16CE24D2E63F009A3AFA /* Account in Frameworks */,
519CA8E525841DB700EB079A /* CrashReporter in Frameworks */,
51A737BF24DB197F0015FA66 /* RSDatabase in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -3888,6 +3890,7 @@
51A737C424DB19B50015FA66 /* RSWeb */,
51A737C724DB19CC0015FA66 /* RSParser */,
17192AD92567B3D500AAEACA /* RSSparkle */,
519CA8E425841DB700EB079A /* CrashReporter */,
);
productName = NetNewsWire;
productReference = 849C64601ED37A5D003D8FC0 /* NetNewsWire.app */;
@ -4012,6 +4015,7 @@
51B0DF0D24D24E3B000AD99E /* XCRemoteSwiftPackageReference "RSDatabase" */,
51B0DF2324D2C7FA000AD99E /* XCRemoteSwiftPackageReference "RSParser" */,
17192AD82567B3D500AAEACA /* XCRemoteSwiftPackageReference "Sparkle-Binary" */,
519CA8E325841DB700EB079A /* XCRemoteSwiftPackageReference "plcrashreporter" */,
);
productRefGroup = 849C64611ED37A5D003D8FC0 /* Products */;
projectDirPath = "";
@ -5940,6 +5944,14 @@
minimumVersion = "1.0.0-beta1";
};
};
519CA8E325841DB700EB079A /* XCRemoteSwiftPackageReference "plcrashreporter" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/microsoft/plcrashreporter.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.8.1;
};
};
51B0DF0D24D24E3B000AD99E /* XCRemoteSwiftPackageReference "RSDatabase" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Ranchero-Software/RSDatabase.git";
@ -6089,6 +6101,11 @@
isa = XCSwiftPackageProductDependency;
productName = Account;
};
519CA8E425841DB700EB079A /* CrashReporter */ = {
isa = XCSwiftPackageProductDependency;
package = 519CA8E325841DB700EB079A /* XCRemoteSwiftPackageReference "plcrashreporter" */;
productName = CrashReporter;
};
51A737AD24DB19730015FA66 /* RSCore */ = {
isa = XCSwiftPackageProductDependency;
package = 5102AE4324D17E820050839C /* XCRemoteSwiftPackageReference "RSCore" */;

View File

@ -46,6 +46,15 @@
"version": "2.1.2"
}
},
{
"package": "PLCrashReporter",
"repositoryURL": "https://github.com/microsoft/plcrashreporter.git",
"state": {
"branch": null,
"revision": "de6b8f9db4b2a0aa859a5507550a70548e4da936",
"version": "1.8.1"
}
},
{
"package": "RSCore",
"repositoryURL": "https://github.com/Ranchero-Software/RSCore.git",