iOS:KSCrash在项目中的实践

前言

上周看了一篇掘友的文章——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客户端。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYxMPgQj' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片