🌐Web rum.js
网页端的 JS 错误、Promise 异常、资源加载失败、接口异常与性能指标。异步加载,不阻塞渲染。
约 12 KB无依赖支持 SPA 路由
v1.0.0更新于 2026-08-04
更新日志
- JS 错误、Promise 异常、资源加载失败捕获
- 接口异常与性能指标采集
- Source Map 堆栈还原
- 会话录制关联
📱小程序 mp.js
微信 / 支付宝 / 抖音 / 百度小程序。自动接管 request 上报与页面路由,捕获内存告警。
约 11 KB四平台通用本地存储降级
v1.0.0更新于 2026-08-04
更新日志
- 微信 / 支付宝 / 抖音 / 百度四平台适配
- 自动接管 request 上报接口异常
- 页面路由与内存告警监听
🍎iOS 1.0.0
Swift 实现。崩溃时只落盘不发网络,下次启动补报 —— 进程正在死亡时网络请求发不出去。
Swift 5.9+iOS 13+单文件接入
v1.0.0更新于 2026-08-04
更新日志
- 信号与 NSException 崩溃捕获
- 崩溃落盘 + 下次启动补报
- 符号表还原调用链
- 操作轨迹记录
🤖Android 1.0.0
Kotlin 实现。捕获未捕获异常与 ANR,处理完把控制权还给系统 —— 不会让你在 Google Play 后台丢掉崩溃数据。
Kotlin 1.9+minSdk 24单文件接入
v1.0.0更新于 2026-08-04
更新日志
- 未捕获异常与 ANR 监测
- 崩溃落盘 + 下次启动补报
- R8 反混淆还原调用链(含内联)
- 操作轨迹记录
🐦Flutter 1.0.0
Dart 异常与 Promise 拒绝自动捕获,路由变化记进操作轨迹。原生崩溃仍由平台 SDK 负责 —— Dart 层感知不到进程已经在死了。
Flutter 3.10+Dart 3.0+Android + iOS
v1.0.0
更新于 2026-08-05
更新日志
- Dart 异常与 Promise 拒绝捕获
- 路由 observer 自动记录页面轨迹
- 渠道归因结果读取
- 原生崩溃由平台 SDK 捕获
⚛️React Native 1.0.0
JS 未捕获异常与未处理的 Promise 拒绝自动上报,JS 堆栈原样透传。红屏不会被吞掉。
RN 0.70+Android + iOS无第三方依赖
v1.0.0
更新于 2026-08-05
更新日志
- JS 未捕获异常与 unhandledrejection
- JS 堆栈透传,不被原生栈覆盖
- 红屏不被吞掉
- 渠道归因结果读取
接入说明
把「你的应用ID」换成看板里给你的那一串。四端上报到同一个 ID,数据在同一个看板里。
1. 引入脚本
放在 </body> 之前。不用下载,直接引用:
<script src="https://你的域名/rum.js" crossorigin="anonymous"></script>
<script>
Woodpecker.init({
id: "你的应用ID",
release: "1.0.0", // 建议填,便于按版本区分问题
sendSpaPv: true // 单页应用:路由变化自动上报
});
</script>
2. 记录用户身份与操作轨迹(可选)
// 关联业务用户,错误列表里能看到「影响了哪些人」
Woodpecker.identify("user_10086");
// 手动埋一条轨迹,出错时会带上出错前的操作过程
Woodpecker.addBreadcrumb("click", { button: "结算" });
// 主动上报一个已捕获的异常
try { risky(); } catch (e) { Woodpecker.capture(e); }
3. 上传 Source Map(压缩过的代码必须传)
不传的话堆栈是 a.b.c:1:2345,定位不到源码:
curl -X POST "https://你的域名/api/apm/你的应用ID/sourcemaps?release=1.0.0&filename=app.js.map" \
-H "Cookie: <登录 Cookie>" \
--data-binary @dist/app.js.map
上传后请把 .map 文件从公开目录删掉 —— source map 等价于源码。
1. 放入工程
下载 mp.js 放到小程序目录(如 utils/mp.js)。
2. 在 app.js 里初始化
const Woodpecker = require('./utils/mp.js');
App({
onLaunch() {
Woodpecker.init({
id: '你的应用ID',
server: 'https://你的域名',
release: '1.0.0'
});
}
});
3. 配置服务器域名
小程序后台「开发设置 → 服务器域名」把 https://你的域名 加进
request 合法域名,否则上报会被平台拦掉。
4. 用法
Woodpecker.setUser('user_10086');
Woodpecker.addBreadcrumb('tap', { page: '购物车' });
Woodpecker.capture(err);
SDK 会自动接管 wx.request(及支付宝/抖音/百度的对应方法)上报接口异常,
并监听页面路由与内存告警,不需要你手动埋。
1. 放入工程
解压后把 Woodpecker/Woodpecker.swift 拖进 Xcode 工程。单文件,无第三方依赖。
2. 启动 —— 越早越好
放在 didFinishLaunchingWithOptions 的最前面,装上之前发生的崩溃抓不到:
func application(_ app: UIApplication,
didFinishLaunchingWithOptions o: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Woodpecker.shared.start(.init(
appId: "你的应用ID",
server: URL(string: "https://你的域名")!,
release: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0"))
return true
}
3. 用法
Woodpecker.shared.addBreadcrumb("tap", ["btn": "结算"])
Woodpecker.shared.capture(name: "DecodingError", message: "字段缺失: expiresAt")
4. 上传符号表
导出时一定要 demangle,否则看板上是 _$s14MyApp4CartO9calcTotalyyFZ 这种读不懂的东西:
nm -n YourApp.app.dSYM/Contents/Resources/DWARF/YourApp \
| xcrun swift-demangle > symbols.txt
curl -X POST "https://你的域名/api/apm/你的应用ID/symbols?platform=ios&release=1.0.0&filename=symbols.txt" \
-H "Cookie: <登录 Cookie>" \
--data-binary @symbols.txt
release 必须与 CFBundleShortVersionString 完全一致,否则匹配不上。
1. 放入工程
解压后把 woodpecker/Woodpecker.kt 拷进工程(包名 com.woodpecker.sdk,可自行改)。
2. 加网络权限
<uses-permission android:name="android.permission.INTERNET"/>
3. 在 Application 里初始化
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Woodpecker.init(this, Woodpecker.Config(
appId = "你的应用ID",
server = "https://你的域名",
release = BuildConfig.VERSION_NAME))
Woodpecker.startAnrWatch() // 可选:ANR 监测
}
}
4. proguard-rules.pro 必加
不保留行号的话,即使反混淆出方法名也定位不到具体代码行:
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute SourceFile
5. 上传 mapping
curl -X POST "https://你的域名/api/apm/你的应用ID/symbols?platform=android&release=1.0.0&filename=mapping.txt" \
-H "Cookie: <登录 Cookie>" \
--data-binary @app/build/outputs/mapping/release/mapping.txt
release 必须与 versionName 完全一致。
被 R8 内联掉的调用层级也会被还原出来。
6. 用法
Woodpecker.setPage("/CartActivity")
Woodpecker.addBreadcrumb("tap", mapOf("btn" to "结算"))
Woodpecker.capture(e)
1. 加依赖
dependencies:
woodpecker:
path: ./woodpecker
原生 SDK 也要放进工程(Android 的 .kt、iOS 的 .swift),见包内 sdk_note.md。
2. 在 runApp 之前启动
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Woodpecker.start(
appId: '你的应用ID',
server: 'https://你的域名',
release: '1.0.0',
);
runApp(const MyApp());
}
3. 路由自动记进轨迹
MaterialApp(
navigatorObservers: [WoodpeckerNavigatorObserver()],
// …
)
4. 用法
Woodpecker.addBreadcrumb('tap', {'btn': '结算'});
try { risky(); } catch (e, s) { Woodpecker.capture(e, s); }
final a = await Woodpecker.attribution();
if (a != null && a.matched) print('来自渠道 ${a.channelName}');
5. release 包要符号化
Flutter release 构建的 Dart 堆栈是混淆的,不处理的话看板里只有一串偏移量:
flutter build apk --obfuscate --split-debug-info=build/symbols
flutter symbolize -i 崩溃堆栈.txt -d build/symbols/app.android-arm64.symbols
Dart 侧异常由插件上报;原生崩溃由平台 SDK 捕获 ——
Dart 层感知不到进程已经在死了,拦不住也不该拦。
1. 安装
npm i @577/woodpecker-react-native
cd ios && pod install
改了原生依赖必须重新构建,热重载不够。
2. 在入口处启动
import Woodpecker from '@577/woodpecker-react-native';
await Woodpecker.start({
appId: '你的应用ID',
server: 'https://你的域名',
release: '1.0.0',
});
3. 用法
Woodpecker.addBreadcrumb('tap', { btn: '结算' });
Woodpecker.setPage('/Cart');
try { risky(); } catch (e) { Woodpecker.capture(e); }
const a = await Woodpecker.attribution();
if (a?.matched) console.log('来自渠道', a.channelName);
4. release 包要传 source map
RN release 的 JS 是压缩过的,堆栈里是 index.android.bundle:1:98234:
curl -X POST "https://你的域名/api/apm/你的应用ID/sourcemaps?release=1.0.0&filename=index.android.bundle.map" \
-H "Cookie: <登录 Cookie>" \
--data-binary @android/app/build/generated/sourcemaps/react/release/index.android.bundle.map
JS 异常由 SDK 捕获;原生崩溃由平台 SDK 捕获。
红屏不会被吞掉 —— 上报完会把原来的处理器叫回去。