首页
/ expo-observe 实战指南:为 React Native 应用接入基于 OpenTelemetry 的性能监控

expo-observe 实战指南:为 React Native 应用接入基于 OpenTelemetry 的性能监控

2026-09-09 15:22:28作者:秋泉律Samson

expo-observe 是 Expo 官方开源的 React Native 性能监控埋点库,负责从真实用户设备与真实网络上采集应用启动、按屏渲染、交互时间等指标,并通过 OpenTelemetry 协议(OTLP)上报。本指南以 packages/expo-observe/README.md 为核心,结合仓库源码说明其接入步骤、指标含义、运行时配置与自定义上报端点,帮助你在一小时内为自己的 Expo 应用建立可查询的性能观测体系。

一、整体架构:可分离的采集端与服务端

expo-observe 的核心理念是"两个部分,彼此可分离":

  • expo-observe(本包):开源的采集端。它从生产环境的 App 中收集指标(metrics)、事件(events)与日志(logs),并通过 OpenTelemetry Protocol(OTLP)传输。
  • EAS Observe(服务端):存储与分析这些数据的服务。它是唯一能把一条指标关联到对应 EAS build、OTA update 乃至背后的 commit 的目的地,因为这三者由同一套系统产生。

两者可以分开使用:你可以只引入库,把数据发往自己的 collector;默认端点则是 EAS Observe。保留默认端点的理由在于归因能力——只有 EAS 知道某个会话运行的是哪一个构建和哪一个更新。同时该包开源、说 OpenTelemetry 语言、端点可替换,不存在供应商锁定(lock-in)。

从当前仓库的 package.json 可以看到,本包版本为 57.0.9,其核心依赖为 expo-app-metricsexpo-eas-client 两个 workspace 包,而 expo-router@react-navigation/native 被声明为可选 peer 依赖——这正是"按屏指标"集成可以按需启用的前提。

二、支持平台与前置要求

  • 支持平台:Android、iOS 与 tvOS(expo-module.config.json 中声明了 platforms: ["apple", "android"],其中 apple 平台覆盖 iOS/tvOS)。
  • 前置要求
    1. Expo SDK 55 或更高版本;
    2. 一个 EAS 项目(在 app config 中配置 extra.eas.projectId,或运行 eas init);
    3. 开发或生产构建(development/production build)。expo-observe 不运行于 Expo Go,因为它需要原生模块随二进制一起编译进应用。

三、三步接入:从零到看到指标

第 1 步:安装

npx expo install expo-observe

使用 npx expo install 而不是直接 npm install,可以让 Expo CLI 自动匹配合适的版本。

第 2 步:包装根布局

import { ObserveRoot } from 'expo-observe';

function RootLayout() {
  // your app
}

export default ObserveRoot.wrap(RootLayout);

ObserveRoot.wrap 的实现位于 ObserveRoot.tsx:它生成一个高阶组件,内部用 AppMetricsRoot 包裹 ObserveProvider,再渲染你的根组件。从源码注释可以确认:

  • 在 SDK 55 上,导出名为 AppMetricsRoot.wrap(当前版本在 index.ts 中仍以 @deprecated 形式保留 AppMetrics 旧名,用于向后兼容)。

第 3 步:标记"应用真正可用"的时刻

import { useObserve } from 'expo-observe';
import { useEffect } from 'react';

function HomeScreen() {
  const { markInteractive } = useObserve();

  useEffect(() => {
    // after your initialization work finishes
    markInteractive();
  }, [markInteractive]);

  return <Feed />;
}

useObserve 钩子(见 useObserve.ts)会检查当前是否启用了 expo-router 或 react-navigation 集成,并优先使用集成提供的 markInteractive(它会自动补充当前路由名),否则回退到 AppMetrics.markInteractive。调用时请在每个入口屏幕(entry screen)都执行一次——同一会话中只有第一次调用会被记录。在 SDK 55 上,对应写法是 AppMetrics.markInteractive()

