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 ────────────────────────────────────────────
|
// ── 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 Foundation
|
||||||
import MetricKit
|
import ByteLystPlatformSDK
|
||||||
|
|
||||||
|
typealias CrashReport = BLCrashReport
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class CrashReporter: NSObject, ObservableObject, MXMetricManagerSubscriber {
|
final class CrashReporter: ObservableObject {
|
||||||
static let shared = CrashReporter()
|
static let shared = CrashReporter()
|
||||||
|
|
||||||
@Published var lastCrashReport: Date?
|
@Published var lastCrashReport: Date?
|
||||||
@Published var diagnosticCount: Int = 0
|
@Published var diagnosticCount: Int = 0
|
||||||
|
|
||||||
private let persistenceKey = "chronomind-crash-reports"
|
private let reporter: BLCrashReporter
|
||||||
|
|
||||||
override private init() {
|
private init() {
|
||||||
super.init()
|
reporter = BLCrashReporter(productId: "chronomind")
|
||||||
MXMetricManager.shared.add(self)
|
diagnosticCount = reporter.diagnosticCount
|
||||||
loadStats()
|
lastCrashReport = reporter.lastCrashReport
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCrashReports() -> [CrashReport] {
|
func loadCrashReports() -> [CrashReport] {
|
||||||
guard let data = UserDefaults.standard.data(forKey: persistenceKey),
|
reporter.loadCrashReports()
|
||||||
let reports = try? JSONDecoder().decode([CrashReport].self, from: data) else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return reports
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearReports() {
|
func clearReports() {
|
||||||
UserDefaults.standard.removeObject(forKey: persistenceKey)
|
reporter.clearReports()
|
||||||
diagnosticCount = 0
|
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