Elastic APM 用于 iOS 和 Android 原生应用

Elastic APM 用于原生应用,提供对传出 HTTP 请求和视图加载的自动检测,捕获自定义事件、错误和崩溃,并包含用于数据分析和故障排除的预构建仪表盘。

阅读时长:12分钟
Elastic APM for iOS and Android Native apps

Elastic® APM 用于 iOS 和 Android 原生应用在堆栈版本 v8.12 中普遍可用。Elastic 的 iOSAndroid APM 代理是开源的,并且是在其之上开发的,即分别作为 OpenTelemetry Swift 和 Android SDK/API 的分发版。

移动 APM 解决方案概述

适用于 iOS 和 Android 的 OpenTelemetry SDK/API 支持自动检测 HTTP 请求、手动检测的 API、基于 OpenTelemetry 语义约定的数据模型以及缓冲支持等功能。此外,Elastic APM 代理分发版还支持更简单的初始化过程以及远程配置和基于用户会话的采样等新功能。Elastic 的 iOSAndroid APM 代理作为分发版,根据 Elastic 的标准支持条款和条件进行维护。

Kibana® 中提供了精选的或预构建的仪表盘,用于监控、数据分析和故障排除。下面显示的服务概述视图提供了相关的前端 KPI,例如崩溃率、HTTP 请求、平均应用加载时间等,包括比较视图。

此外,用户流量的地理分布可在国家和地区级别的地图上查看。服务概述仪表盘还显示了吞吐量、延迟、事务失败率等指标的趋势,以及按设备制造商型号、网络连接类型和应用版本划分的流量分布。

下面显示的事务视图突出显示了不同事务组的性能,包括单个事务的端到端分布式跟踪以及与关联跨度、错误和崩溃的链接。此外,用户可以一目了然地查看按设备制造商和型号、应用版本以及操作系统版本划分的流量分布。

位于事务选项卡底部的突出显示的表格视图(如下所示)使查看设备制造商和型号、应用版本等如何影响延迟和崩溃率变得相对容易。

下面显示的错误和崩溃视图可用于分析不同的错误和崩溃组。单个错误或崩溃实例的未符号化(iOS)或模糊处理(Android)堆栈跟踪也可在此视图中查看。

下面显示的服务地图视图提供了端到端服务相互依赖关系的可视化表示,包括任何第三方 API、代理服务器和数据库。

用于在 Kibana 中观察移动前端的全面预构建仪表盘可以洞察错误、崩溃和瓶颈的来源,从而简化生产环境中问题的故障排除。底层的 Elasticsearch® 平台还支持查询原始数据、构建自定义指标和自定义仪表盘、警报、SLO 和异常检测。总而言之,该平台提供了一套全面的工具来加快根本原因分析和修复,从而促进高速度创新。

一些错误场景的调试工作流程演练

接下来,我们将提供一些 iOS 和 Android 原生应用中错误场景的配置详细信息和故障排除工作流程的演练。

场景 1

在此示例中,我们将使用 Apple 的崩溃报告符号化以及面包屑调试异步方法中的崩溃,以推断崩溃的原因。

