refactor(ios): migrate CrashReporter to ByteLystPlatformSDK's BLCrashReporter
Replaced standalone MetricKit crash reporter (153 lines) with thin wrapper (34 lines) delegating to SDK's BLCrashReporter. Preserves existing call-site API.
This commit is contained in:
parent
d1b4534b22
commit
3adab843cd
@ -1,152 +1,33 @@
|
||||
// ── Crash Reporter ────────────────────────────────────────────
|
||||
// MetricKit-based crash and performance reporting for TestFlight/Production
|
||||
// Wrapper over ByteLystPlatformSDK's BLCrashReporter.
|
||||
// Preserves existing call-site API (CrashReporter.shared, loadCrashReports, clearReports).
|
||||
|
||||
import Foundation
|
||||
import MetricKit
|
||||
import ByteLystPlatformSDK
|
||||
|
||||
typealias CrashReport = BLCrashReport
|
||||
|
||||
@MainActor
|
||||
final class CrashReporter: NSObject, ObservableObject, MXMetricManagerSubscriber {
|
||||
final class CrashReporter: ObservableObject {
|
||||
static let shared = CrashReporter()
|
||||
|
||||
@Published var lastCrashReport: Date?
|
||||
@Published var diagnosticCount: Int = 0
|
||||
|
||||
private let persistenceKey = "chronomind-crash-reports"
|
||||
private let reporter: BLCrashReporter
|
||||
|
||||
override private init() {
|
||||
super.init()
|
||||
MXMetricManager.shared.add(self)
|
||||
loadStats()
|
||||
}
|
||||
|
||||
deinit {
|
||||
MXMetricManager.shared.remove(self)
|
||||
}
|
||||
|
||||
// MARK: - MXMetricManagerSubscriber
|
||||
|
||||
nonisolated func didReceive(_ payloads: [MXMetricPayload]) {
|
||||
// MetricKit delivers daily aggregated metrics
|
||||
Task { @MainActor in
|
||||
for payload in payloads {
|
||||
processMetricPayload(payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated func didReceive(_ payloads: [MXDiagnosticPayload]) {
|
||||
// Crash diagnostics — delivered after app restart following crash
|
||||
Task { @MainActor in
|
||||
for payload in payloads {
|
||||
processDiagnosticPayload(payload)
|
||||
}
|
||||
self.diagnosticCount += payloads.count
|
||||
self.lastCrashReport = Date()
|
||||
self.saveStats()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Processing
|
||||
|
||||
private func processMetricPayload(_ payload: MXMetricPayload) {
|
||||
// Log key performance metrics
|
||||
if let launchTime = payload.applicationLaunchMetrics {
|
||||
let resumeTime = launchTime.histogrammedResumeTime
|
||||
logMetric("app_resume_time", histogram: resumeTime)
|
||||
}
|
||||
|
||||
if let responsiveness = payload.applicationResponsivenessMetrics {
|
||||
let hangTime = responsiveness.histogrammedApplicationHangTime
|
||||
logMetric("hang_time", histogram: hangTime)
|
||||
}
|
||||
}
|
||||
|
||||
private func processDiagnosticPayload(_ payload: MXDiagnosticPayload) {
|
||||
// Store crash data locally for feedback form
|
||||
if let crashDiagnostics = payload.crashDiagnostics {
|
||||
for crash in crashDiagnostics {
|
||||
let report = CrashReport(
|
||||
date: Date(),
|
||||
exceptionType: crash.exceptionType?.description,
|
||||
signal: crash.signal?.description,
|
||||
terminationReason: crash.terminationReason?.description,
|
||||
callStackTree: crash.callStackTree.jsonRepresentation()
|
||||
)
|
||||
storeCrashReport(report)
|
||||
}
|
||||
}
|
||||
|
||||
if let hangDiagnostics = payload.hangDiagnostics {
|
||||
for hang in hangDiagnostics {
|
||||
let report = CrashReport(
|
||||
date: Date(),
|
||||
exceptionType: nil,
|
||||
signal: nil,
|
||||
terminationReason: "Hang: \(hang.hangDuration.description)",
|
||||
callStackTree: hang.callStackTree.jsonRepresentation()
|
||||
)
|
||||
storeCrashReport(report)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Storage
|
||||
|
||||
private func storeCrashReport(_ report: CrashReport) {
|
||||
var reports = loadCrashReports()
|
||||
reports.append(report)
|
||||
// Keep last 50 reports
|
||||
if reports.count > 50 {
|
||||
reports = Array(reports.suffix(50))
|
||||
}
|
||||
if let data = try? JSONEncoder().encode(reports) {
|
||||
UserDefaults.standard.set(data, forKey: persistenceKey)
|
||||
}
|
||||
private init() {
|
||||
reporter = BLCrashReporter(productId: "chronomind")
|
||||
diagnosticCount = reporter.diagnosticCount
|
||||
lastCrashReport = reporter.lastCrashReport
|
||||
}
|
||||
|
||||
func loadCrashReports() -> [CrashReport] {
|
||||
guard let data = UserDefaults.standard.data(forKey: persistenceKey),
|
||||
let reports = try? JSONDecoder().decode([CrashReport].self, from: data) else {
|
||||
return []
|
||||
}
|
||||
return reports
|
||||
reporter.loadCrashReports()
|
||||
}
|
||||
|
||||
func clearReports() {
|
||||
UserDefaults.standard.removeObject(forKey: persistenceKey)
|
||||
reporter.clearReports()
|
||||
diagnosticCount = 0
|
||||
}
|
||||
|
||||
private func loadStats() {
|
||||
diagnosticCount = loadCrashReports().count
|
||||
}
|
||||
|
||||
private func saveStats() {
|
||||
// Stats are derived from stored reports
|
||||
}
|
||||
|
||||
private func logMetric(_ name: String, histogram: MXHistogram<UnitDuration>) {
|
||||
// In production, send to analytics service
|
||||
// For now, just track locally
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Crash Report Model
|
||||
|
||||
struct CrashReport: Codable, Identifiable {
|
||||
let id: String
|
||||
let date: Date
|
||||
let exceptionType: String?
|
||||
let signal: String?
|
||||
let terminationReason: String?
|
||||
let callStackData: Data?
|
||||
|
||||
init(date: Date, exceptionType: String?, signal: String?, terminationReason: String?, callStackTree: Data) {
|
||||
self.id = UUID().uuidString
|
||||
self.date = date
|
||||
self.exceptionType = exceptionType
|
||||
self.signal = signal
|
||||
self.terminationReason = terminationReason
|
||||
self.callStackData = callStackTree
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user