README 对这套设计有一段精准的总结:"Launch, bundle load and render metrics are automatic. The one explicit call exists because only your code knows when your app is genuinely ready for input."(启动、包加载与渲染指标都是自动的;唯一显式调用是因为只有你的代码才知道应用何时真正可交互。)——能自动的地方自动,需要精确的地方显式

最后运行 eas build,埋点会随二进制一起分发,指标即可在 EAS 控制台的 Observe 标签页中看到。

如果你更喜欢声明式写法,包内还提供了 ObserveInteractiveMarker 组件(见 ObserveInteractiveMarker.tsx):它不渲染任何 UI,只在挂载时调用一次 markInteractive,适合"数据加载完成后再渲染"的场景:

import { ObserveInteractiveMarker } from 'expo-observe';

function Feed({ items }) {
  if (!items) return <Spinner />;
  return (
    <>
      <FeedList items={items} />
      <ObserveInteractiveMarker params={{ cacheHit: true }} />
    </>
  );
}

注意它是 fire-once 语义:挂载后 params 变化会被忽略并在开发模式下告警,需要后续追加属性时应改用命令式的 useObserve().markInteractive(...)

四、启动指标:自动采集的五个维度

以下指标均为自动采集,无需任何埋点代码:

  • 冷启动(cold launch)热启动(warm launch) 时长;
  • 包加载时长(bundle load)
  • 首帧渲染时间(time to first render,TTR)
  • 可交互时间(time to interactive,TTI)——来自你的 markInteractive() 调用。

每一个 TTI 事件还会携带帧率、热状态、电量、网络参数,以及启动期间发出的请求摘要,从而帮你区分"设备慢"还是"网络慢"。README 特别指出:为浏览器设计的工具不会采集这些字段,因为它们在 Web 上并不存在。

同时,每条事件都会携带 app、release、device 与 route 上下文,因此你可以按版本、机型或屏幕维度对结果分组。

从原生侧源码可以印证这套自动采集的实现路径:Android 端由 ObservabilityManager.kt 管理指标生命周期,iOS 端由 Observability.swift 实现同样的逻辑;而 module.ts 中的 markInteractivemarkFirstRender 等调用经由 JSI 直通原生模块 ExpoObserve(见 types.ts 中对应方法的注释)。

五、按屏指标:Expo Router 与 React Navigation 集成

在 SDK 56 及更高版本上,Expo Router 与 React Navigation 集成会记录按路由(per-route)的渲染与可交互时间,两者均为可选启用(opt-in)

启用方式是在运行时配置中声明:

import { Observe } from 'expo-observe';

Observe.configure({
  integrations: {
    'expo-router': true,
  },
});

对应的类型定义见 types.ts

  • 'expo-router'?: boolean | ObserveNavigationIntegrationConfig——记录路由状态变化产生的 cold_ttrwarm_ttrtti 指标,要求安装 expo-router
  • 'react-navigation'?: boolean | ObserveNavigationIntegrationConfig——同样记录 cold_ttrwarm_ttrtti,要求安装 @react-navigation/native,并且必须用 <ObserveNavigationContainer> 替换原生 <NavigationContainer>(相关实现位于 integrations/react-navigation/ObserveNavigationContainer.tsx)。

配置项可以传对象,用于过滤导出到指标中的路由/查询参数:

type ObserveNavigationIntegrationConfig = {
  /** 从导出的 navigation metric `routeParams` 中移除的参数键。 */
  filteredParams?: string[];
};

(见 types.ts)。当任一被配置的参数被移除时,导出的解析 URL/路径会用 urlHidden: true 替换,但 routeName 不受影响。

configure 中对集成的处理逻辑位于 module.ts,有几个值得注意的边界行为:

  • 若启用了 'expo-router' 但未安装 expo-router,会打印警告且集成不初始化;
  • 若同时启用了两个集成,只有 expo-router 会初始化,react-navigation 会被忽略并给出警告;
  • 集成应在启动时、任何屏幕挂载前一次性配置(useObserve 会通过 useAssertValueDoesNotChange 断言集成状态不能在屏幕生命周期中切换)。

六、更新下载与用户自定义事件

EAS Update 下载性能

