Flutter中如何在Dart Isolate内实现Faro-flutter-sdk的自动HTTP请求日志追踪
首先得明确:Faro-flutter-sdk目前没办法直接在后台Isolate里实现自动HTTP追踪,核心原因是它严重依赖Flutter的MethodChannel与原生通信,而后台Isolate不允许设置MethodCallHandler(这也是你调用Faro.init()时报错的根源);另外,每个Isolate都是独立的沙箱,主Isolate的Faro配置和实例不会自动同步到后台Isolate,就算你手动设置了HttpOverrides.global,后台Isolate里的Faro实例也没法把收集到的事件发送到Collector(因为没有初始化原生通信通道)。
那有没有办法实现类似的效果?当然有,我们可以换个思路:让后台Isolate负责执行HTTP请求,把请求的关键信息通过Isolate间通信传给主Isolate,再让主Isolate里正常工作的Faro实例手动上报这些事件。下面给你两个具体的实现方案:
方案一:通过Isolate通信,手动传递请求事件到主Isolate上报
这个方案的核心是利用Isolate间的SendPort/ReceivePort,把后台Isolate里HTTP请求的响应、错误等信息传给主Isolate,由主Isolate的Faro来完成上报。
步骤1:主Isolate监听事件端口
在启动后台Isolate时,同时创建一个用于接收事件的ReceivePort,然后监听这个端口,收到事件后调用Faro的API手动上报:
// 主Isolate中 void main() async { // 1. 创建用于接收后台Isolate事件的端口 final eventReceivePort = ReceivePort(); // 2. 启动后台Isolate,把主Isolate的事件发送端口传过去 await Isolate.spawn( _startRemoteIsolate, eventReceivePort.sendPort, // 把事件端口传给后台Isolate ); // 3. 监听来自后台Isolate的事件,用主Isolate的Faro上报 eventReceivePort.listen((dynamic data) { if (data is Map<String, dynamic> && data['type'] == 'http_event') { // 手动构建Faro的HTTP事件并上报 Faro().addHttpEvent( url: data['url'], method: data['method'], statusCode: data['statusCode'], duration: data['duration'], error: data['error'], stackTrace: data['stackTrace'] != null ? StackTrace.fromString(data['stackTrace']) : null, ); } }); }
步骤2:后台Isolate执行请求并发送事件
在后台Isolate中,给Dio添加逻辑,在请求完成/失败时收集所有需要的信息,通过SendPort发送给主Isolate:
// 后台Isolate入口 static void _startRemoteIsolate(SendPort eventSendPort) { if (Platform.isIOS || Platform.isAndroid) { final dio = Dio(); // 执行测试请求,同时记录请求开始时间 final startTime = DateTime.now(); dio.get('https://domain.name.here/endpoint') .then((response) { final duration = DateTime.now().difference(startTime).inMilliseconds; // 把响应信息打包发送给主Isolate eventSendPort.send({ 'type': 'http_event', 'url': response.realUri.toString(), 'method': response.requestOptions.method, 'statusCode': response.statusCode, 'duration': duration, 'error': null, 'stackTrace': null, }); print('RESPONSE: $response'); }) .onError((err, stackTrace) { final duration = DateTime.now().difference(startTime).inMilliseconds; // 把错误信息打包发送给主Isolate eventSendPort.send({ 'type': 'http_event', 'url': err.requestOptions.uri.toString(), 'method': err.requestOptions.method, 'statusCode': err.response?.statusCode, 'duration': duration, 'error': err.toString(), 'stackTrace': stackTrace.toString(), }); print('ERROR: $err\n$stackTrace'); }); } }
方案二:给后台Isolate的Dio添加自定义拦截器
如果后台Isolate里有多个HTTP请求,一个个手动写事件发送逻辑太麻烦,我们可以封装一个Dio拦截器,自动收集请求信息并发送到主Isolate:
1. 定义自定义拦截器
class IsolateHttpReportInterceptor extends Interceptor { final SendPort eventSendPort; IsolateHttpReportInterceptor(this.eventSendPort); @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { // 记录请求开始时间,存在options的extra里 options.extra['startTime'] = DateTime.now(); super.onRequest(options, handler); } @override void onResponse(Response response, ResponseInterceptorHandler handler) { final startTime = response.requestOptions.extra['startTime'] as DateTime; final duration = DateTime.now().difference(startTime).inMilliseconds; // 发送成功事件 eventSendPort.send({ 'type': 'http_event', 'url': response.realUri.toString(), 'method': response.requestOptions.method, 'statusCode': response.statusCode, 'duration': duration, 'error': null, 'stackTrace': null, }); super.onResponse(response, handler); } @override void onError(DioException err, ErrorInterceptorHandler handler) { final startTime = err.requestOptions.extra['startTime'] as DateTime; final duration = DateTime.now().difference(startTime).inMilliseconds; // 发送错误事件 eventSendPort.send({ 'type': 'http_event', 'url': err.requestOptions.uri.toString(), 'method': err.requestOptions.method, 'statusCode': err.response?.statusCode, 'duration': duration, 'error': err.toString(), 'stackTrace': err.stackTrace?.toString(), }); super.onError(err, handler); } }
2. 后台Isolate中使用这个拦截器
static void _startRemoteIsolate(SendPort eventSendPort) { if (Platform.isIOS || Platform.isAndroid) { final dio = Dio(); // 添加自定义拦截器 dio.interceptors.add(IsolateHttpReportInterceptor(eventSendPort)); // 执行任意Dio请求,拦截器会自动收集并发送事件 dio.get('https://domain.name.here/endpoint') .then((msg) => print('RESPONSE: $msg')) .onError((err, st) => print('ERROR: $err\n$st')); } }
为什么你之前的尝试没成功?
最后再帮你复盘下之前的问题:
- 直接设置Faro().config:这个操作只是修改了Faro实例的配置,但没有初始化Faro的核心服务(比如原生通信通道、日志收集器),所以就算
HttpOverrides捕获了请求,Faro也没法把事件发送到Collector。 - 调用Faro.init()加BackgroundIsolateBinaryMessenger:Faro的
init()方法会初始化NativeIntegration,而这个集成会调用MethodChannel.setMethodCallHandler(),但后台Isolate不允许设置MethodCallHandler,所以直接抛出了错误。
目前Faro-flutter-sdk还没有官方支持后台Isolate的自动追踪,所以上面的间接方案是现阶段最可行的解决方式。
内容来源于stack exchange

