Flutter異常監(jiān)控 - 肆 | Rollbar源碼賞析

一. Rollbar可以幫你解決哪些問題

無特別說明,文中Rollbar統(tǒng)指Rollbar-flutter

1. 代碼復(fù)用

Untitled.png

Rollbar官方文檔說是純Dart實(shí)現(xiàn),該特征意味著自帶”代碼復(fù)用”光環(huán)。

如圖當(dāng)接入端(Third-APP)調(diào)用Rollbar SDK時(shí)表示包含的網(wǎng)絡(luò)(異常數(shù)據(jù)上傳等)和存儲(chǔ)(異常存儲(chǔ)管理)可達(dá)到復(fù)用效果。

Untitled 1.png

若Flutter異常監(jiān)控框架非純Dart實(shí)現(xiàn)(第三篇中Bugsnag),就存在代碼無法復(fù)用問題,如圖,Dart-Crash-SDK是這層殼依賴對(duì)端SDK,最終導(dǎo)致各平臺(tái)(android,ios,…)都須對(duì)端SDK(android-crash-sdk, ios-crash-sdk,…)適配,導(dǎo)致網(wǎng)絡(luò)和存儲(chǔ)邏輯對(duì)端SDK都須各自實(shí)現(xiàn)一遍,嚴(yán)重邏輯重復(fù)。

Untitled 2.png

由此在做軟件多端架構(gòu)設(shè)計(jì)時(shí),Dart側(cè)可理解成是多平臺(tái)公共代碼集合,如果存在多端邏輯功能代碼完全可以抽離到Dart側(cè)以復(fù)用,減少測(cè)試和人力成本。

2. 定制包裝操作

前面兩篇文章我們知道,捕獲到原始異常后對(duì)其中的Error和StackTrace有相當(dāng)部分的工作是對(duì)原始異常數(shù)據(jù)的包裝再將包裝類數(shù)據(jù)發(fā)送給對(duì)端或者后臺(tái),不同框架包裝過程是不一樣的,如下圖中Catcher框架包裝后類對(duì)象是Report,Bugsnag對(duì)異常進(jìn)行兩次包裝,第一次是BugsnagError,第二次是BugsnagEvent。

Untitled 3.png

這很好理解,因?yàn)閷?duì)于同一事物不同框架的需求是不一樣的,不同需求注定了不同的包裝行為。
原始異常數(shù)據(jù)就像一條魚,口味清淡的Catcher選擇清蒸,重口味的Bugsnag選擇紅燒,不同框架就是不同口味的吃魚人。而Rollbar 將包裝行為抽象化,將原始的魚以某種方式提供給你,讓你享受自由烹飪樂趣。

3. 線程切換

異常產(chǎn)生后有很多耗時(shí)操作,如原始異常數(shù)據(jù)包裝中存在讀取額外字段,異常的存儲(chǔ),查詢,加密,上報(bào)等。耗時(shí)操作都在main isolate 中做, 勢(shì)必會(huì)影響到main isolate的UI 構(gòu)建等行為,異常數(shù)據(jù)量比大時(shí)UI會(huì)有卡頓情況,就像圖中情況,

Untitled 4.png

Rollbar支持將異常耗時(shí)處理操作交給子isolate(crash isolate),讓main isolate保持專注做UI構(gòu)建等以提高應(yīng)用的流暢度。

Untitled 5.png

4. 追溯生成路徑

該需求與第三篇Flutter異常監(jiān)控 - 叁 |從bugsnag源碼學(xué)習(xí)如何追溯異常產(chǎn)生路徑 相同

該需求目的是能完整記錄用戶操作的整個(gè)行為路徑,這樣達(dá)到清晰指導(dǎo)用戶操作過程,對(duì)問題的定位很有幫助。可以理解成一個(gè)小型的埋點(diǎn)系統(tǒng),只是該埋點(diǎn)系統(tǒng)只是針對(duì)異常來做的。

