新增模块:XuqmBugCollectSDK - BugCollectSDK: 主入口,错误捕获 API - CrashCapture: 崩溃捕获(NSException + POSIX 信号) - IssueEvent/LogEvent: 数据模型 - Fingerprint: 错误指纹计算(SHA-256) - LogQueue: 内存队列 + 定时上传 - LogStorage: 崩溃文件持久化 - LogUploader: HTTP 上传到服务端 - FunnelTracker: 漏斗追踪 功能: - captureError/captureCrash 捕获错误 - addBreadcrumb 添加面包屑 - event 记录自定义事件 - warn/info 日志级别记录 - 自动初始化(onInitialize hook) Co-Authored-By: Claude <noreply@anthropic.com>
63 行
1.9 KiB
Swift
63 行
1.9 KiB
Swift
import Foundation
|
|
|
|
/// 崩溃文件持久化
|
|
enum LogStorage {
|
|
|
|
private static let crashDir = "xuqm_bugcollect_crashes"
|
|
private static let encoder: JSONEncoder = {
|
|
let e = JSONEncoder()
|
|
e.dateEncodingStrategy = .iso8601
|
|
return e
|
|
}()
|
|
|
|
/// 保存崩溃到磁盘
|
|
static func saveCrash(_ event: IssueEvent) {
|
|
let dir = crashDirectory()
|
|
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
|
|
let filename = "\(Int(Date().timeIntervalSince1970))_\(event.fingerprint.prefix(8)).json"
|
|
let url = dir.appendingPathComponent(filename)
|
|
|
|
if let data = try? encoder.encode(event) {
|
|
try? data.write(to: url)
|
|
}
|
|
}
|
|
|
|
/// 加载待处理的崩溃
|
|
static func loadPendingCrashes() -> [IssueEvent] {
|
|
let dir = crashDirectory()
|
|
guard let files = try? FileManager.default.contentsOfDirectory(
|
|
at: dir,
|
|
includingPropertiesForKeys: nil
|
|
) else { return [] }
|
|
|
|
let decoder = JSONDecoder()
|
|
decoder.dateDecodingStrategy = .iso8601
|
|
|
|
return files
|
|
.filter { $0.pathExtension == "json" }
|
|
.compactMap { url -> IssueEvent? in
|
|
guard let data = try? Data(contentsOf: url) else { return nil }
|
|
return try? decoder.decode(IssueEvent.self, from: data)
|
|
}
|
|
}
|
|
|
|
/// 清除已上传的崩溃
|
|
static func clearPendingCrashes() {
|
|
let dir = crashDirectory()
|
|
guard let files = try? FileManager.default.contentsOfDirectory(
|
|
at: dir,
|
|
includingPropertiesForKeys: nil
|
|
) else { return }
|
|
|
|
for file in files {
|
|
try? FileManager.default.removeItem(at: file)
|
|
}
|
|
}
|
|
|
|
private static func crashDirectory() -> URL {
|
|
let caches = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
|
return caches.appendingPathComponent(crashDir)
|
|
}
|
|
}
|