Flutter异常监控 - 叁 | 从bugsnag源码学习如何追溯异常产生路径

Posted bug樱樱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter异常监控 - 叁 | 从bugsnag源码学习如何追溯异常产生路径相关的知识,希望对你有一定的参考价值。

Flutter异常监控 - 叁 | 从bugsnag源码学习如何追溯异常产生路径
发布文章

前言

没错,继Flutter异常监控 | 框架Catcher原理分析 之后,带着那颗骚动的好奇心我又捣鼓着想找其他Flutter异常监控框架读读,看能不能找到一些好玩的东西,于是在官方介绍第三方库里发现了这货Bugsnag,大致扫了下源码发现flutter侧主流程很简单没啥东西可看滴,因为这货强烈依赖对端能力,Flutter异常捕获之后就无脑抛给对端SDK自己啥都不干 ,抛开Bugsnag这种处理异常的方式不论,源码里却也有一些之我见的亮度值得借鉴和学习,比如本文主要介绍Bugsnag如何追溯异常路径的设计思想和实现,对异常捕获的认识有不少帮助。

Bugsnag

功能简介

在介绍可追溯异常路径设计之前,有必要先科普下Bugsnag是什么? 让大佬们有一个大局观,毕竟后面介绍内容只是其中一个小的点。

Bugsnag跟Catcher一样也是Flutter异常监控框架,Bugsnag-flutter只是壳,主要作用有:

  1. 规范多平台(安卓,ios)异常调用和上报的接口。
  2. 拿到flutter异常相关数据传递给对端。

主要支持功能:

  1. dart侧异常支持手动和自动上报。
  2. 支持上报数据序列化,有网环境下会继续上报。
  3. 支持记录用户导航步骤,自定义关键节点操作,网络异常自动上报。

这个框架的侧重点跟Catcher完全不同,它不支持异常的UI客户端自定义显示,也不支持对异常的定制化处理。说白了就是你想看异常就只能登陆到Bugsnag后台看到,后台有套餐包括试用版和收费版(你懂滴)。

基本使用

void main() async => bugsnag.start(
      runApp: () => runApp(const ExampleApp()),
      // 需要到bugsang后台注册账号申请一个api_key
      apiKey: 'add_your_api_key_here',
      projectPackages: const BugsnagProjectPackages.only('bugsnag_example'),
      // onError callbacks can be used to modify or reject certain events
      //...
    );

class ExampleApp extends StatelessWidget 
  const ExampleApp(Key? key) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      navigatorObservers: [BugsnagNavigatorObserver()],
      initialRoute: '/',
      routes: 
        '/': (context) => const ExampleHomeScreen(),
        '/native-crashes': (context) => const NativeCrashesScreen(),
      ,
    );
  

// Use leaveBreadcrumb() to log potentially useful events in order to
  // understand what happened in your app before each error.
  void _leaveBreadcrumb() async =>
      bugsnag.leaveBreadcrumb('This is a custom breadcrumb',
          // Additional data can be attached to breadcrumbs as metadata
          metadata: 'from': 'a', 'to': 'z');
import 'package:bugsnag_breadcrumbs_http/bugsnag_breadcrumbs_http.dart' as http;
void _networkFailure() async =>
      http.post(Uri.parse('https://example.com/invalid'));

后台效果展示

Flutter异常显示页

bugsnag后台Breadcrumbs页显示内容:可以看到路径中包含了当前页面信息,请求信息和关键步骤,异常生成的路径和时间点

异常捕获框架阅读通用套路

在异常上报主流程之前,必要的通用套路不能忘,按照这个思路来追源码事半功倍,如下:

  1. Flutter异常监控点

三把斧:FlutterError.onError ,addErrorListener,runZonedGuarded 详见:不得不知道的Flutter异常捕获知识点:Zone 中Zone异常捕获小节。

  1. 针对Error的包装类生成

我们最好不要直接使用onError参数中的error和stack字段,因为为方便问定位一般原始Error会经过各种转换增加附加信息更容易还原异常现场,比如设备id等,对比Catcher中这个经过包装的对象叫Report

  1. 操作包装类

上面最终生成的包装类对象会经过一些操作,操作主要三个方面:显示、存储、上报。拿Catcher来举例子,它包含了UI显示和上报两个。一般在项目中可能显示不那么重要,最重要的是存储和上报。

Bugsnag主要流程源码简析

主要领略下”异常捕获通用套路” 大法有多香:

