远程遥控开发中

This commit is contained in:
CC
2026-01-21 13:27:55 +08:00
parent 32b702fb6d
commit 8c39f0daf8
45 changed files with 1994 additions and 245 deletions

View File

@@ -0,0 +1,22 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../logging/i_logger_service.dart';
class AppBlocObserver extends BlocObserver {
final ILoggerService logger;
AppBlocObserver(this.logger);
@override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
super.onError(bloc, error, stackTrace);
logger.captureException(error, stackTrace: stackTrace);
}
@override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
logger.addBreadcrumb(
'Bloc State Change',
category: bloc.runtimeType.toString(),
);
}
}

View File

@@ -0,0 +1,111 @@
// lib/core/infrastructure/logging/sentry_logger_impl.dart
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import '../../env/env_config.dart';
import '../../logging/i_logger_service.dart';
import 'package:intl/intl.dart';
class SentryLoggerImpl implements ILoggerService {
File? _localLogFile;
@override
Future<void> init() async {
await SentryFlutter.init((options) {
options.dsn = EnvConfig.sentryDsn;
options.environment = EnvConfig.environment;
// 生产环境只采样 20% 的性能数据,测试环境全开
options.tracesSampleRate = EnvConfig.isProduction ? 0.2 : 1.0;
});
await _collectDeviceInfo();
await _prepareLocalFile();
}
// 准备本地文件路径
Future<void> _prepareLocalFile() async {
try {
final directory = await getApplicationDocumentsDirectory();
// 按天生成文件名,例如: 2024-05-20_logs.txt
final fileName =
"${DateFormat('yyyy-MM-dd').format(DateTime.now())}_logs.txt";
_localLogFile = File('${directory.path}/$fileName');
if (!await _localLogFile!.exists()) {
await _localLogFile!.create(recursive: true);
}
} catch (e) {
debugPrint("无法创建本地日志文件: $e");
}
}
Future<void> _collectDeviceInfo() async {
final deviceInfo = DeviceInfoPlugin();
Sentry.configureScope((scope) async {
if (Platform.isAndroid) {
final android = await deviceInfo.androidInfo;
scope.setTag('device_model', android.model);
scope.setTag('os_version', android.version.release);
} else if (Platform.isIOS) {
final ios = await deviceInfo.iosInfo;
scope.setTag('device_model', ios.utsname.machine);
scope.setTag('os_version', ios.systemVersion);
}
});
}
@override
void captureException(dynamic exception, {dynamic stackTrace}) {
Sentry.captureException(exception, stackTrace: stackTrace);
}
@override
void log(
String message, {
bool isWarning = false,
Map<String, dynamic>? data,
}) {
final timestamp = DateTime.now().toIso8601String();
final level = isWarning ? 'WARNING' : 'INFO';
final logEntry = "[$timestamp] [$level] $message ${data ?? ''}\n";
// 1.发送日志到 Sentry
Sentry.captureMessage(
message,
level: isWarning ? SentryLevel.warning : SentryLevel.info,
);
// 2.写入本地文件
_writeToLocalFile(logEntry);
if (kDebugMode) {
debugPrint(logEntry);
}
}
Future<void> _writeToLocalFile(String entry) async {
try {
// 使用追加模式写入
await _localLogFile?.writeAsString(entry, mode: FileMode.append);
} catch (e) {
// 本地写入失败不应崩溃
}
}
// 增加一个获取本地日志文件路径的方法,方便你以后做“导出日志”功能
Future<String?> getLocalLogPath() async {
return _localLogFile?.path;
}
@override
void addBreadcrumb(String message, {String? category}) {
Sentry.addBreadcrumb(Breadcrumb(message: message, category: category));
}
@override
void setUser(String userId) {
Sentry.configureScope((scope) => scope.setUser(SentryUser(id: userId)));
}
}