提交 43ca6ea5 编写于 作者: Z zengyawen

update docs

Signed-off-by: Nzengyawen <zengyawen1@huawei.com>
上级 53b2ea3e
......@@ -3,6 +3,10 @@
- [应用事件打点开发指导](hiappevent-guidelines.md)
- [性能打点跟踪开发指导](hitracemeter-guidelines.md)
- [分布式跟踪开发指导](hitracechain-guidelines.md)
- [HiLog开发指导(Native)](hilog-guidelines.md)
- 错误管理
- [错误管理开发指导](errormanager-guidelines.md)
- [应用恢复开发指导](apprecovery-guidelines.md)
\ No newline at end of file
- [应用恢复开发指导](apprecovery-guidelines.md)
- 日志分析
- [应用无响应(appfreeze)日志分析指导](appfreeze-guidelines.md)
- [进程崩溃(cppcrash)日志分析指导](cppcrash-guidelines.md)
\ No newline at end of file
# 应用无响应(appfreeze)日志分析指导
## 简介
应用在用户使用的时候会出现点击没有反应、应用没有响应等情况,其超过一定时间后即被定义为应用无响应(appfreeze)。OpenHarmony提供了检测应用无响应的机制,并生成appfreeze日志供应用开发分析使用。
**说明1:** 本指导仅适用于Stage模型下的应用使用。
**说明2:** 在使用本指导分析日志前,需要开发者对JS在OpenHarmony中运行情况、C++程序堆栈信息有相关基础知识,并对应用相关的子系统有一定了解。
## 获取方式
应用无响应日志是一种故障日志,与Native进程崩溃、JS应用崩溃、系统进程异常等都归类到FaultLog下面,可以通过如下三个方式获取日志:
### 通过shell获取日志
在设备/data/log/faultlog/faultlogger/下面,以appfreeze-开头的日志。
日志格文件名格式是appfreeze-应用包名-应用UID-秒级时间
![appfreeze_20230308145160](figures/appfreeze_20230308145160.png)
### 通过DevEco Studio获取日志
DevEco Studio会收集设备的故障日志到FaultLog下面:
DevEco Studio会将包名和故障和时间都分类好显示
![appfreeze_20230308145161](figures/appfreeze_20230308145161.png)
### 通过faultlogger接口获取
faultlogger对外提供了故障查询接口,可以查询各种故障信息,详见[@ohos.faultLogger (故障日志获取)](../reference/apis/js-apis-faultLogger.md)
## 应用无响应检测能力点
目前应用无响应检测从以下维度检测,应用开发者了解其原理对定位和分析appfreeze故障非常有帮助。
| 故障类型 | 说明 |
| -------- | -------- |
| THREAD_BLOCK_6S | 应用主线程卡死超时 |
| APPLICATION_BLOCK_INPUT | 用户输入响应超时 |
| LIFECYCLE_TIMEOUT | Ability生命周期切换超时 |
| APP_LIFECYCLE_TIMEOUT | App生命周期切换超时 |
### THREAD_BLOCK_6S 应用主线程卡死超时
该故障出现是表示当前应用主线程有卡死或者执行任务过多的情况,影响任务执行的流畅度和体验。
该事件的检测原理是:是应用的watchdog线程定期去向主线程插入判活检测并在自己线程插入超时上报机制,当超过3s判活检测没有被执行会上THREAD_BLOCK_3S警告事件,超过6s依然没有被执行就会上报THREAD_BLOCK_6S主线程卡死事件,两个事件匹配生成THREAD_BLOCK的应用无响应日志。原理如图所示:
![appfreeze_20230308145163](figures/appfreeze_20230308145163.png)
### APPLICATION_BLOCK_INPUT 用户输入响应超时
该故障出现会影响了用户体验,用户的点击事件超过10s没有得到响应。
该事件的检测原理是:用户点击应用的某些按钮,输入系统向应用侧发送点击事件,但是超过10s没有收到应用侧的响应反馈回执,上报该故障。如下图所示:
![appfreeze_20230308145162](figures/appfreeze_20230308145162.png)
### 生命周期切换超时
生命周期切换超时分为Ability生命周期切换超时和App生命周期切换超时,对应两个不同的日志LIFECYCLE_TIMEOUT和APP_LIFECYCLE_TIMEOUT。
该故障出现在生命周期切换的过程中,影响当前应用内Ability的切换或者不同APP之间的切换。
该事件的检测原理是:通过获取不同生命周期切换的过程,在生命周期切换开始的位置向watchdog线程插入超时任务,在生命周期切换完成之后移除超时任务,超过时间将上报故障。
![appfreeze_20230308145164](figures/appfreeze_20230308145164.png)
不同的生命周期,超时的时间不一样:
| 生命周期 | 超时时间 |
| -------- | -------- |
| Load | 10s |
| Terminate | 10s |
| Connect | 3s |
| Disconnect | 0.5s |
| Foreground | 5s |
| Background | 3s |
## 应用无响应日志分析
应用无响应(appfreeze)故障需要结合应用无响应日志和流水hilog日志一起分析。
当前示例仅提供一个分析方法,请开发者根据具体问题具体分析。
应用无响应日志主要分以下几个模块信息:
### 日志头部信息
| 字段 | 说明 |
| -------- | -------- |
| Reason | 应用无响应原因,与[应用无响应检测能力点](#应用无响应检测能力点)对应 |
| PID | 发生故障时候的pid,可以用于在流水日志中搜索相关进程信息 |
| PACKAGE_NAME | 应用进程包名 |
![appfreeze_20230310105865](figures/appfreeze_20230310105865.png)
### 日志主干通用信息
全部日志都包括以下几部分信息,可以通过搜索“主要信息字段”在日志中找到对应的位置:
| 主要信息字段 | 说明 |
| -------- | -------- |
| EVENTNAME | 应用无响应原因或者组成卡死检测的不同事件 |
| TIMESTAMP | 发生故障时上报时刻的事件,可以根据[应用无响应检测能力点](#应用无响应检测能力点)中说明的超时时间,在相应流水日志中缩小查看日志的时间范围 |
| PID | 发生故障时候的pid,可以与发生时间和超时时间配合用于在流水日志中搜索相关进程信息 |
| PACKAGE_NAME | 应用进程包名 |
| MSG | 发生故障时dump信息或者说明信息,后面具体说明 |
| OpenStacktraceCatcher | 当前进程堆栈信息 |
| BinderCatcher | 进程与其他系统进程间通信的调用信息,显示调用等待时间长的情况 |
| PeerBinder Stacktrace | 跟当前进程相关的对端进程有卡死,会抓取对端的进程堆栈 |
| cpuusage | 跟当前时间段整机CPU使用情况 |
| memory | 跟当前时间当前进程的内存使用情况 |
OpenStacktraceCatcher当前进程堆栈示例:
示例堆栈表面窗口通过IPC向下发送事件的时候没有调用下去,停留在IPC通信阶段
![appfreeze_20230310105869](figures/appfreeze_20230310105869.png)
BinderCatcher信息示例:
示例表面当前1561进程向685进程请求通信,等待了超过10s没有得到响应。
![appfreeze_20230310105868](figures/appfreeze_20230310105868.png)
PeerBinder Stacktrace信息示例:
示例展示对端卡死进程685的堆栈信息
![appfreeze_20230310105870](figures/appfreeze_20230310105870.png)
cpuusage信息示例:
整机CPU信息
![appfreeze_20230310105871](figures/appfreeze_20230310105871.png)
memory信息示例:
当前进程内存信息
![appfreeze_20230310105872](figures/appfreeze_20230310105872.png)
### 日志主干特异性信息(应用主线程卡死超时)
Reason是THREAD_BLOCK_6S的日志根据前面的[应用主线程卡死超时](#thread_block_6s-应用主线程卡死超时)的原理可知,有两部分组成,一部分是THREAD_BLOCK_3S,一部分是THREAD_BLOCK_6S。将两部分日志对比分析能更准确的获得是卡死还是执行任务过多响应不过来的情况。
THREAD_BLOCK_3S在日志的前部分,THREAD_BLOCK_6S在THREAD_BLOCK_3S后面写入。可以通过EVENTNAME字段来搜索两个事件在日志中的位置。
两个事件中都包含MSG字段,该字段在应用主线程卡死超时故障中写入了当前主线程处理队列的信息,可以查看在两个时间点中主线程事件处理队列排队情况。
示例日志显示了在Low priority的队列中05:06:18.145的事件一直在处理,THREAD_BLOCK_3S和THREAD_BLOCK_6S的队列都显示其存在。说明主线程卡死不是任务过多情况。
由于THREAD_BLOCK_6S是主线程卡死,进程堆栈信息只需要关注主线程的堆栈(主线程线程号跟进程号相同)。当前示例日志主线程堆栈显示通过ArkUI控件到JS运行,说明卡死在Js代码中。3S和6S都是这个位置的堆栈,说明JS有卡死不是任务过多。
THREAD_BLOCK_3S:
![appfreeze_20230310105866](figures/appfreeze_20230310105866.png)
THREAD_BLOCK_6S:
![appfreeze_20230310105867](figures/appfreeze_20230310105867.png)
然后再可以结合流水日志看一下当前应用侧是在执行哪块代码。
一般情况下可以看一下上面[通用日志信息](#日志主干通用信息)内容,是不是有对端通信卡死,是不是整机CPU消耗很高导致当前应用响应不过来,是不是有内存泄漏,内存非常多导致任务非常重跑不起来。
### 日志主干特异性信息(用户输入响应超时)
Reason是APPLICATION_BLOCK_INPUT,表明用户点击事件超过10s没有的到反馈。
MSG信息是这个事件的说明:用户的输入没有得到响应。
APPLICATION_BLOCK_INPUT的日志信息可以参考[通用日志信息](#日志主干通用信息)进行分析。需要特别说明的是,一般情况下用户输入无响应大概率主线程也会卡死。可以结合两个日志的三个堆栈、两个BinderCatcher信息,进行对比查看。如果没有主线程卡死的日志,说明有可能在输入事件之前有大量的细碎的其他事件,细碎的事件不足以卡死主线程,但是数量比较多导致用户的输入事件响应不过来。
### 日志主干特异性信息(生命周期切换超时)
Reason分两个一个是LIFECYCLE_TIMEOUT,一个是APP_LIFECYCLE_TIMEOUT。
LIFECYCLE_TIMEOUT是Ability级别的生命周期切换超时,APP_LIFECYCLE_TIMEOUT表示APP级别的生命周期切换超时。
MSG说明当前是什么生命周期的超时。
示例可以看出,LIFECYCLE_TIMEOUT的可以看出Ability在切换后台的时候超时,APP_LIFECYCLE_TIMEOUT的可以看出APP的终止阶段超时,可以按照上面[生命周期切换超时](#生命周期切换超时)的超时时间来找流水日志等信息。
LIFECYCLE_TIMEOUT:
![appfreeze_20230310105873](figures/appfreeze_20230310105873.png)
APP_LIFECYCLE_TIMEOUT:
![appfreeze_20230310105874](figures/appfreeze_20230310105874.png)
其他的日志信息可以参考[通用日志信息](#日志主干通用信息)进行分析。需要特别说明的是,一般情况下生命周期切换大概率主线程也会卡死。可以结合两个日志的三个堆栈、两个BinderCatcher信息,进行对比查看。
# 进程崩溃(cppcrash)日志分析指导
## 简介
进程崩溃指C/C++运行时崩溃。OpenHarmony FaultLogger模块提供的进程崩溃故障检测、日志采集、日志存储、日志上报能力,为开发者提供了详细的维测日志用以辅助故障定位。
本文将分别介绍进程崩溃检测能力、崩溃日志获取和进程崩溃日志分析。在使用本指导分析日志前,需要开发者对C/C++程序堆栈信息有相关基础知识。
## 进程崩溃检测能力
进程崩溃基于Linux信号机制,目前主要支持对以下C/C++运行时崩溃异常信号的处理:
| 信号值 | 信号 | 解释 | 触发原因 |
| ------ | --------- | --------------- | ------------------------------------------- |
| 4 | SIGILL | 非法指令 | 进程执行了非法、格式错误、未知或特权指令 |
| 5 | SIGTRAP | 断点或陷阱异常 | 异常或trap指令发生 |
| 6 | SIGABRT | 进程终止 | 进程异常终止,通常为进程自身调用标准函数库的`abort()`函数 |
| 7 | SIGBUS | 非法内存访问 | 进程访问了对齐或者不存在的物理地址 |
| 8 | SIGFPE | 浮点异常 | 进程执行了错误的算术运算,如除数为0、浮点溢出、整数溢出等 |
| 11 | SIGSEGV | 无效内存访问 | 进程访问了无效内存引用 |
| 16 | SIGSTKFLT | 栈错误 | 处理器执行了错误的栈操作,如栈空时弹出、栈满时压入 |
| 31 | SIGSYS | 错误的系统调用 | 系统调用时使用了错误或非法参数 |
## 崩溃日志获取
进程崩溃日志是一种故障日志,与应用无响应日志、JS应用崩溃等都由FaultLogger模块进行管理,可以通过如下三个方式获取:
### 通过shell获取日志
1. 设备`/data/log/faultlog/faultlogger/`路径下的故障日志,其文件名格式为`cppcrash-进程名-进程UID-秒级时间`,仅包含设备名、系统版本、进程崩溃调用栈等信息。
![cppcrash-faultlogger-log](figures/20230407112159.png)
2. 设备`/data/log/faultlog/temp/`路径下的故障日志,其文件名格式为`cppcrash-进程PID-系统毫秒级时间戳`,还包含进程崩溃时栈内存、进程maps等信息。
![cppcrash-temp-log](figures/20230407111853.png)
### 通过DevEco Studio获取日志
DevEco Studio会收集设备`/data/log/faultlog/faultlogger/`路径下的进程崩溃故障日志到FaultLog下面,根据进程名和故障和时间分类显示。
![DevEco Studio cppcrash](figures/20230407112620.png)
### 通过faultlogger接口获取
FaultLogger对外提供了故障查询接口,可以查询各种故障信息,详见[@ohos.faultLogger (故障日志获取)](../reference/apis/js-apis-faultLogger.md)
## 进程崩溃日志分析
### 日志格式
以下是一份DevEco Studio归档在FaultLog的进程崩溃日志的核心内容。
```
Generated by HiviewDFX@OpenHarmony
==================================================================
Device info:OpenHarmony 3.2 <- 设备信息
Build info:OpenHarmony 4.0.5.5 <- 版本信息
Module name:crasher_c <- 模块名
Pid:1205 <- 进程号
Uid:0 <- 用户ID
Reason:Signal:SIGSEGV(SEGV_ACCERR)@0x0042d33d <- 异常信息
Thread name:crasher <- 异常线程名
#00 pc 0000332c /data/crasher(TriggerSegmentFaultException+15)(8bc37ceb8d6169e919d178fdc7f5449e) <- 调用栈
#01 pc 000035c7 /data/crasher(ParseAndDoCrash+277)(8bc37ceb8d6169e919d178fdc7f5449e)
#02 pc 00003689 /data/crasher(main+39)(8bc37ceb8d6169e919d178fdc7f5449e)
#03 pc 000c3b08 /system/lib/ld-musl-arm.so.1(__libc_start_main+116)
#04 pc 000032f8 /data/crasher(_start_c+112)(8bc37ceb8d6169e919d178fdc7f5449e)
#05 pc 00003284 /data/crasher(_start+32)(8bc37ceb8d6169e919d178fdc7f5449e)
...
```
### 通过日志定位问题
1. 通过故障日志等基础信息确定问题模块和故障类别
通过崩溃进程名一般能定界到故障的模块,通过信号能判断崩溃的原因,通过堆栈中的方法名,可以复原崩溃栈的函数调用链。\
如范例中的SIGSEGV是由Linux内核抛出,原因为访问了非法内存地址,问题发生在TriggerSegmentFaultException这个函数中。\
大部分场景下崩溃栈的最上层就是崩溃的原因,如空指针访问以及程序主动终止运行。\
少部分场景调用栈无法定位原因,需要查看其他信息,例如踩内存或者栈溢出的问题场景。
2. 通过addr2line工具解析出代码行号来复原崩溃现场调用栈
使用Linux addr2line工具解析崩溃栈的行号,这里需要使用带调试信息的二进制。一般在版本编译或者应用编译时会生成带调试信息的二进制。
应用二进制位置在DevEco Studio应用构建的临时目录中,如`build/default/intermediates/libs`。
系统二进制位置在如下目录,对于直接获取的版本,二进制会归档在完整镜像包中。
```
\代码根路径\out\产品\lib.unstripped
\代码根路径\out\产品\exe.unstripped
```
Linux环境下,开发者可以通过`apt-get install addr2line`命令安装addr2line软件来使用。\
在应用开发环境下,开发者还可以使用SDK中归档的llvm-addr2line工具来解析行号,使用方法一致。
使用addr2line工具根据偏移地址解析行号:
```
root:~/OpenHarmony/out/rk3568/exe.unstripped/hiviewdfx/faultloggerd$ addr2line -e crasher 0000332c
base/hiviewdfx/faultloggerd/tools/crasher/dfx_crasher.c:57
```
范例中的崩溃故障是由赋值给一块不可写的区域导致的,代码行为dfx_crasher.c文件的57行,修改后可以避免发生此崩溃。\
另外,使用addr2line后,如果得出的行号看起来不是很正确,可以考虑对地址进行微调(如减1),或者考虑关闭一些编译优化,已知使用LTO的二进制可能无法正确获得行号。
# HiLog开发指导
## 概述
HiLog是OpenHarmony日志系统,提供给系统框架、服务、以及应用打印日志,记录用户操作、系统运行状态等。
> **说明:**
> 仅当开发者使用Native API开发应用时,可参考本开发指导。API文档请参考[HiLog Native API参考](../reference/native-apis/_hi_log.md)
## 接口说明
| 方法/宏 | 接口描述 |
| -------- | -------- |
| int OH_LOG_Print(LogType type, LogLevel level, unsigned int domain, const char *tag, const char *fmt, ...) | 输出指定日志类型、日志级别、业务领域、Tag的hilog日志,并且按照printf格式类型和隐私指示确定需要输出的变参。<br/>输入参数:见参数解析。<br/>输出参数:&nbsp;<br/>返回值:打印成功则返回日志总字节数;失败则返回-1。|
| #define OH_LOG_DEBUG(type, ...) ((void)OH_LOG_Print((type), LOG_DEBUG, LOG_DOMAIN, LOG_TAG, \__VA_ARGS__))| DEBUG级别写日志,宏封装接口 |
| #define OH_LOG_INFO(type, ...) ((void)OH_LOG_Print((type), LOG_INFO, LOG_DOMAIN, LOG_TAG, \__VA_ARGS__)) | INFO级别写日志,宏封装接口 |
| #define OH_LOG_WARN(type, ...) ((void)OH_LOG_Print((type), LOG_WARN, LOG_DOMAIN, LOG_TAG, \__VA_ARGS__)) | WARN级别写日志,宏封装接口 |
| #define OH_LOG_ERROR(type, ...) ((void)OH_LOG_Print((type), LOG_ERROR, LOG_DOMAIN, LOG_TAG, \__VA_ARGS__)) | ERROR级别写日志,宏封装接口 |
| #define OH_LOG_FATAL(type, ...) ((void)OH_LOG_Print((type), LOG_FATAL, LOG_DOMAIN, LOG_TAG, \__VA_ARGS__)) | FATAL级别写日志,宏封装接口 |
| bool OH_LOG_IsLoggable(unsigned int domain, const char *tag, LogLevel level) | 功能:检查指定业务领域、TAG、级别的日志是否可以打印。<br/>输入参数:见参数解析。<br/>输出参数:无<br/>返回值:如果指定domain、tag、level日志可以打印则返回true;否则返回false。 |
## 参数解析
| 参数名 | 类型 | 必填 | 说明 |
| ------ | ------ | ---- | ------------------------------------------------------------ |
| type | enum | 是 | 日志打印类型枚举,应用日志默认为LOG_APP。 |
| level | enum | 是 | 日志打印等级枚举,见LogLevel。 |
| domain | number | 是 | 日志对应的领域标识,范围是0x0~0xFFFF。<br/>建议开发者在应用内根据需要自定义划分。 |
| tag | string | 是 | 指定日志标识,可以为任意字符串,建议用于标识调用所在的类或者业务行为。 |
| fmt | string | 是 | 格式字符串,用于日志的格式化输出。格式字符串中可以设置多个参数,参数需要包含参数类型、隐私标识。<br/>隐私标识分为{public}和{private},缺省为{private}。标识{public}的内容明文输出,标识{private}的内容以\<private>过滤回显。 |
| args | any[] | 是 | 与格式字符串format对应的可变长度参数列表。参数数目、参数类型必须与格式字符串中的标识一一对应。 |
## LogLevel
日志级别。
| 名称 | 值 | 说明 |
| ----- | ------ | ------------------------------------------------------------ |
| DEBUG | 3 | 详细的流程记录,通过该级别的日志可以更详细地分析业务流程和定位分析问题。 |
| INFO | 4 | 用于记录业务关键流程节点,可以还原业务的主要运行过程;<br/>用于记录可预料的非正常情况信息,如无网络信号、登录失败等。<br/>这些日志都应该由该业务内处于支配地位的模块来记录,避免在多个被调用的模块或低级函数中重复记录。 |
| WARN | 5 | 用于记录较为严重的非预期情况,但是对用户影响不大,应用可以自动恢复或通过简单的操作就可以恢复的问题。 |
| ERROR | 6 | 应用发生了错误,该错误会影响功能的正常运行或用户的正常使用,可以恢复但恢复代价较高,如重置数据等。 |
| FATAL | 7 | 重大致命异常,表明应用即将崩溃,故障无法恢复。
## 开发示例
1.在CMakeLists.txt中新增libhilog_ndk.z.so链接:
```
target_link_libraries(entry PUBLIC libhilog_ndk.z.so)
```
2.在源文件中包含hilog头文件, 并定义domain、tag宏:
```c++
#include "hilog/log.h"
```
```c++
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x3200 // 全局domain宏,标识业务领域
#define LOG_TAG "MY_TAG" // 全局tag宏,标识模块日志tag
```
3.打印日志,以打印ERROR级别的日志为例:
```c++
OH_LOG_ERROR(LOG_APP, "Failed to visit %{private}s, reason:%{public}d.", url, errno);
```
4.输出结果:
```
12-11 12:21:47.579 2695 2695 E A03200/MY_TAG: Failed to visit <private>, reason:11.
```
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册