找监控点

这个流程中少了addErrorListener,说明bugsnag对isolate异常是监控不到滴。

Future<void> start(
    FutureOr<void> Function()? runApp,
    //... Tag1 一堆额外参数
  ) async 
    //...
    //开始就想着用对端SDK,这里当然少不了初始化通道
    _runWithErrorDetection(
      detectDartErrors,
      () => WidgetsFlutterBinding.ensureInitialized(),
    );

    //...

    await ChannelClient._channel.invokeMethod('start', <String, dynamic>
      //... Tag2:这里将Tag1处的额外参数传给了对端SDK

    );

   //Tag3:dart error的处理类,其中全部都是通过channel来桥接的
    final client = ChannelClient(detectDartErrors);
    client._onErrorCallbacks.addAll(onError);
    this.client = client;

    _runWithErrorDetection(detectDartErrors, () => runApp?.call());
  

  void _runWithErrorDetection(
    bool errorDetectionEnabled,
    FutureOr<void> Function() block,
  ) async 
    if (errorDetectionEnabled) 
      //多么熟悉的味道,
      await runZonedGuarded(() async 
        await block();
      , _reportZonedError);
     else 
      await block();
    
  

//最终_reportZonedError会执行到_notifyInternal
void _notifyUnhandled(dynamic error, StackTrace? stackTrace) 
    _notifyInternal(error, true, null, stackTrace, null);
  
ChannelClient(bool autoDetectErrors) 
    if (autoDetectErrors) 
      FlutterError.onError = _onFlutterError;
    
  

void _onFlutterError(FlutterErrorDetails details) 
    _notifyInternal(details.exception, true, details, details.stack, null);
    //...
  

找包装类生成

Future<void> _notifyInternal(
    dynamic error,
    bool unhandled,
    FlutterErrorDetails? details,
    StackTrace? stackTrace,
    BugsnagOnErrorCallback? callback,
  ) async 
    final errorPayload =
        BugsnagErrorFactory.instance.createError(error, stackTrace);
    final event = await _createEvent(
      errorPayload,
      details: details,
      unhandled: unhandled,
      deliver: _onErrorCallbacks.isEmpty && callback == null,
    );

   //...

    await _deliverEvent(event);
  

//我说什么来着:连最基本的Event构造,都是在对端。
Future<BugsnagEvent?> _createEvent(
    BugsnagError error, 
    FlutterErrorDetails? details,
    required bool unhandled,
    required bool deliver,
  ) async 
    final buildID = error.stacktrace.first.codeIdentifier;
    //...
    ;
    //调用了对端通道方法来实现。
    final eventJson = await _channel.invokeMethod(
      'createEvent',
      
        'error': error,
        'flutterMetadata': metadata,
        'unhandled': unhandled,
        'deliver': deliver
      ,
    );

    if (eventJson != null) 
      return BugsnagEvent.fromJson(eventJson);
    

    return null;
  

操作包装类

本来以为此处要大干一场,结果灰溜溜给了对端。。。,什么都不想说,内心平静毫无波澜~~~

Future<void> _deliverEvent(BugsnagEvent event) =>
      _channel.invokeMethod('deliverEvent', event);

主要源码流程看完了,下面来看下Bugsnag我觉得比较好玩的需求和实现。

什么是可追溯异常路径

这个是我自己想的一个词,该需求目的是能完整记录用户操作的整个行为路径,这样达到清晰指导用户操作过程,对问题的定位很有帮助。可以理解成一个小型的埋点系统,只是该埋点系统只是针对异常来做的。

如下:异常产生流程,state被成功加载后用户先进入了主页,然后从主页进入了native-crashes页之后异常就产生了。 对开发者和测试人员来说很容易复现通过如上路径来复现问题。

异常路径后台显示效果

如何实现

前置知识

Bugsnag中将可追溯的路径命名为Breadcrumb,刚开始我不理解,这个单词英文意思:面包屑,跟路径八竿子都扯不上关系,直到查维基百科才发现为什么这么命名,通过一片一片的面包屑才能找到回家的路。。。,老外们还真够有情怀的!

Breadcrumb的命名的含义, 有没有发觉这个名字起得好形象!

页面路径(英语:breadcrumb或breadcrumb trail/navigation),又称面包屑导航,是在用户界面中的一种导航辅助。它是用户一个在程序或文件中确定和转移他们位置的一种方法。面包屑这个词来自糖果屋 这个童话故事;故事中,汉赛尔与葛丽特企图依靠洒下的面包屑找到回家的路。