區(qū)別在代碼層面實(shí)現(xiàn),bugsnag中有自動(dòng)添加和手動(dòng)添加路徑兩種情況,Rollbar中只有手動(dòng)添加,但是手動(dòng)添加分類更加細(xì)化,比如圖中將Breadcrumb構(gòu)造過程被分成Breadcrumb.error、Breadcrumb.navigation、Breadcrumb.widget、Breadcrumb.log 對(duì)應(yīng)不同圖標(biāo)事件。

Untitled 6.png

話說,追溯異常生成路徑需求是標(biāo)配么? 目前看Bugsnag和Rollbar都有實(shí)現(xiàn)。

二. 如何使用

  1. 將包添加到您的文件中:pubspec.yaml
dependencies:
  rollbar_flutter: ^0.3.0-beta
  1. 運(yùn)行 flutter pub get

代碼中配置:

import 'package:rollbar_flutter/rollbar.dart';
Future<void> main() async {
  const config = Config(
      //accessToken到https://rollbar.com/注冊(cè)獲取
      accessToken: 'YOUR-ROLLBAR-ACCESSTOKEN',
      package: 'rollbar_flutter_example');

  await RollbarFlutter.run(config, () {
    runApp(const MyApp());
  });
}
  1. 要求
  • Dart SDK >= 2.7.0
  • Flutter >= 1.20.0
  • A Rollbar account

三. 原理解析

Rollbar是Flutter異??蚣埽?dāng)然少不了讀這類源碼套路,直接拿出第三篇文章中的通用閱讀路徑, 按照如下流程一步步走:

image.png

1. Flutter異常監(jiān)控點(diǎn)

  1. 接入端通過RollerFlutter.run 進(jìn)入到Rollbar內(nèi)部邏輯。
    重點(diǎn)關(guān)注Config中默認(rèn)的四個(gè)變量:
  • Notifier:控制發(fā)送事件是通過主線程還是其他線程中發(fā)送。
  • Transformer:對(duì)異常數(shù)據(jù)進(jìn)行轉(zhuǎn)換的轉(zhuǎn)換器。
  • Wrangler: 提供對(duì)異常數(shù)據(jù)二次包裝機(jī)會(huì)返回最終發(fā)送的真實(shí)數(shù)據(jù)。
  • Sender: 將Wrangler提供的真實(shí)數(shù)據(jù)發(fā)送。
Untitled 8.png
Untitled 9.png
  1. 通過FlutterError.onError(21行)和runZonedGuarded(13行)兩個(gè)監(jiān)控點(diǎn)邏輯處理,將異常收攏到Rollbar.error方法中
Untitled 10.png
  1. 將原始異常以Event方式交給Notifier.notify(15行)。
Untitled 11.png
  1. 通過步驟1中Config提供默認(rèn)實(shí)現(xiàn)知道步驟3中_notifier是IsolatedNotifier,這樣下圖中(14行)事件最終會(huì)發(fā)送到子線程中(45行)。

這里主要涉及到isolate雙向通信知識(shí),不清楚可以看下這個(gè)帖子Flutte 指北 -> Isolate

  • 40~43 : 實(shí)際拿到的是步驟1中傳入的幾個(gè)默認(rèn)值,其中telemetry變量可以理解成數(shù)據(jù)庫封裝對(duì)象用來緩存異常數(shù)據(jù)的。
  • 46~49 : 在轉(zhuǎn)換Event之前,需要對(duì)數(shù)據(jù)庫中緩存的異常進(jìn)行處理,其中數(shù)據(jù)庫中緩存數(shù)據(jù)有兩類1. breadcrumb 2. Event 。49會(huì)對(duì)正常Event進(jìn)行過期判斷,如果過期就刪除掉。
  • 51~53: 這個(gè)通過默認(rèn)wrangler獲取真實(shí)數(shù)據(jù)。
  • 54:sender發(fā)送真實(shí)數(shù)據(jù)到服務(wù)器等。
