sixty

客户端 agent

React Native

一个应用没有服务端的 span 可供比较,所以被拒绝的调用或者空的响应只能从设备上看到。这就是那个把它们变得可见的 agent。

包
@sixty-sh/react-native 发布在 npm
运行环境
iOS 和 Android。每观测一次请求,阻塞开销大约 2.6 微秒。
源码
sixty-sh/sixty-react-native

安装

iOS and Android. API shape and latency, errors and crashes, render loops — with a public key.

安装说明是写给你已经开着的那个编码助手的提示词,而不是给你的一份清单。这是有意的:它说的是安装完成之后什么必须成立,而不是要改哪些文件 —— 因为代码该放在哪里取决于框架,而放错地方是会静默失败的。助手可以读你的仓库并把这件事推断出来;文档页上的一个段落做不到。

同样这段文字,就是 install_sixty 通过 MCP 服务器 返回的内容,也是收集端在以下位置提供的内容: /v1/setup?kind=react-native. 它只有一份。

React Native 的完整安装说明
Add sixty.sh monitoring to this React Native app. It measures API call shape
and latency, JavaScript errors and crashes, and runaway re-render loops, and
reports anonymously.

Before editing, ask: "Do you want mobile incident replay?" Enable replay
only if the user says yes. It retains a bounded screen/request/error timeline
around failures, never screenshots, screen text, bodies, headers or ids.

1. Install @sixty-sh/react-native. Also install
   @react-native-async-storage/async-storage if it is not already present —
   without it, telemetry buffered when the user backgrounds the app is lost,
   and that is how most sessions end.

2. Call init() once, at the top of the entry file (index.js or App.tsx),
   before anything else imports:

      import { init, composeRelease } from "@sixty-sh/react-native"

      init({
        key: "sixty_pk_YOUR_KEY",
        service: "my-app",
        endpoint: "https://ingest.sixty.sh",
        release: composeRelease({ version: APP_VERSION, build: BUILD_NUMBER }),
        // replay: true, // uncomment only with explicit consent
      })

   Work out where the version and build number actually come from in THIS
   project — expo-constants, react-native-device-info, a generated file, or a
   constant somebody maintains by hand. Do not add a dependency just for this;
   if none exists, hardcode the current version and say so in your summary.

   IF this app ships over-the-air updates (expo-updates or CodePush), the
   release MUST also include the update id:

      composeRelease({ version, build, updateId: Updates.updateId })

   Without it every OTA bundle reports as the same release, and since OTA is
   how most changes ship, almost nothing can ever be compared. Nothing errors;
   the feed just stays empty.

3. Add the Babel plugin to babel.config.js:

      plugins: ["@sixty-sh/react-native/babel"]

   Then start Metro with --reset-cache once. Metro caches transformed modules
   and will not notice the plugin list changed, so without this the build
   succeeds and the transform is silently not applied.

4. Wire the navigator, or every measurement in the app is filed under one
   route and no per-screen finding is possible. Find the NavigationContainer:

      import { createNavigationTracker } from "@sixty-sh/react-native/navigation"
      const tracker = createNavigationTracker()

      <NavigationContainer onReady={tracker.onReady} onStateChange={tracker.onStateChange}>

   If this app does not use React Navigation, call setRoute("ScreenName") from
   wherever it does track the current screen instead.

5. If the app has React error boundaries, call reportError(error) from
   componentDidCatch. React calls the boundary instead of any global handler,
   so errors caught there are invisible to the agent otherwise — and those are
   the ones that leave a user looking at a fallback screen.

Constraints — correctness requirements, not style preferences:

- Do NOT wrap or replace fetch. React Native's fetch is implemented on top of
  XMLHttpRequest, which the agent already instruments; adding a fetch wrapper
  makes every request count twice and nothing will look wrong.
- Do NOT change any existing screen, hook, or network call. This is
  instrumentation only; no behaviour should change.
- The key above is PUBLIC by design: write-only and pinned to one service.
  Paste it literally. Do not move it into an environment variable.
- Do NOT add any analytics library, user id, session id, or device id. This
  agent is deliberately anonymous and must stay that way.

When you are done, tell me where you found the version number, whether this
app ships OTA updates, and whether you cleared the Metro cache. Findings need
two released builds with traffic under each before anything can be compared.

它需要一个公开密钥 —— 绑定到你的来源,因为一个没有自己服务器的应用没有地方存放机密。

它测量什么

信号单位含义
client_errorof viewsthis error is being thrown in real users’ browsers
auth_failures—The server is turning these away on permission grounds rather than failing. People see an empty page or a save that quietly does nothing.
silent_empty—The query runs and succeeds, and returns no rows where it used to return plenty. Nothing reports an error, so the page just renders blank — this is what a broken permission rule looks like from the outside.
latencyms per callthis operation takes longer end to end than it used to
errorserror ratea larger fraction of calls are throwing
runawaycalls per minutethis operation is being called far more often than anything triggers it

它接在哪里

  • React Navigation — 接到导航器上,测量结果就会归到某个界面下。跳过这一步,所有数字都会挤在同一条路由下面。
  • 你自己的组件 — 一个 Babel 插件。Metro 会缓存转换过的模块,而且不会注意到插件列表变了,所以必须清缓存,否则构建会悄无声息地什么都不做。

只有它才做的事

  • 来自设备的网络信号 — 认证被拒,以及成功但为空的响应 —— 在手机上,这两者唯一能被观测到的地方就是这里。
  • 能识别 OTA 的版本 — 空中更新的 id 是版本标识的一部分。没有它,每一个通过空中推送的 bundle 都是同一个版本,比较也就没有可比的对象。

它做不到什么

  • 用的是公开密钥,因为一个应用既没有可以代理的来源,也没有能存放机密的地方。和浏览器那一档不同,这里并没有放弃某个更好的模式。
  • dead_interaction 绑定的是 button、a 和 form,还不认识 Pressable 或 onPress。effect_loop 和 missing_cleanup 是 React 的规则,对这份代码同样原样适用。

配置

每个 agent 都读同样四个变量,而且凡是 SIXTY_* 能用的地方 DRIFT_* 依然有效 —— 产品改过名,但那个名字不是我们说撤就能从别人的部署里撤掉的。

SIXTY_API_KEY没有它,agent 就保持沉默不动,并且会说出来。它从不猜测,从不对着一个未知端点重试,也从不抛异常。
SIXTY_SERVICE这个服务叫什么。在能读出项目名的地方,默认用项目名。
SIXTY_RELEASE最重要的一个。在 Vercel、Render、Railway、Fly、Heroku 和 GitHub Actions 上会自动取到;其他地方请把它设成 commit 的 SHA。没有它,所有测量都会落进同一个没有名字的桶里,任何比较都无从谈起。
SIXTY_ENDPOINT往哪里上报。默认是 http://localhost:4319,这在笔记本上是对的,而在应用被交付给别人的那一刻就是错的。

其余的 —— 发送间隔、采样率、要给什么埋点 —— 都在这个包自己的 README 里,因为那里才是它能随着 agent 变化而保持正确的地方。

sixty 的 React Native agent —— 它测量什么、怎么安装