闲鱼如何建设技术舆情治理体系 (多图多代码)

avatar
@阿里巴巴集团

作者:闲鱼技术——云从

现状和问题

线上质量情况

闲鱼的舆情治理,依托阿里集团的设施建设,有以下能力:

•崩溃异常、性能在线聚合查询;•本地日志:TLog;•在线日志:埋点日志(t+1)和用户行为日志(路径和请求)

但在应对舆情治理方面仍存在较多的问题:

有相当一部分闪退、黑屏、卡死舆情无 Crash 或 ANR 日志;

技术舆情中的业务问题定位困难;

•业务问题如图片视频上传失败、内容显示异常、无法退出、服务器错误等;•日志内容缺失:大部分业务日志被写入控制台,而不是本地日志或在线日志;•个别重要业务使用埋点日志,但仍可能存在内容不足和 t+1 不及时问题。

本地日志定位问题能力有限

•本地 TLog 日志有较为详细的集团二方 SDK 日志,但缺失设备基础信息、业务日志、用户行为日志等•未知异常问题,如视频绿屏等,无控制台 logcat 日志•日志规范不完善,日志查看效率低

本地日志回捞困难

•用户主动反馈时,没有触发本地日志上报•闲鱼属于交易 App,用户不会长时间在线,在线命令推送成功率低•命令推送在后台没有缓存机制

反馈问题和线上实际情况存在偏差

•闲鱼反馈入口较深,相比闲鱼上亿用户基数,反馈占比低,存在线上有问题但无反馈的情况

舆情治理方案整体设计

基于现状问题,重新梳理和补充的舆情治理体系如下图:

舆情日志

线上舆情问题治理体系

下面会重点展开讲述的内容:

•如何提升本地日志定位能力•补充线上卡顿监测能力•补充主动发现问题能力

提升本地日志定位能力

本地控制台日志补充

治理前期,大量业务日志进入了控制台日志,即便治理后将大量日志转入 tlog 日志,仍有部分日志流入控制台,如 Android Maven 引入独立模块和 Flutter 插件包模块。此外,部分未知异常问题,如视频绿屏、黑屏等,logcat 日志也提供了定位的可能性。

Android Log 模块提供了多种日志缓存类型,见 Android logging[1],可通过 logcat 命令获取对应类型的日志。这里,我们在用户反馈的时候,分别将 LOG_ID_MAINLOG_ID_EVENTSLOG_ID_CRASH 类型 logcat 读取并写入到本地文件,而后将 logcat 日志同 tlog 文件一起打包通过 AUS 上传至 OSS。

// LOG_ID_MAIN 主应用程序log,-t 设置日志上限 20000
adb logcat -d -v threadtime -t 20000

// LOG_ID_EVENTS 系统事件信息
adb logcat -d -b events -v threadtime -t 6666

// LOG_ID_CRASH 应用程序 crash 日志
adb logcat -d -b crash -v threadtime -t 6666

logcat日志

logcat.txt 内容

本地日志回捞能力

前面提到,由于在线命令推送成功率低且后台无命令缓存机制,所以闲鱼 App 本地日志回捞困难。为解决日志难以获取问题,舆情治理体系中设计了多种日志回捞策略,见下图所示。

本地日志回捞策略

本地日志回捞策略

本地日志下载查看流程

本地日志下载查看流程

•为提升用户主动反馈触发的日志上传成功率,使用 AUS 上传日志打包文件至 OSS 平台,同时将 url 写入反馈内容(打包和上传若超出5秒则无法写入反馈内容)和阿里云 SLS 实时日志平台;•为提升研发下载本地日志的效率,使用 TLog SDK 上传至 TLog 平台,其中 50% 以上能上传成功。

线上卡顿/ANR检测能力

线上用户反馈 ANR 并给出截图证明,因为没有卡顿日志,难以定位问题。

用户反馈卡死

线上用户反馈黑屏,无有效日志难以定位问题

页面卡死的另一种表现

技术方案现状

在闲鱼 App 的混合工程场景,依托 Emas 平台已实现 iOS 端卡顿检测,Flutter 端卡顿检测方案查看 Flutter卡顿问题的监控与思考[2],这节主要讲述 Android 端线上卡顿/ANR 检测。

在线下场景,Android 端卡顿/ANR 检测手段已经很成熟,普通卡顿检测方案有 BlockCanary[3],ANR 检测可通过 adb bugreport 查看 traces.txt 文件得到。然而在线上环境,以上卡顿检测方案就存在一定问题。

traces.txt 文件权限问题

在线上场景,为了监听识别是否发生 ANR 以及读取 ANR 内容,APP 需要监听 traces.txt 文件变化,并尝试读取 traces.txt 文件内容。监听文件的方案,在 Android 6.0 及以后存在权限问题,大部分场景无法检测到 ANR

•无法访问 /data/anr/traces.txt 文件•无法读取有效系统属性 dalvik.vm.stack-trace-file

