我给 Expo / React Native 做了个应用内调试面板,真机排障终于不用一直连电脑了
最近一段时间在做 Expo / React Native 项目排障时,越来越觉得一个问题挺烦:很多 bug 只在真机、测试包,甚至某个用户环境里出现。
开发机上看着一切正常,一到手机上就开始玄学。让测试同学复现一下吧,往往只能得到一句:
我刚才点这里,然后它好像卡了一下,再然后接口就失败了。
这时候如果还得让对方接电脑、开 Metro、翻 Xcode / Android Studio 日志,基本就很难推进了。
所以我做了一个小工具:expo-inapp-debugger。它的目标很简单:把常用排障信息直接放进 App 里,让测试包、内部包、甚至受控的生产包都能自己看日志、错误和网络请求。
项目地址:
- GitHub:
https://github.com/Concur-max/expo-inapp-debugger - npm:
expo-inapp-debugger
下面截图来自示例 Expo 项目,iOS / Android 都有。
它长什么样
接入之后,App 里会出现一个原生浮动入口。点开后可以看日志、网络请求、请求详情、App/runtime 信息,也可以临时打开原生日志或原生网络采集。
日志面板大概是这样:
网络请求列表:
点进去可以看请求详情:
Android 这边也做了同样的入口和面板:
为什么想做这个
React Native 项目排障时,很多信息其实都散在不同地方:
- JS 日志在 Metro / DevTools 里
- 原生日志在 Xcode 或 logcat 里
- 网络请求可能在代理工具里
- 崩溃和 React 错误又是另一套链路
- 测试同学复现时,开发不一定在现场
这几个东西分开看时,最麻烦的不是“没有数据”,而是时间线很容易断。
比如一个页面白屏了,到底是接口先 500,还是解析 response 时抛错,还是某个 native callback 比预期晚回来?如果只能靠口头描述和几段截断日志,很容易误判。
所以这个库一开始就不是按“做一个漂亮面板”的思路来的,而是按排障现场来设计:
尽量把日志、错误、请求这些事件放到同一个 App 内时间线里,优先保证真实发生顺序和可还原性。
目前能看哪些东西
现在主要支持这些能力:
- JS 日志:
console.log、console.info、console.warn、console.error、console.debug - JS 错误:全局 error、未处理 Promise rejection、React Error Boundary 错误
- 网络请求:
fetch、XMLHttpRequest、WebSocket - 手动写入:通过
inAppDebug.log()和inAppDebug.captureError()主动记录 - 原生日志:iOS / Android 都支持,但默认不打开
- 原生网络提示:部分 URLSession / OkHttp 信息,同样默认不打开
- App 信息:基础 runtime、系统、应用信息
这里有个取舍:native logs 和 native network 会更重一点,所以我没有默认打开。一般是遇到需要查原生层问题时,再从 App Info 里临时开启。
快速接入
安装:
pnpm add expo-inapp-debugger
npm / yarn / bun 也都可以。
因为它包含 iOS / Android 原生代码,所以安装后需要重新构建 App。Expo Go 不支持,适合 Expo Dev Client、Expo prebuild 或 bare React Native。
npx expo prebuild
npx expo run:ios
# 或
npx expo run:android
最简单的接入方式是用 InAppDebugRoot:
import { InAppDebugRoot } from 'expo-inapp-debugger';
export default function Root() {
return (
<InAppDebugRoot enabled={__DEV__} locale="zh-CN">
<App />
</InAppDebugRoot>
);
}
InAppDebugRoot 里面包含了 Provider 和一个内置 React Error Boundary,适合开发阶段快速接入。
如果是准备放到正式包里,我更推荐只挂 Provider,继续使用你自己项目里的 Error Boundary:
import { InAppDebugProvider } from 'expo-inapp-debugger';
export default function Root() {
return (
<InAppDebugProvider enabled={__DEV__} locale="zh-CN">
<App />
</InAppDebugProvider>
);
}
我比较在意的几个点
1. 关闭时尽量别打扰业务
调试工具最怕什么?不是功能少,而是它自己变成问题来源。
所以 enabled={false} 时,库不会安装 JS 日志和网络 hook,也不会启动 native 采集。也就是说,普通用户路径上它应该尽量接近“没存在过”。
如果你要把它随正式包发出去,建议不要对所有人默认开启,而是绑定内部账号、测试设备、远程开关或隐藏手势。
2. 生产包也可以按需开
有些问题只在正式包环境出现,这种场景下 Debug 包不一定复现。
我自己的建议是:正式包可以带着,但入口一定要收起来。例如:
- 关于页连续点版本号 7 次
- 内部账号才出现调试入口
- 远程配置下发白名单
- 临时口令 + 本地手势组合开启
如果你特别在意普通用户路径的开销,可以等开关命中后再 require:
export default function Root() {
const debuggerEnabled =
isInternalUser && remoteConfig.inAppDebuggerEnabled;
if (!debuggerEnabled) {
return <App />;
}
const { InAppDebugProvider } = require('expo-inapp-debugger');
return (
<InAppDebugProvider enabled locale="zh-CN">
<App />
</InAppDebugProvider>
);
}
这段代码不复杂,但对正式包接入会安心很多。
3. 原生能力默认克制
JS 层日志、错误、请求已经能解决一大部分问题。
原生日志和原生网络采集虽然有用,但也更容易带来额外成本。所以现在默认是关闭的,只在需要查 native 层问题时打开。
这点可能没有“默认全开”爽,但我觉得调试工具还是应该保守一点:平时别添乱,需要时再加码。
配置项
常用配置大概这些:
<InAppDebugProvider
enabled={__DEV__}
initialVisible
enableNetworkTab
enableNativeLogs={false}
enableNativeNetwork={false}
maxLogs={2000}
maxErrors={100}
maxRequests={100}
locale="zh-CN"
>
<App />
</InAppDebugProvider>
几个我觉得更常调的参数:
| 参数 | 说明 |
|---|---|
enabled | 是否启用调试器 runtime 和采集器 |
initialVisible | 启用后是否显示原生浮动入口 |
enableNetworkTab | 是否启用网络面板和 JS 网络采集 |
enableNativeLogs | 是否启动时直接开启原生日志采集 |
enableNativeNetwork | 是否启动时直接开启原生网络采集 |
maxLogs | 最多保留多少条日志 |
maxErrors | 最多保留多少条错误 |
maxRequests | 最多保留多少条网络请求 |
locale | UI 语言,支持 auto、en-US、zh-CN、zh-TW、ja |
手动写日志
除了自动采集,也可以在关键业务位置主动打点:
import { inAppDebug } from 'expo-inapp-debugger';
inAppDebug.log('checkout:start', {
orderId,
source: 'cart',
});
try {
await submitOrder();
} catch (error) {
inAppDebug.captureError(error, {
scene: 'submitOrder',
orderId,
});
throw error;
}
这类日志对复现偶发问题很有用。尤其是支付、登录、推送跳转这种链路,光看接口请求通常不够,业务上下文也得跟上。
适合什么场景
我觉得它比较适合这些情况:
- 测试同学经常在真机上复现问题,但开发不一定在旁边
- 有些 bug 只在 TestFlight、内测包或正式包出现
- 想在 App 内快速看网络请求和错误信息
- 项目里 Expo / React Native 混合了不少原生模块
- 不想每次排障都先让对方接电脑、开代理、导日志
它不是用来替代 Sentry、Datadog 或 Charles 的。更准确地说,它补的是“现场排障”这一层:问题刚发生时,先在设备上把最关键的信息看一眼。
很多时候就这一眼,已经能决定下一步该查接口、查状态、查 native,还是查业务逻辑。
现在的状态
当前版本已经覆盖了 iOS / Android 的基础调试面板、JS 日志、错误、网络请求、WebSocket,以及部分原生采集能力。
后面我还想继续打磨几块:
- 更准确的跨 JS / native 时间线合并
- 更好用的过滤和搜索
- 请求/日志导出
- 更细的性能开销控制
- 更多真实业务场景下的边界处理
如果你也在做 Expo / React Native 项目,并且经常被真机偶发问题折磨,可以试试看。
项目还比较新,欢迎提 issue,也欢迎直接拿去跑一跑。