使用 EAS Update 的应用会自动上报每个更新包(update bundle)的下载耗时。这完全不需要插桩——只要应用在使用 EAS Update,该指标就会出现在 Observe 中。

用户自定义事件

Observe.logEvent 记录你自己的命名事件:

import { Observe } from 'expo-observe';

Observe.logEvent('checkout_completed', {
  attributes: { orderValue: 129.9, currency: 'USD' },
});

它们与启动指标落在同一条时间线上,因此一个业务事件会紧挨着它附近发生的掉帧被一起呈现。源码层面的细节:logEvent 事件会先持久化到本地,在下次 dispatchEvents() 冲刷时统一发送(见 types.ts);在 module.ts 中,凡是原生模块 ExpoObserve 未实现的属性(如 logEvent),都会通过 Proxy 自动转发给 expo-app-metrics 模块——Android 端 JSI host object 没有 has 钩子,因此用 Object.keys 判断真实成员并决定转发。

七、错误上报:自动 + 边界 + 手动三层

在 SDK 57 及更高版本上,未处理的 JavaScript 错误会自动记录(该功能当前处于 preview 状态),并配套两个显式手段:

import { Observe, ObserveErrorBoundary } from 'expo-observe';

// 1. 用错误边界捕获渲染期错误
function App() {
  return (
    <ObserveErrorBoundary fallback={<ErrorScreen />}>
      <HomeScreen />
    </ObserveErrorBoundary>
  );
}

// 2. 对自己捕获并处理的错误,手动上报
try {
  await syncCart();
} catch (error) {
  Observe.reportError(error);
}

要点(来自 types.tsObserveErrorBoundary.tsx):

  • 未处理错误记录为 exception 日志事件,由 errorHandlingEnabled 控制(默认 true);关闭后,React Native 自身的处理(开发模式红框、生产模式致命终止)不受影响;
  • 全局错误处理器在包首次 import 时就已安装,早于任何 configure 调用,因此在 configure 之前抛出的错误也会被记录;
  • Observe.reportError 会把捕获值规范化:Error 对象提取 namemessagestack;其他值(字符串、普通对象)会被字符串化作为 message;
  • ObserveErrorBoundaryAppMetricsErrorBoundary 的别名导出,用于捕获子树渲染错误。

八、自定义端点:摆脱锁定的 OTLP 上报

expo-observe 通过 OTLP over HTTP(JSON payload)传输数据。要把数据发往自己的 collector,只需在 app config 中设置 endpointUrl

// app.json / app.config.js
{
  "expo": {
    "extra": {
      "eas": {
        "observe": {
          "endpointUrl": "https://your-collector.example.com/v1/metrics"
        }
      }
    }
  }
}

Android 端从 manifest 读取该配置的实现见 ExpoManifest.kt(读取 extra.eas.observe.endpointUrl 属性),实际 POST 请求由 EventDispatcher.kt 发送。

底层协议细节:OTLP 重试语义

值得说明的是,传输层严格遵循 OTLP 规范。Android 端 DispatchUtils.kt 与 iOS 端 DispatchUtils.swift 都把一次 dispatch 的结果划分为五种情形(对齐 OTLP HTTP 响应指引):

  • 成功(success);
  • 可重试失败(如 TLS、超时、连接重置);
  • 部分成功(partial_success,服务端接受了批次但拒绝了部分记录);
  • 明确拒绝(明确的非 2xx 状态码,不重试);
  • 不可重试错误。

同时,ObservabilityManager.kt 注释说明内存中的重试门控(retry-gate)状态是按 OTLP 端点独立维护的——/v1/metrics 与日志端点互不干扰;仓库中 DispatchUtilsBackoffTestDispatchUtilsRetryGateTestDispatchUtilsTest 等测试(见 android/src/test)将这些规范规则与真实网络调用解耦,纯单元测试验证。此外,OTAnyValueSerializer.kt 按 OTLP 规范把 64 位整数编码为 JSON 字符串以避免精度丢失。

九、运行时配置:Observe.configure 全参数详解