Untitled 12.png

至此流程圖如下:

<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8c0473a57fb949378c72a6d485300d03~tplv-k3u1fbpfcp-watermark.image?" alt="Untitled 13.png" width="30%" />

2. 生成異常包裝類

  1. 10行:Event轉(zhuǎn)換成Data對(duì)象,主要是添加一些除了Error和StackTrack之外信息。比如客戶端信息(當(dāng)前OS系統(tǒng),OS版本,dart版本,平臺(tái)CPU內(nèi)核數(shù)目等)、包名,事件等級(jí),環(huán)境等。
  2. 11行:Data對(duì)象交給Transformer轉(zhuǎn)換器,讓開發(fā)者可以自定義自己的轉(zhuǎn)換行為。
  3. 12行:返回最終真實(shí)數(shù)據(jù)Payload。
Untitled 14.png

異常數(shù)據(jù)包裝流程:

Untitled 15.png

3. 操作包裝類

上面步驟中經(jīng)過對(duì)Event二次封裝,生成最終包裝類為Payload, 最后該類轉(zhuǎn)換成字符串發(fā)送到Rollbar后臺(tái)。

Untitled 16.png

四.如何進(jìn)行線程切換

上面分析可知線程切換通過Notifier實(shí)現(xiàn),線程切換思路:通過Config配置自定義Notifier值來指定異常處理運(yùn)行線程,AsyncNotifier是main UI isolate, IsolateNotifier會(huì)新建子線程執(zhí)行異常相關(guān)操作。

Notifier定義

abstract class Notifier {
  // notifier version to be updated with each new release: [todo] automate
  static const version = '0.4.0-beta';
  static const name = 'rollbar-dart';

  Sender get sender;
  Wrangler get wrangler;
  Telemetry get telemetry;

  FutureOr<void> notify(Event event);
  FutureOr<void> dispose();
}

Notifier及子類關(guān)系圖

Untitled 17.png

子isolate處理好處

默認(rèn)初始化IsolatedNotifier.spwan 將產(chǎn)生一個(gè)新線程。

總結(jié)了幾點(diǎn)好處:

  1. 發(fā)送事件之前Telemetry會(huì)做數(shù)據(jù)庫相關(guān)增加,查詢和刪除操作,這個(gè)耗時(shí)。
  2. Wrangler對(duì)象會(huì)通過Transformer對(duì)Event進(jìn)行二次保證操作,這個(gè)過程也可能耗時(shí)。
  3. Sender.send發(fā)送事件的時(shí)候,如果當(dāng)前應(yīng)用某個(gè)時(shí)間段異常頻繁,在主線程也可能影響UI。

綜上將可能耗時(shí)都放到異步線程,可以提高主線程流暢性。

五. 如何定制包裝類

上面分析可知,包裝過程通過Transformer來實(shí)現(xiàn),自定義包裝類思路:通過Config配置自定義Transformer值來實(shí)現(xiàn)自定義處理異常數(shù)據(jù)邏輯,可以進(jìn)行加密等。

Transformer定義

abstract class Transformer {
  FutureOr<Data> transform(Data data, {required Event event});
}

Transformer子類

Config默認(rèn)實(shí)現(xiàn)是這個(gè),如果想自定義數(shù)據(jù)包裝過程,可以復(fù)寫其中transform,對(duì)其中date和event操作。

class NoopTransformer implements Transformer {
  const NoopTransformer(Config _);

  @override
  Data transform(Data data, {required Event event}) => data;
}

六. 設(shè)計(jì)模式相關(guān)

1. 單一職責(zé)原則

