feature: APM

This commit is contained in:
杭城小刘
2024-07-25 12:58:02 +08:00
parent e3fde7a1df
commit dae10db9d4
14 changed files with 1500 additions and 8 deletions

View File

@@ -7778,7 +7778,149 @@ typedef NS_ENUM(NSUInteger, WeexAPMType) {
// todo? 参考前端资源模块化,如何实现资源预加载(全局 LRU或者基于用户行为路径的预热功能比如某个收银员角色固定的情况下他日常的操作行为是固定的商品扫码、开单
#### 2. Flutter 异常监控
### Flutter 异常监控
Flutter 异常指的是Flutter 程序中 Dart 代码运行时意外发生的错误事件。我们可以通过与 Java 类似的 try-catch 机制来捕获它。但与 Java 不同的是Dart 程序不强制要求我们必须处理异常。
这是由于 Flutter 本身是 Dart 事件循环机制来运行任务的。各个任务的运行状态是相互独立的。即使某个任务出现了异常Dart 也不会退出,只会导致当前任务的后续代码不会执行,用户还可以继续使用其他功能。
Dart 异常可以分为 App 异常和 Framework 异常,分别处理。
#### App 异常监控
App 异常就是应用代码的异常通常由未处理应用层其他模块所抛出的异常引起。根据异常代码的执行时序App 异常可以分为两类:
- 同步异常:可以通过 try-catch 机制捕获
- 异步异常:需要采用 Future 提供的 catchError 语句捕获
```dart
// 使用 try-catch 捕获同步异常
try {
throw StateError('This is a Dart exception.');
} catch(e) {
print(e);
}
// 使用 catchError 捕获异步异常
Future.delayed(Duration(seconds: 1))
.then((e) => throw StateError('This is a Dart exception in Future.'))
.catchError((e)=>print(e));
```
需要注意的是,这两种方式是不能混用的。可以看到,在上面的代码中,我们是无法使用 try-catch 去捕获一个异步调用所抛出的异常的
```dart
// try...catch...无法捕获异步异常
try {
Future.delayed(Duration(seconds: 1))
.then((e) => throw StateError('This is a Dart exception in Future.'))
} catch(e) {
print("This line will never be executed. ");
}
```
但这适合写业务代码的时候分散处理,如果做 APM需要一种统一收口的方式监控异常该怎么办呢
Flutter 提供了 `Zone.runZoned` 方法,给代码执行对象指定一个 Zone在 Dart 中Zone 表示一个代码执行的环境范围,其概念类似沙盒,不同沙盒之间是互相隔离的。如果我们想要观察沙盒中代码执行出现的异常,沙盒提供了 onError 回调函数,拦截那些在代码执行对象中的未捕获异常。
可以将代码都写到 Zone 里,即使没有使用 `try...catch...` 和 `catchError` ,任何同步、异步异常也都被 Zone 捕获到
```dart
// 同步异常
runZoned(() {
throw StateError('This is a Dart exception.');
}, onError: (dynamic e, StackTrace stack) {
print('Sync error caught by zone');
});
// 异步异常
runZoned(() {
Future.delayed(Duration(seconds: 1))
.then((e) => throw StateError('This is a Dart exception in Future.'));
}, onError: (dynamic e, StackTrace stack) {
print('Async error aught by zone');
});
```
如何收口?
要集中捕获 Flutter 应用中的未处理异常,可以把 main 函数中的 runApp 语句也放置在 Zone 中。这样在检测到代码中运行异常时,我们就能根据获取到的异常上下文信息,进行统一处理。
```dart
runZoned<Future<Null>>(() async {
runApp(MyApp());
}, onError: (error, stackTrace) async {
// 异常数据收集,上报 APM
});
```
### Framework 异常监控
Framework 异常,就是 Flutter 框架引发的异常,通常是由应用代码触发了 Flutter 框架底层的异常判断引起的。比如当布局不合规范时Flutter 就会自动弹出一个红色错误界面。
这是由于 Flutter 框架在调用 build 方法构建页面时进行了 `try...catch...` 处理,并提供了一个 `ErrorWidget`,用于组建渲染错误的时候进行信息展示
```dart
@override
void performRebuild() {
Widget built;
try {
// 创建页面
built = build();
} catch (e, stack) {
// 使用 ErrorWidget 创建页面,展示错误信息
built = ErrorWidget.builder(_debugReportException(ErrorDescription("building $this"), e, stack));
...
}
...
}
```
该方案适合在开发阶段定位问题。但如果让用户看到这样一个页面,就很糟糕。因此,通常会重写 `ErrorWidget.builder` 方法,将这样的错误提示页面替换成一个更加友好的页面。
如何重写?
```dart
ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails){
return Scaffold(
body: Center(
// ... UI 描述
)
);
};
```
`ErrorWidget.builder`方法提供了一个参数 `FlutterErrorDetails` 用于表示当前的错误上下文,可以将异常信息上报 APM用于后续分析异常原因。
为了集中处理框架异常Flutter 提供了 `FlutterError` 类,这个类的 `onError` 属性会在接收到框架异常时执行相应的回调。因此,要实现自定义捕获逻辑,我们只要为它提供一个自定义的错误处理回调即可。
### 如何收口
App 异常和 Framework 异常都存在写2个口子收集也可以。但 Flutter 提供了更加友好的口子。使用 Zone 提供的 `handleUncaughtError` 语句,将 Flutter 框架的异常统一转发到当前的 Zone 中,这样我们就可以统一使用 Zone 去处理应用内的所有异常了
```dart
FlutterError.onError = (FlutterErrorDetails details) async {
// Framework 异常转发至 Zone 中
Zone.current.handleUncaughtError(details.exception, details.stack);
};
runZoned<Future<Null>>(() async {
// App 异常本身就在 Zone 的 onError 中收集
runApp(MyApp());
}, onError: (error, stackTrace) async {
//Do sth for error
});
```
异常信息收集了,走 Native 的数据聚合上报策略。用于后续的异常问题自动定位和告警机制。
## 十、子线程 UI 监控