一、概述
CrashSDK 允许在崩溃前预先增加一些不经常改变的信息,也可以在崩溃时回调获取最新的信息,还可以缓存最近的一些信息(如用户的操作步骤或场景)等等,在崩溃时,CrashSDK 会将这些指定的内容全部写入到崩溃日志中。
本文档主要介绍如何向各类崩溃日志中,增加各 APP 自己的内容,以便更好地分析和解决问题。
一般来说,需要增加自定义内容的场景有如下几类:
APP 特有的比较重要的内容,如对于浏览器而言有当前打开的 url,对于需要登录的 APP 而言有当前登录的帐号等;
特定崩溃问题分析时,在灰度包中加的一些分析问题用的临时信息;
特定问题(并不局限于崩溃问题)生成自定义日志收集更多信息,以便优化。
在阅读本文前,请先阅读CrashSDK 接入步骤。
二、如何向日志中增加内容
1. 增加头信息
头信息一般是数据量比较小,且运行过程中不会改变或者很少情况下会发生改变的信息,如用户ID、某些设置项等。
使用 CrashApi.addHeaderInfo 即可增加头信息:
sCrashApi.addHeaderInfo("userId", getUserId());
sCrashApi.addHeaderInfo("header key", "header value");
头信息的个数没有限制,其将为所有的日志(java, native, unexp, 自定义日志)增加。
2. 注册线程
java 和 native 崩溃日志中,默认只会将崩溃线程的栈及相关信息写到日志中。如果想知道崩溃时其它某些重要的线程(如主线程)的栈等信息,则可以调用 CrashApi.registerThread。
另外,在某些 ROM 中,native 崩溃经常会因栈溢出(其它原因导致的,并不是 APP 自己的代码存在栈溢出),而无法进入 signal 处理,无法生成 native 崩溃日志,但使用 CrashApi.registerThread 为 native 日志注册线程时,CrashSDK 会为该线程做一些特殊处理,该线程出现 native 崩溃时,就不会因栈内存不足而无法生成日志。
sCrashApi.registerThread(LogType.JAVA | LogType.NATIVE, null);
该接口必需在需要注册的线程内部调用,不能由 A 线程注册另一个 B 线程。
3. 增加 dump 文件
java, native, unexp 三类日志,最多可分别指定 10 个文件,在生成崩溃日志时,CrashSDK 会将注册的文件中的内容写到日志中。API 为 CrashApi.addDumpFile。
int logType = LogType.JAVA | LogType.NATIVE | LogType.UNEXP;
DumpFileInfo info = new DumpFileInfo("/path/to/a/file", "myfile", logType);
sCrashApi.addDumpFile(info);
4. 增加回调
在 CrashSDK 初始化完成后,可注册信息回调,在崩溃时,CrashSDK 回调指定接口来获取内容,将获取的内容写入到崩溃日志中。API 为 CrashApi.registerInfoCallback。
public static final String sCallbackTest = "test callback:";
public static void registerCallback() {
int logType = LogType.JAVA | LogType.NATIVE;
sCrashApi.registerInfoCallback(sCallbackTest, logType);
}
private static class CrashClientImpl implements ICrashClient {
@Override
public String onGetCallbackInfo(String category) {
if (sCallbackTest.equals(category)) {
return "any string you want write into log";
}
return "":
}
...
}
5. 增加缓存
CrashSDK 提供了一套缓存机制,可由 APP 不断加入最新的信息,CrashSDK 只会保留最近加入的 n 条信息,在崩溃时,将这些数据写入到崩溃日志中。相关 API 为 CrashApi.createCachedInfo 和 CrashApi.addCachedInfo。
使用如下:
private static final String CACHED_INFO_TEST = "test cached buffer";
public static void testCachedInfo() {
sCrashApi.createCachedInfo(CACHED_INFO_TEST, 10, LogType.JAVA | LogType.NATIVE);
for (int i = 0; i < 15; ++i) {
sCrashApi.addCachedInfo(CACHED_INFO_TEST, "data line " + i);
}
}
三、如何使用 native API
注意:目前 native API 不提供 CrashSDK 初始化及日志开关等接口,仅提供向日志中增加内容的相关接口。
1. 准备工作
请先参考初级接入文档的复制文件说明。
先复制 crashsdk.h 到自己的工程中,再将 crashsdk.h 所在的目录,加到需要使用到 CrashSDK native API 工程的 Android.mk 中的 LOCAL_C_INCLUDES 包含目录;
将当前目标编译 CPU 架构的 libcrashsdk.so 复制到对应的 “libs/$(arch)/“ 目录下,并链接 libcrashsdk.so。
Android.mk 中增加如下:
# 1. 加入包含目录
LOCAL_C_INCLUDES += /the/full/path/that/contains/file/crashsdk_h
# 2. 链接 libcrashsdk.so
LOCAL_SHARED_LIBRARIES += -lcrashsdk
2. 增加信息
native 提供增加信息的 API 与 java 的 API 基本一致,可通过 native API 向 java, native, unexp 日志中增加自定义的内容。
2.1 增加头信息
native 使用 crashsdk_addHeaderInfo 增加头信息,同 java API CrashApi.addHeaderInfo:
crashsdk_addHeaderInfo("native HeaderInfo key", "any string as value");
2.2 注册线程
native 使用 crashsdk_registerThread 注册当前线程,同 java API CrashApi.registerThread:
crashsdk_registerThread(NULL, LogTypeNative | LogTypeJava);
如果当前线程为 native 启动的线程,没有 JNI 环境,则只会为 native 崩溃日志注册当前线程。
2.3 增加 dump 文件
native 使用 crashsdk_addDumpFile 增加文件,同 java API CrashApi.addDumpFile:
DumpFileInfo info;
memset(&info, 0, sizeof(info));
char tmp[32] = { 0 };
snprintf(tmp, 32, "/proc/%d/stack", getpid());
info.infoSize = sizeof(info);
info.category = "linux kernel stack:";
info.fileTobeDump = tmp;
info.logType = LogTypeNative | LogTypeJava | LogTypeUnexp;
info.writeCategory = true;
info.deleteAfterDump = false;
crashsdk_addDumpFile(&info);
2.4 增加回调
native 使用 crashsdk_registerInfoCallback 增加回调,同 java API CrashApi.registerInfoCallback:
const char* testInfoCallbackFunc(const char* category, LogType type, long* dataSize)
{
const int kBufferSize = getpagesize(); // 4KB
// Do not use libc malloc or free here, which may leads crash with the libc
// heap corruption. Use mmap system call to allocate a buffer is recommended.
//char* buffer = (char*)malloc(kBufferSize);
char* buffer = (char*)mmap(NULL, kBufferSize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
int len = snprintf(buffer, kBufferSize,
"enter native callback function.\n"
"category: '%s', type: %d.\n"
"you can add any content as you want.",
category, type);
*dataSize = len;
// REMIND: the buffer can not be unmap-d!
return buffer;
}
// register info callback
crashsdk_registerInfoCallback("test native callback:",
LogTypeNative | LogTypeJava, testInfoCallbackFunc);
2.5 增加缓存
native 使用 crashsdk_createCachedInfo 和 crashsdk_addCachedInfo 创建和增加缓存信息,同 java API CrashApi.createCachedInfo 和 CrashApi.addCachedInfo:
const char* cachedBufferKey = "test native cached buffer:";
crashsdk_createCachedInfo(cachedBufferKey, 10, LogTypeNative | LogTypeJava);
char buffer[256] = { 0 };
for (int i = 0; i < 15; ++i) {
sprintf(buffer, "native data line %d", i);
crashsdk_addCachedInfo(cachedBufferKey, buffer, strlen(buffer));
}
四、自定义日志
上面所介绍的是为 CrashSDK 内置的崩溃日志类型增加自定义内容;CrashSDK 还提供生成自定义日志的功能,这类日志的所有内容完全由生成日志者决定,可为跟进解决某一类特定问题而制定一类自定义日志,也可为收集某些重要的信息而制定另一类自定义日志。
java API CrashApi.generateCustomLog 的使用示例如下:
StringBuffer buffer = new StringBuffer();
buffer.append("Any strings as you want write into the log from java.");
CustomLogInfo info = new CustomLogInfo(buffer, "mytype");
sCrashApi.generateCustomLog(info);
native API crashsdk_generateCustomLog 的使用示例如下:
CustomLogInfo info;
memset(&info, 0, sizeof(info));
const char* datas = "Any string that you want write into the log from native";
info.infoSize = sizeof(info);
info.datas = datas;
info.dataSize = strlen(datas);
info.logType = "mytype";
info.addHeader = true;
info.addFooter = true;
info.addLogcat = true;
info.uploadNow = false;
crashsdk_generateCustomLog(&info);
不过,native API 生成自定义日志所在的线程,必需有 JNI 环境,否则生成自定义日志失败。
五、应用场景示例
1. 头信息
每条头信息的内容一般只有一行,比较简洁。头信息主要适用于各 APP 定义的一些不经常变化且重要的基本信息,如用户唯一标识、当前网络环境,以及一些重要的设置项的值等信息。
2. 注册线程
一般比较重要的线程才建议注册,通常来说如主线程;还有一些干活比较多的线程,建议也注册,尤其是经常会调用到 native 代码的线程,注册后可提高该线程 native 崩溃的捕获率。
3. dump 文件
主要的场景是:某些子模块运行中会生成一些日志文件,在崩溃前将这些文件预先注册到 CrashSDK 中,在崩溃时,CrashSDK 就会将这些文件的内容写到崩溃日志中。
4. 回调
回调的应用范围比较广,基本上可以替代其它向日志中增加内容的 API,通过回调可以向日志里面写入任何内容。 不过,一般建议那些对实时性要求比较高的内容才在回调中获取,因为回调的成功率较其它的几种写入自定义内容的成功率会低一些。
通常的场景有:崩溃时打开了哪些 View、加载了哪些模块、某些易变设置项最新的状态等信息。
5. 缓存
缓存的设计最初是为了记录用户最近的操作、行为等信息,方便对崩溃问题进行重现,或者按场景对崩溃进行归类。当然也可以使用缓存记录某个模块在崩溃前最新的运行状态等信息。
6. 自定义日志
参见文档:CrashSDK 原理介绍。
六、崩溃统计
CrashSDK 在开启 CustomInfo.mEnableStatReport 后,会上报崩溃率相关统计数据到啄木鸟后台;若接入的 APP 自己也需要收集一份崩溃统计数据,可使用 CrashApi.reportCrashStats 获取各类崩溃次数,或使用 CrashApi.getLastExitType 获取上次退出类型等数据。
在适配崩溃统计之前,建议先阅读崩溃统计相关的介绍文档。
1. 获取统计数据
1.1 ExitType
ExitType 中定义了几种退出类型,使用接口 CrashApi.getLastExitType 可获取上次的退出类型,如下:
final int type = sCrashApi.getLastExitType();
final boolean isFgCrash = (type == ExitType.FOREGROUND_CAUGHT_CRASH ||
type == ExitType.FOREGROUND_UNCAUGHT_CRASH);
final boolean isBgCrash = (type == ExitType.BACKGROUND_CAUGHT_CRASH ||
type == ExitType.BACKGROUND_UNCAUGHT_CRASH);
final boolean isCrashed = (type != ExitType.NORMAL_EXIT);
final boolean hasGeneratedLog = (type == ExitType.FOREGROUND_CAUGHT_CRASH ||
type == ExitType.BACKGROUND_CAUGHT_CRASH ||
type == ExitType.UNKNOWN_GROUND_CAUGHT_CRASH);
// TODO: report |type| to your server
服务器端,可使用崩溃类型的 ExitType 数量,除以所有类型 ExitType 的总数量,得到前台崩溃率、后台崩溃率、前台捕获崩溃率、后台捕获崩溃率等。
1.2. CrashStatKey
在调用 CrashApi.reportCrashStats 后,崩溃统计数据会通过回调接口 ICrashClient.onAddCrashStats 逐一回调。
ICrashClient 的实例必需在初始化时传入,另外也可以初始化后使用 CrashApi.registerCallback 注册独立的回调。
APP 中请求报告统计数据:
private void reportCrashStats() {
sCrashApi.reportCrashStats(false);
}
初始化时传入的 ICrashClient 回调实例中处理统计:
@Override
public void onAddCrashStats(String processName, int key, int count) {
Log.i(TAG, "onAddCrashStats, processName: " + processName
+ ", key: " + key + ", count: " + count);
// TODO: report to your server
}
ICrashClient.onAddCrashStats 将在 CrashApi.reportCrashStats 中同步回调。参数 key
为 CrashStatKey 中定义的任意常量,count
为其对应的数量,建议将所有的字段都上报。
统计数据在 CrashSDK 内部有临时缓存,只会缓存上次和本次运行过程中的统计数据,若不上报,可能会丢失。
服务器端,使用总崩溃次数、java崩溃次数、native崩溃次数等,除以 APP 启动次数 APP_START_TIMES,分别得到总崩溃率、java崩溃率、native崩溃率等数据。
2. 自定义统计字段
CrashSDK 可使用接口 CrashApi.addStatInfo 增加自定义统计项。自定义统计项将随着崩溃率统计数据一起上报到啄木鸟后台。具体见接口 CrashApi.addStatInfo 的说明。
(本文完)