前言

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