如以下代码执行在红米手机 Android 11 上运行,mSystemTraceFilePath 为 "",mSystemTraceFile 的路径为 "/"

File mSystemTraceFile;

...
this.mSystemTraceFilePath = "/data/anr/traces.txt";
this.mSystemTraceFile = new File(this.mSystemTraceFilePath);
if (!this.mSystemTraceFile.exists()) {
    String propSystemTraceFilePath = SystemPropertiesUtils.get("dalvik.vm.stack-trace-file");
    ...
    this.mSystemTraceFile = new File(propSystemTraceFilePath);
    ...
}
...

BlockCanary 检测原理和性能问题

核心原理

BlockCanary

BlockCanary 检测 500ms 卡顿

BlockCanary 的核心原理是,设置 UI 线程的 Looper.mLogging 字段,主线程每次处理消息均会执行 print 方法。

public void start() {
    if (!mMonitorStarted) {
        mMonitorStarted = true;
        Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
    }
}

BlockCanary.java[4]

public void setMessageLogging(@Nullable Printer printer) {
    mLogging = printer;
}

public static void loop() {
    ...
    for (;;) {
        ...
        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        ...
    }
}

Looper.java

在 print 方法中时,触发 StackSampler 工作,即取消上一次异步线程延迟任务,重新触发一次延迟任务,延迟时间为 BlockThreshold * 0.8f (假设要检测 500ms 以上的卡顿堆栈,则延迟时间为 400ms)。若 UI Looper 任务发生卡顿(>BlockThreshold),则延迟任务被执行,且在卡顿期间的获取主线程堆栈信息,之后在下一次 print 方法被执行的时候,若确认发生卡顿,则可把主线程堆栈信息当做卡顿堆栈记录上报。

@Override
public void println(String x) {
    ...
    if (!mPrintingStarted) {
        mStartTimestamp = System.currentTimeMillis();
        mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
        mPrintingStarted = true;
        startDump();
    } else {
        final long endTime = System.currentTimeMillis();
        mPrintingStarted = false;
        if (isBlock(endTime)) {
            notifyBlockEvent(endTime);
        }
        stopDump();
    }
}

LooperMonitor.java[5]

性能问题

在线上环境,大部分场景下并不会发生卡顿,但卡顿检测 SDK 会一直执行。可以发现 app 每一帧时间(16.6ms),UI Looper 中的任务会执行多次,最终产生大量的无效字符串拼接操作大量小对象碎片。特别的,在线上低端机或者 cpu 负载较高的场景下,app 性能会因此降级,影响用户体感。

logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);

卡顿检测方案

方案设计

针对 traces.txt 读取权限问题,可通过检测主线程 5s 卡顿当做 anr。针对 BlockCanary 线上使用的性能问题,为降低延迟任务取消和触发频率,同时避免字符串对象频繁创建问题,必须放弃 Looper.mLogging 的方案。重新思考为什么可以通过设置 Looper.mLogging 检测卡顿?其满足 2 个条件:

•若主线程方法执行发生卡顿,则 Looper Task 执行时长变长•可通过 Looper.mLogging 监听每个 Task 执行时长

闲鱼线上使用 Android 帧回调代替 Looper Task 方案,同时满足以上 2 个条件:

•若主线程方法执行发生卡顿,则帧回调间隔时长变长•可通过注册帧回调监听每帧时长

此外,为避免 BlockCanary 方案延迟任务触发和取消的频率过高的问题,仅在帧回调处记录时间戳,不再取消延迟任务,但在延迟任务执行时判断是否发生卡顿,同时记录主线程堆栈。

假设 500ms 以上为卡顿,整体方案流程图如下:

无卡顿场景

BlockCanary

卡顿场景

BlockCanary

•仅在频繁触发的帧回调处,记录时间戳,无性能消耗•避免延迟任务触发和取消的频率过高的问题,500ms(卡顿阈值) 最多执行 2 次延迟任务•不再取消延迟任务•在任务执行时,判断当前时间和上一帧时间戳时间差。仅在发生卡顿时,dump 主线程堆栈

定义 5s 以上卡顿为 ANR,为检测 ANR,同样通过 500ms 卡顿检测,并做卡顿堆栈聚合,当连续发生卡顿大于 5s 且堆栈信息不变,则认为发生 ANR。发生 ANR 时,记录当前设备 CPU 负载等信息。

检测效果

闲鱼首页卡片点击事件中故意制造 500ms 和 5s 卡顿查看卡顿检测结果。

// CardView61801.java

private void doOnClick(String redirectUrl, Map<String, String> trackParams) {
    if (null == mCardBean) return;

    try {
        Thread.sleep(500);
        // Thread.sleep(5000);
    } catch (Exception e) {
        e.printStackTrace();
    }
    ...
}

查看检测结果

Sleep 500ms

BlockCanary

Sleep 5s

