48 lines
2.0 KiB
Dart
48 lines
2.0 KiB
Dart
/// Provider 层的全局错误出口。来源:conti-docs/12-error-and-api-contract.md §五。
|
|||
|
|
library;
|
||
|
|
|
||
|
|
import 'package:core_foundation/core_foundation.dart';
|
||
|
|
import 'package:core_logging/core_logging.dart';
|
||
|
|
import 'package:core_ui/core_ui.dart';
|
||
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
|
|
||
|
|
/// 所有 provider 抛出的异常都会经过这里。
|
||
|
|
///
|
||
|
|
/// ---------------------------------------------------------------------------
|
||
|
|
/// 它是**兜底**,不是主路径:UI 该显示的错误由 `AsyncValueView` 负责,这里只
|
||
|
|
/// 负责"这个异常有没有人处理过"之外的另一件事——落日志和上报。
|
||
|
|
///
|
||
|
|
/// 三类要**主动排除**,否则线上告警会被噪音淹没:
|
||
|
|
/// - [BusinessException]:后端明确告诉我们"这个操作不允许",是预期内的流程
|
||
|
|
/// 分支(余额不足、单据已关闭),不是缺陷;
|
||
|
|
/// - [UnauthorizedException]:登录过期,SessionNotifier 已经在处理了;
|
||
|
|
/// - [RequestCancelledException]:用户切走了页面,请求被主动取消。
|
||
|
|
/// ---------------------------------------------------------------------------
|
||
|
|
final class ErrorObserver extends ProviderObserver {
|
||
|
|
/// 构造。
|
||
|
|
ErrorObserver({required this.logger, required this.reporter});
|
||
|
|
|
||
|
|
/// 日志出口。
|
||
|
|
final AppLogger logger;
|
||
|
|
|
||
|
|
/// 崩溃上报出口。
|
||
|
|
final CrashReporter reporter;
|
||
|
|
|
||
|
|
@override
|
||
|
|
void providerDidFail(ProviderObserverContext context, Object error, StackTrace stackTrace) {
|
||
|
|
final String name = context.provider.name ?? context.provider.runtimeType.toString();
|
||
|
|
|
||
|
|
if (ErrorPresenter.isSilent(error)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (error is BusinessException) {
|
||
|
|
// 记一条 info 就够:需要它来复盘"用户为什么走不下去",但它不是缺陷。
|
||
|
|
logger.i('业务拒绝 $name: ${error.code} ${error.message}');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
logger.e('provider 失败 $name', error: error, stackTrace: stackTrace);
|
||
|
|
reporter.report(error, stackTrace, extra: <String, String>{'provider': name});
|
||
|
|
}
|
||
|
|
}
|