Observe.configure() 在运行时设置环境标签、采样率、派发行为与集成。完整配置类型见 types.ts

Observe.configure({
  environment: 'production',        // 事件的环境标签,默认 process.env.NODE_ENV
  dispatchingEnabled: true,         // 是否向服务器派发事件,默认 true
  dispatchInDebug: false,           // 是否派发 debug 构建采集的指标,默认 false,对 release 构建无影响
  sampleRate: 0.5,                  // 参与派发的安装比例 [0,1],默认 undefined(全量)
  errorHandlingEnabled: true,       // 是否记录未处理 JS 错误,默认 true
  integrations: { 'expo-router': true }, // 按屏指标集成,默认全部关闭
});

各参数的底层行为要点:

  • dispatchingEnabled:设为 false 时,待处理指标会被标记为"已发送"而不实际派发,直到重新设为 true
  • dispatchInDebug:为 false 时 debug 构建产生的指标被标记为已发送而不派发;为 true 时与 release 指标一起派发。注意:如果 dispatchingEnabledfalse,或该设备在 sampleRate 采样之外,则无论 dispatchInDebug 如何都不会派发;
  • sampleRate:超出 [0,1] 会被钳制。决策按安装确定性——一台设备对给定采样率要么永久在样本内、要么永久在样本外,跨启动保持稳定。样本外的设备会丢弃待处理指标而不是累积;
  • environment:默认值来自 index.ts 中模块导入时自动调用的 Observe.setBundleDefaults({ environment: process.env.NODE_ENV ?? 'production', isJsDev: !!__DEV__ })

手动冲刷:Observe.dispatchEvents()

Observe.dispatchEvents() 立即冲刷待发送事件。自动派发时机因平台而异:

  • 应用进入后台时自动派发;
  • Android 上还有一个后台 worker,在网络连通后派发事件;
  • iOS 上在应用 resign active 或即将终止时派发。

因此手动冲刷主要用于测试或"在某个节点前确保事件已发出":

await Observe.dispatchEvents();

其他实用 API

  • Observe.clientId:EAS client id,一个随机的、假名化的安装标识符,所有 EAS 客户端库共享;Observe 以 expo.eas_client.id 属性记录它,可用于与其他服务的数据关联。它存储在原生偏好中,跨启动与更新保持稳定,清除数据或重装会变化(备份恢复可能带旧 id 过来);标识的是安装而非用户或设备;在 Web 上为 null(见 types.ts)。
  • Observe.setGlobalAttributes({...}):设置合并进此后每条指标与日志事件的全局属性;按条目的键在冲突时优先;传 null/undefined/空对象可清除。
  • Observe.registerIntegration(name, callback):在指定集成配置可用时回调一次(实现见 module.ts),供第三方包集成自己的行为。

十、命令行与 Agent:把观测结果接入脚本

控制台里能看到的一切,同样可以通过终端获取。所有 eas observe: 命令都支持 --json --non-interactive 参数,因此可以直接管道给脚本,或交给编码 Agent 调用。仓库 README 还提到 eas-observe Expo Skill 可以教 Agent 完成"配置库 + 查询结果"的完整流程。

典型用法(示意):

eas observe:metrics --json --non-interactive

十一、总结:一套"真实用户视角"的观测体系

expo-observe 的设计哲学贯穿始终:自动采集所有能自动的东西(启动、包加载、渲染、TTI 附带的环境参数、EAS Update 下载耗时),只把无法推断的时刻(应用真正可交互)留给开发者显式标记,并为归因、扩展与迁移留足空间——OTLP 标准协议保证可替换端点,集成注册机制保证可扩展第三方采集,eas observe: CLI 与 Skill 保证可被脚本与 Agent 消费。

想在当前仓库继续深入,建议按以下路径阅读:

前提说明:本文涉及的 SDK 版本行为(55 的 AppMetrics* 旧名、56 的按屏指标、57 的错误上报预览)均以当前仓库 README.md 与源码为准;接入前请确认你的 Expo SDK 版本与仓库中 57.0.9 版本的对应关系。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
924
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
599
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
394