当然最终这些丢下的面包屑(leaveBreadcrumb)路径数据也是通过调用到对端SDK来实现:

Future<void> leaveBreadcrumb(
    String message, 
    Map<String, Object>? metadata,
    BugsnagBreadcrumbType type = BugsnagBreadcrumbType.manual,
  ) async 
    final crumb = BugsnagBreadcrumb(message, type: type, metadata: metadata);
    await _channel.invokeMethod('leaveBreadcrumb', crumb);
  

这里主要关注下自动添加面包屑的场景。

如何添加路径

两种方式:

  1. 手动添加,通过调用bugsnag.leaveBreadcrumb

  2. 自动添加,其中包括两个场景:导航栏跳转和 网络请求

如上两个场景的的实现原理涉及到对应用性能的监控功能,重点分析其中原理。

导航栏自动埋点实现原理

MaterialApp: navigatorObservers 来实现对页面跳转的监听,Bugsnag中是通过自定义BugsnagNavigatorObserver,并在其回调函数中监听导航行为手动调用leaveBreadcrumb方法上报导航信息给后台从而达到监听页面的效果。

注意事项: navigatorObservers是创建导航器的观察者列表,将要观察页面跳转对象放在该列表中,页面中发生导航行为时候,就可以监听到。

如果一个应用中有多个MaterialApp的情况,需要保证每个MaterialApp:navigatorObservers中都有BugsnagNavigatorObserver才行,不然某些MaterialApp中也监控不到。最好是一个应用统一一份MaterialApp减少这种不必要的麻烦。

如下代码中

  1. Bugsnag框架自定义了BugsnagNavigatorObserver对象, 该对象必须继承NavigatorObserver并实现其中回调函数方可放入到MaterialApp:navigatorObservers中,不是随便什么对象都可以放到列表中的。
  2. 这样Bugsnag就具有了对整个接入应用导航的监控能力,页面进入或者页面退出行为都可以被监控到。
  3. 然后在步骤2回调中手动调用_leaveBreadcrumb 来实现对导航路径的监听。
  4. _leaveBreadcrumb 将数据传送给对端SDK,SDK传输数据给bugsnag后台Breadcrumb 页,也就是上面效果中呈现的。
class ExampleApp extends StatelessWidget 
  const ExampleApp(Key? key) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      navigatorObservers: [BugsnagNavigatorObserver()],
      //...
    );
  


----[BugsnagNavigatorObserver]----->
// BugsnagNavigatorObserver extends NavigatorObserver
BugsnagNavigatorObserver(
    //...
  ) : _navigatorName = (navigatorName != null) ? navigatorName : 'navigator';

  @override
  void didReplace(Route<dynamic>? newRoute, Route<dynamic>? oldRoute) 
    _leaveBreadcrumb('Route replaced on', 
      if (oldRoute != null) 'oldRoute': _routeMetadata(oldRoute),
      if (newRoute != null) 'newRoute': _routeMetadata(newRoute),
    );
    //...
  

  //....其他回调函数

  void _leaveBreadcrumb(String function, Map<String, Object> metadata) 
    if (leaveBreadcrumbs) 
      bugsnag.leaveBreadcrumb(
        _operationDescription(function),
        type: BugsnagBreadcrumbType.navigation,
        metadata: metadata,
      );
    
  

网络请求自动埋点实现原理

通过自定义http.BaseClient实现对默认http.Client中 send方法代理来实现,对请求发送和失败进行统一化监听,并记录了请求时长埋点上报。

推荐个网络监听通用方案: 可以看下didi的Flutter方案: 复写HttpOverride即可,DoKit/dokit_http.dart at master · didi/DoKit

如下

  1. 当点击发送网络请求时,会调用Bugsnag自己的http库。
  2. Bugsnag http库中自己实现了Client类,该类复写send方法(该方法在发生网络行为时都会被触发),并在其中做了网络监听的额外埋点操作_requestFinished,其中包括对网络结果反馈和网络请求时间的统计。
  3. 例子中最终post会执行client.send,从而完成了对网络自埋点路径的上报。

import 'package:bugsnag_breadcrumbs_http/bugsnag_breadcrumbs_http.dart' as http;
void _networkFailure() async =>
      http.post(Uri.parse('https://example.com/invalid'));

