如何搜集Crash Reports
在阅读crash report 之前,我们首先要能够搜集crash report。
透过Xcode 收集
如果是在我们自己的装置上发生了crash,要找到crash report 最简单的方法,就是透过Xcode。我们先将装置连接到Mac 上,从Xcode 的选单中,选择Window -> Devices,就会出现一个window。
在这个window 的左方会列出所有目前可用的装置,如果你是在开发Apple Watch App,Apple Watch 不会独立显示,而是会依附在与Apple Watch 配对的iPhone 上。除了连接的iOS 设备外,由于Xcode 也可以用来开发Mac App,所以你自己正在使用的这台Mac 也会被当做是一台专属装置。
在左下方会列出目前所有Xcode 可以使用的iOS 模拟器,如果你觉得平常在Xcode 选单中的iOS 模拟器太多—iPhone 4、5、6 都列出来-实在显得杂乱,或有个需要用到的模拟器之前被关闭了,你也可以在这边管理。
我们选择了指定的iOS 装置后,从画面右方,点选「View Device Logs」按钮。
接下来会跳出另外一个window。这个window 的左方会列出目前所存放的crash report,按照App 名称与时间排列。我们可以就我们所知道的crash 发生时间,找到我们的App 的crash report。
如果在你的团队中有QA 人员,我们建议在QA 人员也使用Mac 电脑,同时安装Xcode,并且在crash 发生的同时,除了crash report 之外,也把console logs 目前所有的内容也剪下来一份。因为在发生crash 的时候,在console 上面同时也会print 出来一些重要资讯。
透过iTunes Connect 收集
产品上线之后,苹果其实也会帮我们收集crash report。从iTunes Connect 的App 资讯页面中,在最下方可以看到一个叫做「Crash Reports」的连结,接下来就会导引到查看crash reports 的页面。
在iTunes Connect 上,苹果会帮我们列出每个主要作业系统版本上的十大crash 原因,如果我们想在推出下一个版本之前,把前一个版本的所有crash 都修一轮,这个功能还算有用。
不过,我们更常遇到的状况是,用户遇到crash之后,直接透过电话等方式向我们的客服反应,我们需要解决的往往不只是某个版本有哪些问题,更需要的是可以在最短的时间内,解决单一用户的问题。
苹果在后来几个版本的Xcode 中,也增加了浏览线上crash 的功能(可以用Window -> Organizer 功能叫出来) ,让浏览crash 变得方便一些,但一样没有找到特定用户crash 的办法。
直接从iOS 装置上浏览crash log
如果crash 是发生在用户的装置上,我们不太有机会可以叫用户安装Xcode—如果不是因为工作的需求,谁会安装这么肥大的软体呢?我们也不太可能直接拿到用户的装置搜集log,毕竟我们在台湾,用户可能会在世界的任何一个角落。很多时候,我们会想办法请用户直接从装置上协助我们取得crash report。
我们可以从系统设定App 找到crash report,不过,不同版本的iOS 中, crash report 存放的位置不太一样。
- iOS 8 之前,放在Settings->General->About->Diagnostics &Usage
- iOS 8 开始,放在Settings->Privacy->Diagnostics &Usage
在这个画面中,点选了Diagnostics & Usage Data 选项,就可以看到这台装置上的crash report 列表。点下去之后,就可以看到crash report 的内容。
在这个画面中,用户其实很难把crash report 拿出来。因为这个画面就只有一个简单的text view 显示crash report,没有任何方便汇出的功能,要不就是请用户想办法全选后另外找一个文字编辑App 贴上,要不就是要请用户拍摄萤幕截图,而当我们收到萤幕截图之后也很麻烦,因为接下来我们要解开crash 的call stack 中的记忆体位置,只有截图,就只能对图片中的文字做肉眼OCR。
透过第三方服务收集
从iTunes Connect上很难掌握单一用户发生的crash,从用户装置上抓log也很麻烦,因此像TestFlight1、 Crashlytics或HockeyApp等厂商就看准了这种需求推出服务,安装这些服务的SDK后,这些服务会帮我们尽可能的收集crash report,在SDK中设定必要的资讯后,我们便可以透过特定的user ID,找到特定用户发生的crash。
像Crashlytics SDK,就可以呼叫+setUserIdentifier:
、+setUserName:
与setUserEmail
2,在HockeyApp SDK中,则是要在App中实作BITHockeyManagerDelegateprotocol,告诉HockeyApp用户ID。
在使用这些服务的时候,他们会告诉我们要上传每个版本的debug symbol,原因是,当我们发行release build 的时候,compiler 会把程式中的Debug 资讯抽掉—不然所有人只要一拿到crash report,就可以轻松知道我们App 是怎么写的,而形成安全性的风险。所以在发生crash 时,crash log 中其实只有发生错误的function/method 的记忆体位置,必须要有debug symbol 档案才能还原。而Crashlytics 或HockeyApp,可以帮我们在server 上就还原记忆体位置,让我们不必手动做这件事。
我们在这边不特别推荐使用哪一家的服务,建议你各自试试看,然后就功能与价格自行比较。
如果我们在App 中用了Google 的Google Analytics 服务,统计App 中每个功能的用量,可以注意到,其实Google 也会帮我们搜集crash report,但由于Google Analytics 并不会还原记忆体,所以Google Analytics搜集的资料,对我们解决问题没有太多帮助,但Google 的报表拿来看长时间crash 的趋势变化倒还顶不错。
这些服务拦截crash report 的原理是,当exception 发生的时候,其实App 会对自己发送一个UNIX signal,原始的signal handler 做的事情就是在console 上print 讯息、产生crash report 并且停止应用程式。其实Objective-C 里头的try...catch 也是透过UNIX signal 实作的,我们不妨想像整个App 其实都做了一个很大的try...catch,只是这个catch 做的事情就只有让App crash。
这些服务要求你在App 启动的时候,也启动他们的服务,目的就在于改变signal handler,让他们可以将crash report 拦截下来,在下一次启动的时候,再找个恰当的时间回传。
如此一来我们可以知道:如果我们的crash 发生在这些服务启动之前,那么这些服务也拦截不到crash report,所以启动这些服务的时机应该要尽可能早。另外,由于这些SDK 也改变了signal handler 的行为,原本一些状况下App 应该要发生crash,当我们加了某些服务的SDK 之后,他们的signal handler 的实作反而会是只收集crash report,但是当App 继续执行;所以,当用户回报某个功能无法使用,其实很有可能是已经发生了exception。
此外,如果是我们的App 内存用量使用太多,导致被系统中断,这种crash report 也没办法搜集到,因为这种crash 是由外部的watch dog 造成的,而不是内部的UNIX signal 驱动的。
1 .后来被苹果并购 ↩
2 .参考 http://support.crashlytics.com/knowledgebase/articles/92521-how-do-i-set-user-information- ↩