前言
- sentry,一个线上异常监控平台,类似国内的Bugly,但要收费,Bugly用免费版就基本足够
导入依赖
dependencies:
sentry_flutter: ^8.12.0
初始化SDK
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = (errorDetails) {
Sentry.captureException(
errorDetails,
stackTrace: errorDetails.stack,
);
};
PlatformDispatcher.instance.onError = (error, stack) {
Sentry.captureException(
error,
stackTrace: stack,
);
return true;
};
await SentryFlutter.init(
(options) {
options.dsn = 'https://8e0d1dc077f7530d6e3d8f89b417ec18@o4508619471716352.ingest.us.sentry.io/4508645179588608';
options.tracesSampleRate = 1.0;
options.profilesSampleRate = 1.0;
},
appRunner: () => {
// 执行原本的初始化逻辑
runApp(const MyApp());
}),
;
}
捕获Dio日志
Future<dynamic> post(String url,
{dynamic body,
Map<String, dynamic>? query,
bool isBodyEncrypt = true,
bool isFileUpload = false,
Function? callback}) async {
LoggerUtils.w('send url:$url');
LoggerUtils.w('send body:$body');
if (isBodyEncrypt) {
String bodyJson = jsonEncode(body);
body = DecryptUtils.getInstance().encrypt(bodyJson);
}
_dio.options.method = 'POST';
if (isFileUpload) {
_dio.options.connectTimeout = const Duration(seconds: zip6in1Timeout);
_dio.options.receiveTimeout = const Duration(seconds: zip6in1Timeout);
} else {
_dio.options.connectTimeout = const Duration(seconds: commonTimeout);
_dio.options.receiveTimeout = const Duration(seconds: commonTimeout);
}
Response response;
try {
url = DecryptUtils.getInstance().encrypt(url, isUrl: true);
response = await _dio.post(url, data: body, queryParameters: query);
} on DioException catch (error) {
// Sentry上报异常
captureException(error, null);
// 请求超时
if (error.type == DioExceptionType.connectionTimeout ||
error.type == DioExceptionType.sendTimeout ||
error.type == DioExceptionType.receiveTimeout) {
callback?.call(TextConfig.networkTimeout);
} else {
callback?.call(TextConfig.networkError);
}
return null;
} catch (error) {
callback?.call(TextConfig.networkError);
return null;
}
return parseResponse(response, callback: callback);
}
/// Sentry捕获日志
void captureException(DioException error, Map? extra) {
String decrypt16tPath = "";
try {
if (error.requestOptions.path.isNotEmpty &&
!error.requestOptions.path.contains("/")) {
decrypt16tPath =
DecryptUtils.getInstance().encrypt(error.requestOptions.path);
// encryptUtil.decryptDataReturnStr(error.requestOptions.path);
} else {
decrypt16tPath = error.requestOptions.path;
}
} catch (_) {
decrypt16tPath = error.requestOptions.path;
}
int code = -99999;
Map<dynamic, dynamic>? map;
Map? responseData;
if (error.requestOptions.data != null) {
if (error.requestOptions.data is! Map) {
try {
// if (NetConfig.isOpenEncrypt) {
var data = error.requestOptions.data;
map = jsonDecode(DecryptUtils.getInstance().decrypt(data.toString()))
as Map<dynamic, dynamic>;
// map = jsonDecode(encryptUtil.decryptDataReturnStr(data.toString()));
// }
} catch (e) {
print(e.toString());
}
} else {
map = error.requestOptions.data as Map<dynamic, dynamic>;
}
}
if (error.response != null && error.response!.statusCode != null) {
code = error.response!.statusCode ?? -99999;
}
if (error.response != null && error.response!.data != null) {
responseData = error.response!.data as Map;
}
print('httpCode=$code\n'
'errorMessage=${error.message ?? ""}\n'
'responseData=${(responseData ?? {}).toString()}\n'
'requestOptions=${(map ?? {}).toString()}\n'
'errorType=${error.type ?? ''}\n'
'extra=${(extra ?? {}).toString()}\n'
'url=$decrypt16tPath \n');
Sentry.captureMessage(
'httpCode=$code\n'
'errorMessage=${error.message ?? ""}\n'
'responseData=${(responseData ?? {}).toString()}\n'
'requestOptions=${(map ?? {}).toString()}\n'
'errorType=${error.type ?? ''}\n'
'extra=${(extra ?? {}).toString()}\n'
'url=$decrypt16tPath \n',
);
Sentry.captureException(
error,
stackTrace: error.stackTrace,
);
}
Sentry监控日志和AF埋点
// 注意: af和打日志,统一调这个函数
static void debugLog(Object? object) {
if (!CGAppBaseConfig.productionEnvironment) {
debugPrint('${object ?? ''}');
}
Sentry.addBreadcrumb(
Breadcrumb(message: object.toString(), level: SentryLevel.info));
}
Sentry监控home键和返回键
/// 物理返回键
late final main_physics_back = 'main_physics_back'.create;
Future<bool> onWillPop() async {
BurialPointEvent.homePage.main_physics_back.send();
return true;
}
}
- 按home键去到桌面:main_back_desktop
- 从桌面回到app中:main_from_desktop_enter
class XXXpage with WidgetsBindingObserver {
@override
void initState() {
super.initState();
// 监听前后台切换
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
// 应用从后台,回到前台
if (state == AppLifecycleState.resumed) {
LoggerUtils.d("App => Foreground");
Sentry.addBreadcrumb(Breadcrumb(
message: 'main_from_desktop_enter', level: SentryLevel.info));
} else if (state == AppLifecycleState.paused) {
// 应用进入后台
LoggerUtils.d("App => Background");
Sentry.addBreadcrumb(
Breadcrumb(message: 'main_back_desktop', level: SentryLevel.info));
}
}
@override
void dispose() {
// 注销观察者
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}