類功能抽象精準(zhǔn),清晰的職能分工:

  1. Isolate切換模塊,Notifier 子類實(shí)現(xiàn)。
  2. 轉(zhuǎn)換模塊: Transformer 對(duì)象給了自定義和默認(rèn)的轉(zhuǎn)換方式。
  3. 傳輸模塊:Wrangler 將提供最終真實(shí)數(shù)據(jù)并傳輸給sender。
  4. 發(fā)送模塊:Sender 子類實(shí)現(xiàn),可以擴(kuò)展出httpSender等。
  5. 存儲(chǔ)模塊:Telemetry 對(duì)數(shù)據(jù)庫的包裝,可插入,查詢 異常和異常路徑對(duì)象。

2. 可插拔設(shè)計(jì)

可插拔意味更自由的功能和更開閉的設(shè)計(jì)。Rollbar像堆積木一樣,將包裝,傳輸,發(fā)送,存儲(chǔ)通過組合方式統(tǒng)一配置起來更具靈活性。

Untitled 18.png

通過非空命名構(gòu)造函數(shù)提供默認(rèn)實(shí)現(xiàn),模塊直接是以組合配置,外部可設(shè)置和替換,滿足開閉原則。

const Config({
    this.notifier = IsolatedNotifier.spawn,
    this.wrangler = DataWrangler.new,
    this.transformer = NoopTransformer.new,
    this.sender = PersistentHttpSender.new,
  });

PS: 一直沒想明白Dart中構(gòu)造函數(shù)的多非空可選參數(shù)與構(gòu)建者模式有啥不同,感覺前者完全可以替換構(gòu)建者模式的場(chǎng)景,哪位大佬能告訴我應(yīng)用場(chǎng)景區(qū)別?

七. 其他

考慮到篇幅原因,文章分析了主要流程,其實(shí)還有很多點(diǎn)值得學(xué)習(xí)和借鑒。如

  1. 異常存儲(chǔ)和序列化相關(guān)邏輯。
  2. 多stacktrace處理,例如:Android平臺(tái)中的PlatformException。
  3. Dart2.15中構(gòu)造函數(shù)拆分。

八. 問題及說明

  1. 官方flutter還是beta版本官網(wǎng)創(chuàng)建項(xiàng)目的時(shí)候沒有flutter項(xiàng)目圖標(biāo)選擇,可以不選,直接將客戶端accesstoken拿到example中即可。
  2. 在發(fā)送過程中會(huì)報(bào)accesstoken的錯(cuò)誤,這個(gè)是因?yàn)橹癮ccesstoken配置錯(cuò)誤的情況下記錄沒發(fā)送出去導(dǎo)致的,將應(yīng)用卸載或者應(yīng)用數(shù)據(jù)庫刪掉后,再用最新的accesstoken測(cè)試即可。

九. 優(yōu)點(diǎn)和缺點(diǎn)

優(yōu)點(diǎn)

  1. 支持發(fā)送線程切換。
  2. 支持dart層數(shù)據(jù)庫保持?jǐn)?shù)據(jù)。
  3. 支持多stacktrace處理,例如:Android平臺(tái)中的PlatformException。
  4. 整個(gè)流程看起來比較順暢,組件間分工明確,且支持config可配置。
  5. 支持追溯異常路徑。

缺點(diǎn)

  1. 異常追溯路徑?jīng)]有針對(duì)導(dǎo)航和網(wǎng)絡(luò)進(jìn)行自動(dòng)埋點(diǎn)的設(shè)計(jì)都是手動(dòng)埋點(diǎn)有些費(fèi)事,這完全可以借鑒Bugsnag來做。
  2. 雖然Rollbar官方說是純Dart實(shí)現(xiàn),但是它存儲(chǔ)相關(guān)底層用了sqlite3,這玩意是通過通道來實(shí)現(xiàn)的,非純Dart實(shí)現(xiàn)存在依賴對(duì)端原生功能的風(fēng)險(xiǎn),是否可以考慮用純Dart的hive來替換。

十. 參考鏈接

Flutter異常監(jiān)控 - 叁 | 從bugsnag源碼學(xué)習(xí)如何追溯異常產(chǎn)生路徑 - 掘金

Releases · rollbar/rollbar-flutter

Flutter

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容