符号化
在此场景中,用户注意到错误和崩溃选项卡中特定崩溃组的崩溃事件发生次数激增,并决定进一步调查。崩溃选项卡上出现了一个新的崩溃,开发人员按照以下步骤在本地符号化崩溃报告。

  1. 通过 UI 复制崩溃并将其粘贴到以下名称格式的文件中:<AppBinaryName>_<DateTime>。例如,“opbeans-swift_2024-01-18-114211.ips`。

  1. Apple 提供了有关如何在本地自动通过 Xcode 或手动使用命令行符号化此文件的详细说明

面包屑
第一个线程的第二个帧显示崩溃发生在 Worker 实例中。

此实例实际上在许多地方使用,并且由于此函数的异步特性,无法立即确定此调用的来源。但是,我们可以利用 Open Telemetry SDK 的功能为这些崩溃添加更多上下文,然后将这些部分拼凑在一起以找到崩溃的位置。

通过在 Worker 实例周围添加“面包屑”,可以跟踪哪些对 Worker 的调用实际上与该崩溃相关。

示例
在 Worker 类中创建一个日志记录器提供程序作为公共变量,以便于访问,如下所示

在调用 Worker.doWork() 函数的所有位置创建面包屑

这些面包屑中的每一个都将使用相同的事件名称“worker_breadcrumb”,以便可以一致地查询它们,并且将使用“来源”属性进行区分。

在此示例中,Worker.doWork() 函数是从 CustomerRow 结构体(一个执行工作“onTapGesture”的表格行)中调用的。如果要从 CustomerRow 结构体的多个位置调用此方法,还可以为“来源”属性值添加其他区分,例如关联函数(例如,“CustomerRow#onTapGesture”)。

现在应用正在报告这些面包屑,我们可以使用 Discover 来查询它们,如下所示

_注意:_代理发送的事件_名称_在 Elastic Common Schema (ECS) 中转换为事件_操作_,因此请确保查询使用此字段。

  1. 您可以添加过滤器

    event.action: “worker_breadcrumb”
    它显示了由此新面包屑生成的全部事件。

  2. 您还可以看到各种来源:ProductRow、CustomerRow、CartRow 等。

  3. 如果将error.type : crash添加到查询中,则可以在面包屑旁边看到崩溃

时间轴上并排的崩溃和面包屑可能来自完全不同的设备,因此我们需要另一个区分器。对于每个崩溃,我们都有包含与崩溃关联的session.id的元数据,可从元数据选项卡中查看。我们可以使用此session.id进行查询,以确保我们仅查看 Discover 中来自导致崩溃的单个用户会话(即单个设备)的数据。

在 Discover 中,我们现在可以通过面包屑查看单个设备上与崩溃相关的会话事件流,如下所示

看起来崩溃之前的最后一个面包屑来自“CustomerRow”面包屑。现在,这为应用开发人员提供了一个良好的起点,可以开始其根本原因分析或调查。

场景 2

_注意:_此场景需要 Elastic Android 代理版本“0.14.0”或更高版本。

一个 Android 示例应用有一个由两个屏幕组成的表单,这些屏幕是使用两个片段创建的(

FirstPage
SecondPage
)。在第一个屏幕上,应用进行后端 API 调用以获取标识表单提交的密钥。此密钥存储在应用的内存中,并且必须在发送表单的最后一个屏幕上可用;必须将密钥与表单数据一起发送。

问题
我们开始在 Kibana 的错误和崩溃选项卡中看到崩溃事件发生次数激增(空指针异常),这些崩溃似乎总是发生在表单的最后一个屏幕上,当用户点击“完成”按钮时。但是,这并不总是可重现的,因此仅通过查看崩溃的堆栈跟踪本身无法清楚地了解根本原因。如下所示

当我们查看堆栈跟踪中引用的代码时,我们可以看到以下内容

这是发生崩溃的行,因此看起来变量“formId”(一个位于“FirstPage”中的静态字符串)在执行此代码时为 null,导致抛出空指针异常。此变量在后端请求完成以检索 ID 后,在“FirstPage”片段中设置。到达“SecondPage”的唯一途径是通过“FirstPage”。因此,仅凭堆栈跟踪帮助不大,因为页面必须按顺序打开,而第一个页面将始终设置“formId”变量。因此,在“SecondPage”中 formId 为 null 的可能性似乎不大。

查找根本原因
除了查看崩溃的堆栈跟踪外,查看一些补充数据也很有用,这些数据有助于将各个部分拼凑起来,并更全面地了解应用程序在发生崩溃时运行期间发生的其它情况。对于这种情况,我们知道表单 ID 必须来自我们的后端服务,因此我们可以首先排除后端调用是否存在错误。我们通过检查“交易详情”视图中创建 FirstPage 片段(其中执行表单 ID 请求)的跟踪来执行此操作。

“已创建”跨度表示创建第一个片段所需的时间。最上面的一个显示了 Activity 的创建,然后是 NavHostFragment,然后是“FirstScreen”。在其创建后不久,我们看到向我们的后端发出了一个 GET HTTP 请求以检索我们的表单 ID,并且根据跟踪,GET 请求成功了。因此,我们可以排除此问题与后端通信存在问题。

另一种选择可能是查看应用程序中发生崩溃的会话期间发送的日志(我们也可以查看来自应用程序的所有日志,但分析此单个问题将过于繁琐)。为此,我们首先复制其中一个跨度的“session.id”值(任何跨度都可以,因为所有从应用程序发送的数据中都将提供相同的会话 ID,这些数据是在发生崩溃时发送的),该值位于跨度详细信息弹出窗口中。

_ 注意: _ 崩溃元数据中也可以找到相同的会话 ID。

现在我们已经确定了会话,可以打开日志资源管理器视图,并查看同一会话中应用程序的所有日志,如下所示

通过查看日志,并添加一些字段来显示应用程序的生命周期状态和错误类型,我们可以看到从应用程序自动收集的日志事件。我们可以看到崩溃事件位于列表顶部,作为最新的事件。我们还可以看到应用程序的生命周期事件,如果继续向下滚动,我们将看到一些有助于查找根本原因的生命周期事件。

我们可以看到,有几个生命周期事件告诉我们应用程序在会话期间重新启动了。这是一个重要的提示,因为它意味着 Android 操作系统在某个时刻终止了我们的应用程序,当应用程序在后台停留一段时间时,这很常见。有了这些信息,我们可以尝试通过强制操作系统在后台终止我们的应用程序,然后查看从最近打开的应用程序菜单重新打开时它的行为来重现此问题。

尝试后,我们可以重现此问题,并且发现当应用程序重新启动时,静态“formId”变量丢失了,导致在 SecondPage 片段请求它时为 null。现在我们可以研究将参数传递给片段的最佳实践,以便我们可以更改代码以避免依赖静态字段,而是跨屏幕存储和共享值,从而防止此崩溃再次发生。

额外信息:对于这种情况,我们只需依靠 APM 代理自动发送的事件即可;但是,如果对于其他情况这些事件不够,我们始终可以通过 OpenTelemetry 事件 API 在想要跟踪应用程序状态更改的位置发送自定义事件,如下面的代码片段所示

充分利用您的 Elastic APM 体验

在这篇文章中,我们回顾了 Elastic 在 8.12 版中提供的新的移动 APM 解决方案。新解决方案使用 Elastic 的新的iOSAndroid APM 代理,它们是开源的,并且是在 OpenTelemetry Swift 和 Android SDK/API 的基础上开发的,即作为其发行版。

我们还回顾了 iOS 和 Android 原生应用程序中两个错误场景的配置细节和故障排除工作流程。

  • iOS 场景:使用 Apple 的崩溃报告符号化以及面包屑调试异步方法中的崩溃,以推断崩溃的原因。

  • Android 场景:分析为什么用户在点击表单的“完成”按钮时在最后一个屏幕上遇到空指针异常。仅通过查看崩溃的堆栈跟踪并不总是清楚,并且不容易重现。

在这两种情况下,我们都使用来自移动设备的分布式跟踪以及关联的日志找到了崩溃的根本原因。希望这篇博文能回顾 Elastic 如何帮助管理和监控移动原生应用程序。

Elastic 邀请 SRE 和开发人员亲身体验我们的移动 APM 解决方案,并在其数据任务中开拓新视野。立即在https://ela.st/free-trial尝试。

本文中描述的任何功能或功能的发布和时间安排完全由 Elastic 自行决定。任何当前不可用的功能或功能可能无法按时或根本无法交付。

分享这篇文章