BlockCanary

小结

以上是闲鱼 Android 线上卡顿检测方案,线上已经运行超过 3 个版本,通过日志可发现用户反馈卡死、无反应、闪退、黑屏等现象都有可能是 ANR 造成。整体方案有以下特点:

线上卡顿检测无性能问题

以 5s 以上卡顿当做 ANR

相比 BlockCanary Looper Task 在 0.8*卡顿阈值 时获取卡顿堆栈,本方案 500ms 卡顿检测有更高概率获取错误堆栈。但通过连续卡顿堆栈比对,可确保 5s 卡顿堆栈的准确性。而 500ms 卡顿通过线上统计提升准确性

•若业务需要,必须提升 500ms 单次卡顿堆栈准确率,可简单修改方案:判断大于 250ms 获取一次卡顿堆栈,2 次连续相同卡顿堆栈且卡顿时长大于 500ms 作为最终卡顿判断

主动发现问题能力

由于闲鱼 App 反馈入口较深,因此相比上亿用户而言,每日技术舆情反馈量占比偏低,以此可知技术舆情反馈量并不能准确反应线上质量情况。为此,我们通过监控埋点构建线上关键舆情问题和基础性能大盘,同时通过监控大盘主动发现线上重点待解决问题,加速线上舆情收敛速度和质量提升,流程图如下所示。

主动发现问题设计图

主动发现问题流程图

监控大盘

主动发现问题监控大盘

主动发现问题大盘示例

业务舆情问题大盘

通过统计一段时间的舆情问题,得到关键舆情问题,以此添加监控埋点并构建线上报表。通过大盘数据得到线上问题发生量和重要归因,通过重点解决排名前几的归因,达到舆情问题快速收敛的目的。

基础问题

除了 Crash、Flutter异常、性能等线上大盘,我们构建了 5s 卡顿监控、网络请求失败、慢请求、错误 toast 等基础监控大盘。

监控问题定位

基于大盘发现了问题,需要获取对应日志来定位问题,但问题对应的用户很可能并不会反馈舆情或反馈内容不是该问题,为此我们构建了实时日志查询以及本地日志批量回捞能力。

实时日志查询平台

实时日志查询平台

自建舆情追踪平台

针对关键监控问题,客户端增加对应的 SLS 实时日志,在自建舆情追踪平台获取用户的在线日志。平台支持用户名和时间查询,同时支持用户行为、用户异常、舆情日志类型的组合查询。

本地日志批量回捞能力

在线日志难以避免日志内容不足的问题,如基础日志、其他关键模块日志等,然而单个用户的本地日志回捞成功很低(原因见上文内容),为此构建舆情日志回捞平台,通过批量回捞的方式确保能获取到舆情问题某个用户的本地全量日志。

输入舆情 IssueName 查询用户 id

舆情日志回捞平台

舆情日志回捞平台

批量回捞结果查询

舆情日志回捞平台

总结和展望

经过治理,整体线上技术类舆情占比从 10.5% 降低至 4.7%;基于主动发现问题和解决重点问题,每日上传图片失败数从 10W+ 次降低至小于 7K+ 次,其他数据不再罗列。

初步建立的闲鱼舆情治理体系如下所示:

日志类型:本地日志和在线日志;

在线日志:实时在线 SLS 日志,用户行为日志,埋点(t+1)日志,高可用 SDK 日志(崩溃异常、白屏、性能);

日志查询:提供日志规范和日志过滤文档提供查询效率;

日志内容:基础日志和业务日志;

基础日志:logcat 日志,卡顿/ANR日志,高可用 SDK 日志(crash、异常等),设备信息、账号信息等;

日志回捞:反馈时主动上传(TLog平台和OSS平台),在线命令回捞(含TLog平台回捞,自建消息通道回捞);

问题发现:用户反馈问题和主动发现问题

反馈入口:普通客服反馈和灰度截屏反馈

特别的,MIUI 系统下的截屏事件可监听广播 miui.intent.TAKE_SCREENSHOT

此外,未来仍有很大的演进空间如下:

日志可视化

日志语义化描述展示

用户行为、内存、CPU、流量等可视化

用户行为和内存曲线

闲鱼用户本地日志手动解析后的内存可视化举例

日志智能解析

•关键日志和历史问题关联形成知识库

关键日志反向回捞用户日志

•类似主动发现问题场景中的回捞能力,可根据配置下发或其他关键在线日志回捞用户日志

日志关联聚合查询

•基于用户和时间,聚合服务端、前端、客户端日志,聚合多种在线和本地日志。

References

[1] Android logging: developer.android.com/ndk/referen…
[2] Flutter卡顿问题的监控与思考: zhuanlan.zhihu.com/p/148985175
[3] BlockCanary: github.com/markzhai/An…
[4] BlockCanary.java: github.com/markzhai/An…
[5] LooperMonitor.java: github.com/markzhai/An…