前言
上星期看了一篇掘友的文章——APM – iOS Crash监控 KSCrash代码解析,首要便是对KSCrash这个结构的源码做了剖析。
最近手上正好有个项目要集成溃散盯梢相关功用,细心看了一下掘友的这篇文章,顺带也在Github上面了解一下这个项目。所以决定用KSCrash在项目中。
我决定运用KSCrash有以下2个原因:
- 一开始我是引荐公司运用Bugly的,可是项目负责人意思是不期望溃散相关的数据在其他平台上,仍是期望自己能管控起来。
- 期望能够找到开源、高质量的bug盯梢工具,说白了便是期望集成成本低,功用又不错。
所以乎,就有了这篇文章。
KSCrash的集成
KSCrash支持cocopods,所以需求像官方文档中写的那样手动集成,直接在Podfile中添加后,一句pod install
就搞定了。
别的文档中说明晰KSCrash说明晰上传溃散日志到后台的方式,我简略说一下。
KSCrash can report to the following servers:
- Hockey:网页打不开了,感觉应该用不了。
- QuincyKit:这个应该是要自己建立一个php服务器,一起还需求在App中集成别的的SDK合作运用,如同没有必要,我只需求将溃散日志上传到项目的服务器即可。
- Victory:An error reporting server in Python. It runs on Google App Engine.一个用Python写的溃散盯梢办理平台,也没有必要。
- Email:这个比较合适个人开发者,有溃散后,经过邮件的形式发给开发者的邮箱中。
- Standard:上传到自定义的URL中,进行网络恳求并上传。
怎么看都觉得Standard
比较合适我需求的方式,代码立马走起,因为我的项目首要是运用Swift,所以这儿都经过Swift代码进行展示:
func installCrashHandler() {
let installation = makeStandardInstallation()
installation.sendAllReports { array, completed, error in
if completed {
print("Sent \(array?.count ?? 0) reports")
} else {
print("Failed to send reports: \(error.debugDescription)")
}
}
installation.install()
}
private func makeStandardInstallation() -> KSCrashInstallation {
let standard = KSCrashInstallationStandard.sharedInstance()!
let url = URL(string: "溃散日志上传地址")
standard.url = url
return standard
}
最终咱们只需求将installCrashHandler()
这个办法在func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
中调用即可。
为了看看溃散日志的收集作用,我特地在自己项目里边写了一个数组越界,然后去沙盒捞了一把日志,我只摘要其中的要害信息:
"threads": [
{
"backtrace": {
"contents": [
{
"object_name": "libswiftCore.dylib",
"object_addr": 7358046208,
"symbol_name": "<redacted>",
"symbol_addr": 7358277980,
"instruction_addr": 7358278340
},
{
"object_name": "libswiftCore.dylib",
"object_addr": 7358046208,
"symbol_name": "<redacted>",
"symbol_addr": 7358277980,
"instruction_addr": 7358278340
},
{
"object_name": "libswiftCore.dylib",
"object_addr": 7358046208,
"symbol_name": "<redacted>",
"symbol_addr": 7358277476,
"instruction_addr": 7358277672
},
{
"object_name": "libswiftCore.dylib",
"object_addr": 7358046208,
"symbol_name": "<redacted>",
"symbol_addr": 7358276960,
"instruction_addr": 7358277168
},
{
"object_name": "libswiftCore.dylib",
"object_addr": 7358046208,
"symbol_name": "$ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF",
"symbol_addr": 7358275744,
"instruction_addr": 7358275976
},
{
"object_name": "libswiftCore.dylib",
"object_addr": 7358046208,
"symbol_name": "$ss12_ArrayBufferV37_checkInoutAndNativeTypeCheckedBounds_03wasfgH0ySi_SbtF",
"symbol_addr": 7358125164,
"instruction_addr": 7358125444
},
{
"object_name": "libswiftCore.dylib",
"object_addr": 7358046208,
"symbol_name": "$sSayxSicig",
"symbol_addr": 7358144088,
"instruction_addr": 7358144176
},
{
"object_name": "RxStudy",
"object_addr": 4308205568,
"symbol_name": "$s7RxStudy16HotKeyControllerC7setupUI33_D55B00154F4922155B5D3E54789A2010LLyyF",
"symbol_addr": 4309867052,
"instruction_addr": 4309869376
},
{
"object_name": "RxStudy",
"object_addr": 4308205568,
"symbol_name": "$s7RxStudy16HotKeyControllerC11viewDidLoadyyF",
"symbol_addr": 4309866844,
"instruction_addr": 4309866936
},
{
"object_name": "RxStudy",
"object_addr": 4308205568,
"symbol_name": "$s7RxStudy16HotKeyControllerC11viewDidLoadyyFTo",
"symbol_addr": 4309866992,
"instruction_addr": 4309867028
}
.
.
.
.
.
.
我大约收拾一下这段json中的一些要害信息:
- $ss12_ArrayBufferV37_checkInoutAndNativeTypeCheckedBounds_03wasfgH0ySi_SbtF
- $s7RxStudy16HotKeyControllerC7setupUI33_D55B00154F4922155B5D3E54789A2010LLyyF
- “$s7RxStudy16HotKeyControllerC11viewDidLoadyyF”
在项目RxStudy中的HotKeyController的viewDidLoad办法中的setupUI办法中,尝试checkInoutAndNativeTypeCheckedBounds
而溃散了。
运用搜索或者AI你能够很简略的查到checkInoutAndNativeTypeCheckedBounds
办法是和数组越界有关的溃散,到此,我觉得KSCrash的Bug盯梢基本契合我的预期。
能够定位到详细页面,而不用去自己符号化,别的也基本上告知了溃散的原因。
当然,我这儿仅仅一个故意的溃散,更多的检测只能经过实在的项目去检测。
怎么上传溃散日志
咱们假如细心看,能够看到上面的代码中有这样一个办法installation.sendAllReports
,这个办法会在有溃散的情况下,自动进行日志的上传,咱们能够追着这个函数,找到其详细完成:
- (void) filterReports:(NSArray*) reports
onCompletion:(KSCrashReportFilterCompletion) onCompletion
{
NSError* error = nil;
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:self.url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:15];
KSHTTPMultipartPostBody* body = [KSHTTPMultipartPostBody body];
NSData* jsonData = [KSJSONCodec encode:reports
options:KSJSONEncodeOptionSorted
error:&error];
if(jsonData == nil)
{
kscrash_callCompletion(onCompletion, reports, NO, error);
return;
}
[body appendData:jsonData
name:@"reports"
contentType:@"application/json"
filename:@"reports.json"];
// TODO: Disabled gzip compression until support is added server side,
// and I've fixed a bug in appendUTF8String.
// [body appendUTF8String:@"json"
// name:@"encoding"
// contentType:@"string"
// filename:nil];
request.HTTPMethod = @"POST";
request.HTTPBody = [body data];
[request setValue:body.contentType forHTTPHeaderField:@"Content-Type"];
[request setValue:@"KSCrashReporter" forHTTPHeaderField:@"User-Agent"];
// [request setHTTPBody:[[body data] gzippedWithError:nil]];
// [request setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
self.reachableOperation = [KSReachableOperationKSCrash operationWithHost:[self.url host] allowWWAN:YES
block:^
{
[[KSHTTPRequestSender sender] sendRequest:request
onSuccess:^(__unused NSHTTPURLResponse* response, __unused NSData* data)
{
kscrash_callCompletion(onCompletion, reports, YES, nil);
} onFailure:^(NSHTTPURLResponse* response, NSData* data)
{
NSString* text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
kscrash_callCompletion(onCompletion, reports, NO,
[NSError errorWithDomain:[[self class] description]
code:response.statusCode
userInfo:[NSDictionary dictionaryWithObject:text
forKey:NSLocalizedDescriptionKey]
]);
} onError:^(NSError* error2)
{
kscrash_callCompletion(onCompletion, reports, NO, error2);
}];
}];
}
这是一段OC代码,尽管看着有点长,可是其实本质上便是经过原生的办法进行网络恳求,然后就上传了。
不过这办法有个十分十分严峻的问题,那便是无法对网络恳求的恳求头和恳求参数进行自主配置!
比如我想上传溃散日志的时分,带上cid,抱愧,办不到!
乃至,是我的项目中,规则需求在恳求头带特定的参数才行,无法自定义配置,就连网络恳求都会被回绝。
看来经过对KSCrashInstallationStandard
单例来配置url进行网络恳求,并不是特别好的方案。
绕了一圈,溃散日志能够抓取到,可是无法经过现有的API进行上传,该怎么进行优化呢?
此路不通,咱们换条路逛逛
假如我能拿到KSCrash的溃散数据,自己按照自己的业务线逻辑走网络恳求不就好了?
所以我就去翻了翻KSCrash的API,其实十分简略,在KSCrash.h
就有啦:
/** Get all unsent report IDs.
*
* @return An array with report IDs.
*/
- (NSArray*) reportIDs;
/** Get report.
*
* @param reportID An ID of report.
*
* @return A dictionary with report fields. See KSCrashReportFields.h for available fields.
*/
- (NSDictionary*) reportWithID:(NSNumber*) reportID;
获取溃散日志的reportIDs,经过reportID拿到一个NSDictionary的溃散报告。
考虑到我运用Moya进行上传操作,操作就简略了。
我写了一个KCrash分类,直接拿到一切溃散日志的数据:
import KSCrash
extension KSCrash {
func getCrashData() -> Data? {
let array = KSCrash.sharedInstance().reportIDs()
if let ids = array as? [NSNumber],
ids.isNotEmpty {
let jsons = ids.map {
var json = KSCrash.sharedInstance().report(withID: $0)
json?["binary_images"] = nil
return json
}
return try? KSJSONCodec.encode(jsons, options: KSJSONEncodeOption(rawValue: 2))
} else {
return nil
}
}
}
为了简练,我全部都是用的KSCrash里边的转化办法,仅仅经过Swift言语,经过高阶函数,一步把[reportID]
转成[Dictionary]
最终转成Data?
。
这样一来,当Data?
不为nil的时分,我就经过自己的网络恳求去上传溃散日志即可。
一起网络恳求成功后,我就上传成功的日志整理掉就能够了,调用办法KSCrash.sharedInstance().deleteAllReports()
即可。
总结
这篇文章,是对KSCrash在自己项目中的一点运用心得,从集成、验证、查阅源码和简略改造。
在阅读KSCrash源码的过程中,我真的觉得这代码写的不简略,C、C++、OC都用上了。尽管年代久远了,不过仍是有一些学习学习意义。一起我表示看不懂
参阅文档
APM – iOS Crash监控 KSCrash代码解析
自己写的项目,欢迎咱们star⭐️
RxStudy:RxSwift/RxCocoa结构,MVVM形式编写wanandroid客户端。
GetXStudy:运用GetX,重构了Flutter wanandroid客户端。