----[bugsnag_breadcrumbs_http.dart]---->
Future<http.Response> post(Uri url,
        Map<String, String>? headers, Object? body, Encoding? encoding) =>
    _withClient((client) =>
        client.post(url, headers: headers, body: body, encoding: encoding));

Future<T> _withClient<T>(Future<T> Function(Client) fn) async 
  var client = Client();
  try 
    return await fn(client);
   finally 
    client.close();
  


---->[client.dart]---->
class Client extends http.BaseClient 
  /// The wrapped client.
  final http.Client _inner;

  Client() : _inner = http.Client();

  Client.withClient(http.Client client) : _inner = client;

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) async 
    final stopwatch = Stopwatch()..start();
    try 
      final response = await _inner.send(request);
      //拦截点:这里监听发送成功
      await _requestFinished(request, stopwatch, response);
      return response;
     catch (e) 
      //拦截点:这里监听发送失败
      await _requestFinished(request, stopwatch);
      rethrow;
    
  

  Future<void> _requestFinished(
    http.BaseRequest request,
    Stopwatch stopwatch, [
    http.StreamedResponse? response,
  ]) =>
      _leaveBreadcrumb(Breadcrumb.build(_inner, request, stopwatch, response));

总结

本文主要对可追溯Crash路径自动埋点原理进行分析,该需求是读Bugsnag是觉得想法上有亮点的地方,就重点拎出来说说,结合自身做Flutter异常捕获过程经验,压根没考虑到这种记录异常路径的需求。而且它还做得这么细针对了导航监听和网络监听自动埋点,而这两块又恰恰是对定位问题比较关键的,试问哪个异常出现了你不关注发生的页面,哪个线上App逃得开网络异常。

另外本文也总结阅读Flutter异常监控框架必看的几个关键步骤,结合Bugsnag源码进行实际讲解。其实Flutter异常监控框架来回就那么几个步骤没什么大的变化,主要是看其中有什么亮度的需求并针对需求做了哪些开闭设计,这些才是令人振奋的东西。

参考链接

bugsnag/bugsnag-flutter: Bugsnag crash reporting for Flutter apps
DoKit/Flutter at master · didi/DoKit

作者:听蝉
链接:https://juejin.cn/post/7179426939893973029

Flutter异常监控 - 叁 | 从bugsnag源码学习如何追溯异常产生路径
同步滚动:
前言
没错,继Flutter异常监控 | 框架Catcher原理分析 之后,带着那颗骚动的好奇心我又捣鼓着想找其他Flutter异常监控框架读读,看能不能找到一些好玩的东西,于是在官方介绍第三方库里发现了这货Bugsnag,大致扫了下源码发现flutter侧主流程很简单没啥东西可看滴,因为这货强烈依赖对端能力,Flutter异常捕获之后就无脑抛给对端SDK自己啥都不干 ,抛开Bugsnag这种处理异常的方式不论,源码里却也有一些之我见的亮度值得借鉴和学习,比如本文主要介绍Bugsnag如何追溯异常路径的设计思想和实现,对异常捕获的认识有不少帮助。

Bugsnag
功能简介
在介绍可追溯异常路径设计之前,有必要先科普下Bugsnag是什么? 让大佬们有一个大局观,毕竟后面介绍内容只是其中一个小的点。

Bugsnag跟Catcher一样也是Flutter异常监控框架,Bugsnag-flutter只是壳,主要作用有:

规范多平台(安卓,ios)异常调用和上报的接口。
拿到flutter异常相关数据传递给对端。
主要支持功能:

dart侧异常支持手动和自动上报。
支持上报数据序列化,有网环境下会继续上报。
支持记录用户导航步骤,自定义关键节点操作,网络异常自动上报。
这个框架的侧重点跟Catcher完全不同,它不支持异常的UI客户端自定义显示,也不支持对异常的定制化处理。说白了就是你想看异常就只能登陆到Bugsnag后台看到,后台有套餐包括试用版和收费版(你懂滴)。

基本使用
void main() async => bugsnag.start(
runApp: () => runApp(const ExampleApp()),
// 需要到bugsang后台注册账号申请一个api_key
apiKey: ‘add_your_api_key_here’,
projectPackages: const BugsnagProjectPackages.only(‘bugsnag_example’),
// onError callbacks can be used to modify or reject certain events
//…
);

class ExampleApp extends StatelessWidget
const ExampleApp(Key? key) : super(key: key);

@override
Widget build(BuildContext context)
return MaterialApp(
navigatorObservers: [BugsnagNavigatorObserver()],
initialRoute: ‘/’,
routes:
‘/’: (context) => const ExampleHomeScreen(),
‘/native-crashes’: (context) => const NativeCrashesScreen(),
,
);


// Use leaveBreadcrumb() to log potentially useful events in order to
// understand what happened in your app before each error.
void _leaveBreadcrumb() async =>
bugsnag.leaveBreadcrumb(‘This is a custom breadcrumb’,
// Additional data can be attached to breadcrumbs as metadata
metadata: ‘from’: ‘a’, ‘to’: ‘z’);
import ‘package:bugsnag_breadcrumbs_http/bugsnag_breadcrumbs_http.dart’ as http;
void _networkFailure() async =>
http.post(Uri.parse(‘https://example.com/invalid’));
后台效果展示

Flutter异常显示页

bugsnag后台Breadcrumbs页显示内容:可以看到路径中包含了当前页面信息,请求信息和关键步骤,异常生成的路径和时间点

异常捕获框架阅读通用套路
在异常上报主流程之前,必要的通用套路不能忘,按照这个思路来追源码事半功倍,如下:

Flutter异常监控点
三把斧:FlutterError.onError ,addErrorListener,runZonedGuarded 详见:不得不知道的Flutter异常捕获知识点:Zone 中Zone异常捕获小节。

针对Error的包装类生成
我们最好不要直接使用onError参数中的error和stack字段,因为为方便问定位一般原始Error会经过各种转换增加附加信息更容易还原异常现场,比如设备id等,对比Catcher中这个经过包装的对象叫Report

操作包装类
上面最终生成的包装类对象会经过一些操作,操作主要三个方面:显示、存储、上报。拿Catcher来举例子,它包含了UI显示和上报两个。一般在项目中可能显示不那么重要,最重要的是存储和上报。

Bugsnag主要流程源码简析
主要领略下”异常捕获通用套路” 大法有多香:

找监控点

这个流程中少了addErrorListener,说明bugsnag对isolate异常是监控不到滴。

Future start(
FutureOr Function()? runApp,
//… Tag1 一堆额外参数
) async
//…
//开始就想着用对端SDK,这里当然少不了初始化通道
_runWithErrorDetection(
detectDartErrors,
() => WidgetsFlutterBinding.ensureInitialized(),
);

//...

await ChannelClient._channel.invokeMethod('start', <String, dynamic>
  //... Tag2:这里将Tag1处的额外参数传给了对端SDK

);

//Tag3:dart error的处理类,其中全部都是通过channel来桥接的
final client = ChannelClient(detectDartErrors);
client._onErrorCallbacks.addAll(onError);
this.client = client;

_runWithErrorDetection(detectDartErrors, () => runApp?.call());

void _runWithErrorDetection(
bool errorDetectionEnabled,
FutureOr Function() block,
) async
if (errorDetectionEnabled)
//多么熟悉的味道,
await runZonedGuarded(() async
await block();
, _reportZonedError);
else
await block();

//最终_reportZonedError会执行到_notifyInternal
void _notifyUnhandled(dynamic error, StackTrace? stackTrace)
_notifyInternal(error, true, null, stackTrace, null);

ChannelClient(bool autoDetectErrors)
if (autoDetectErrors)
FlutterError.onError = _onFlutterError;

void _onFlutterError(FlutterErrorDetails details)
_notifyInternal(details.exception, true, details, details.stack, null);
//…

找包装类生成

Future _notifyInternal(
dynamic error,
bool unhandled,
FlutterErrorDetails? details,
StackTrace? stackTrace,
BugsnagOnErrorCallback? callback,
) async
final errorPayload =
BugsnagErrorFactory.instance.createError(error, stackTrace);
final event = await _createEvent(
errorPayload,
details: details,
unhandled: unhandled,
deliver: _onErrorCallbacks.isEmpty && callback == null,
);

//…

await _deliverEvent(event);

//我说什么来着:连最基本的Event构造,都是在对端。
Future<BugsnagEvent?> _createEvent(
BugsnagError error,
FlutterErrorDetails? details,
required bool unhandled,
required bool deliver,
) async
final buildID = error.stacktrace.first.codeIdentifier;
//…
;
//调用了对端通道方法来实现。
final eventJson = await _channel.invokeMethod(
‘createEvent’,

‘error’: error,
‘flutterMetadata’: metadata,
‘unhandled’: unhandled,
‘deliver’: deliver
,
);

if (eventJson != null) 
  return BugsnagEvent.fromJson(eventJson);


return null;


操作包装类

本来以为此处要大干一场,结果灰溜溜给了对端。。。,什么都不想说,内心平静毫无波澜~~~

Future _deliverEvent(BugsnagEvent event) =>
_channel.invokeMethod(‘deliverEvent’, event);
主要源码流程看完了,下面来看下Bugsnag我觉得比较好玩的需求和实现。

什么是可追溯异常路径
这个是我自己想的一个词,该需求目的是能完整记录用户操作的整个行为路径,这样达到清晰指导用户操作过程,对问题的定位很有帮助。可以理解成一个小型的埋点系统,只是该埋点系统只是针对异常来做的。

如下:异常产生流程,state被成功加载后用户先进入了主页,然后从主页进入了native-crashes页之后异常就产生了。 对开发者和测试人员来说很容易复现通过如上路径来复现问题。

异常路径后台显示效果

如何实现
前置知识
Bugsnag中将可追溯的路径命名为Breadcrumb,刚开始我不理解,这个单词英文意思:面包屑,跟路径八竿子都扯不上关系,直到查维基百科才发现为什么这么命名,通过一片一片的面包屑才能找到回家的路。。。,老外们还真够有情怀的!

Breadcrumb的命名的含义, 有没有发觉这个名字起得好形象!

页面路径(英语:breadcrumb或breadcrumb trail/navigation),又称面包屑导航,是在用户界面中的一种导航辅助。它是用户一个在程序或文件中确定和转移他们位置的一种方法。面包屑这个词来自糖果屋 这个童话故事;故事中,汉赛尔与葛丽特企图依靠洒下的面包屑找到回家的路。

当然最终这些丢下的面包屑(leaveBreadcrumb)路径数据也是通过调用到对端SDK来实现:

Future leaveBreadcrumb(
String message,
Map<String, Object>? metadata,
BugsnagBreadcrumbType type = BugsnagBreadcrumbType.manual,
) async
final crumb = BugsnagBreadcrumb(message, type: type, metadata: metadata);
await _channel.invokeMethod(‘leaveBreadcrumb’, crumb);

这里主要关注下自动添加面包屑的场景。

如何添加路径
两种方式:

手动添加,通过调用bugsnag.leaveBreadcrumb

自动添加,其中包括两个场景:导航栏跳转和 网络请求

如上两个场景的的实现原理涉及到对应用性能的监控功能,重点分析其中原理。

导航栏自动埋点实现原理
MaterialApp: navigatorObservers 来实现对页面跳转的监听,Bugsnag中是通过自定义BugsnagNavigatorObserver,并在其回调函数中监听导航行为手动调用leaveBreadcrumb方法上报导航信息给后台从而达到监听页面的效果。

注意事项: navigatorObservers是创建导航器的观察者列表,将要观察页面跳转对象放在该列表中,页面中发生导航行为时候,就可以监听到。

如果一个应用中有多个MaterialApp的情况,需要保证每个MaterialApp:navigatorObservers中都有BugsnagNavigatorObserver才行,不然某些MaterialApp中也监控不到。最好是一个应用统一一份MaterialApp减少这种不必要的麻烦。

如下代码中

Bugsnag框架自定义了BugsnagNavigatorObserver对象, 该对象必须继承NavigatorObserver并实现其中回调函数方可放入到MaterialApp:navigatorObservers中,不是随便什么对象都可以放到列表中的。
这样Bugsnag就具有了对整个接入应用导航的监控能力,页面进入或者页面退出行为都可以被监控到。
然后在步骤2回调中手动调用_leaveBreadcrumb 来实现对导航路径的监听。
_leaveBreadcrumb 将数据传送给对端SDK,SDK传输数据给bugsnag后台Breadcrumb 页,也就是上面效果中呈现的。
class ExampleApp extends StatelessWidget
const ExampleApp(Key? key) : super(key: key);

@override
Widget build(BuildContext context)
return MaterialApp(
navigatorObservers: [BugsnagNavigatorObserver()],
//…
);

----[BugsnagNavigatorObserver]----->
// BugsnagNavigatorObserver extends NavigatorObserver
BugsnagNavigatorObserver(
//…
) : _navigatorName = (navigatorName != null) ? navigatorName : ‘navigator’;

@override
void didReplace(Route? newRoute, Route? oldRoute)
_leaveBreadcrumb(‘Route replaced on’,
if (oldRoute != null) ‘oldRoute’: _routeMetadata(oldRoute),
if (newRoute != null) ‘newRoute’: _routeMetadata(newRoute),
);
//…

//…其他回调函数

void _leaveBreadcrumb(String function, Map<String, Object> metadata)
if (leaveBreadcrumbs)
bugsnag.leaveBreadcrumb(
_operationDescription(function),
type: BugsnagBreadcrumbType.navigation,
metadata: metadata,
);


网络请求自动埋点实现原理
通过自定义http.BaseClient实现对默认http.Client中 send方法代理来实现,对请求发送和失败进行统一化监听,并记录了请求时长埋点上报。

推荐个网络监听通用方案: 可以看下didi的Flutter方案: 复写HttpOverride即可,DoKit/dokit_http.dart at master · didi/DoKit

如下

当点击发送网络请求时,会调用Bugsnag自己的http库。
Bugsnag http库中自己实现了Client类,该类复写send方法(该方法在发生网络行为时都会被触发),并在其中做了网络监听的额外埋点操作_requestFinished,其中包括对网络结果反馈和网络请求时间的统计。
例子中最终post会执行client.send,从而完成了对网络自埋点路径的上报。

import ‘package:bugsnag_breadcrumbs_http/bugsnag_breadcrumbs_http.dart’ as http;
void _networkFailure() async =>
http.post(Uri.parse(‘https://example.com/invalid’));

----[bugsnag_breadcrumbs_http.dart]---->
Future<http.Response> post(Uri url,
Map<String, String>? headers, Object? body, Encoding? encoding) =>
_withClient((client) =>
client.post(url, headers: headers, body: body, encoding: encoding));

Future _withClient(Future Function(Client) fn) async
var client = Client();
try
return await fn(client);
finally
client.close();

---->[client.dart]---->
class Client extends http.BaseClient
/// The wrapped client.
final http.Client _inner;

Client() : _inner = http.Client();

Client.withClient(http.Client client) : _inner = client;

@override
Future<http.StreamedResponse> send(http.BaseRequest request) async
final stopwatch = Stopwatch()…start();
try
final response = await _inner.send(request);
//拦截点:这里监听发送成功
await _requestFinished(request, stopwatch, response);
return response;
catch (e)
//拦截点:这里监听发送失败
await _requestFinished(request, stopwatch);
rethrow;

Future _requestFinished(
http.BaseRequest request,
Stopwatch stopwatch, [
http.StreamedResponse? response,
]) =>
_leaveBreadcrumb(Breadcrumb.build(_inner, request, stopwatch, response));

总结
本文主要对可追溯Crash路径自动埋点原理进行分析,该需求是读Bugsnag是觉得想法上有亮点的地方,就重点拎出来说说,结合自身做Flutter异常捕获过程经验,压根没考虑到这种记录异常路径的需求。而且它还做得这么细针对了导航监听和网络监听自动埋点,而这两块又恰恰是对定位问题比较关键的,试问哪个异常出现了你不关注发生的页面,哪个线上App逃得开网络异常。

另外本文也总结阅读Flutter异常监控框架必看的几个关键步骤,结合Bugsnag源码进行实际讲解。其实Flutter异常监控框架来回就那么几个步骤没什么大的变化,主要是看其中有什么亮度的需求并针对需求做了哪些开闭设计,这些才是令人振奋的东西。

参考链接
bugsnag/bugsnag-flutter: Bugsnag crash reporting for Flutter apps
DoKit/Flutter at master · didi/DoKit

作者:听蝉
链接:https://juejin.cn/post/7179426939893973029

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

二、源码解析合集


三、开源框架合集


欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

以上是关于Flutter异常监控 - 叁 | 从bugsnag源码学习如何追溯异常产生路径的主要内容,如果未能解决你的问题,请参考以下文章

技术实践第二期|Flutter异常捕获

在 Flutter 中使用 StreamProvider 4.0.1 从 Firebase 监控身份验证状态

Flutter - 从 websocket 消息动态创建小部件 - 未处理的异常:在构造函数中调用 setState()

前端代码异常监控总结

spring boot 从开发到上线—AOP 异常监控上报

Flutter:未处理的异常:错误状态:调用关闭后无法添加新事件(不一样的情况)