使用框架的功能开发错误的捕获,可以写项目日志,可以记录设备信息,代码如下:
import 'dart:io';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
import 'package:device_info/device_info.dart';
/// ### 记录项目日志、捕获项目错误、缓存所有记录、写入文件、上传服务器
/// * 初始化:
/// ```dart
/// ErrorLog.log = new ErrorLog(
/// reportZone: () async {
/// runApp(new MyApp());
/// },
/// debugMode: true,
/// uploadFile: (file) async {},
/// minutesWait: 30,
/// [fileName: 'error_log.txt']
/// );
/// ```
///
/// ### 项目日志
/// 默认标记的项目日志可以使用相应的方法;自定义标记的项目日志可以使用基础方法。
/// * 使用方式:
/// ```dart
/// ErrorLog.log.debug('msg'*8);
/// ErrorLog.log.info('msg'*8);
/// ErrorLog.log.warn('msg'*8);
/// ErrorLog.log.error('msg'*8);
/// ErrorLog.log.fatal('msg'*8);
/// ErrorLog.log.collectLog('msg'*8, 'error'); // 都是调用这个基础方法
/// ```
/// * 输出格式:
/// #### [2019-04-18 11:50:29.844858][error] msgmsgmsgmsgmsgmsgmsgmsg
///
/// ### 错误报告
/// 错误报告的信息比较多,标记为`report`。
/// * 使用方式:
/// 自动捕获错误,不包含 `try/catch`,不包含 `print`。
/// * 输出格式:
/// #### [2019-04-18 14:05:03.578755][report]
/// #### 所有错误信息
///
/// ### 写入文件
/// 所有记录都缓存在一个数组里,如果`debugMode`为真,打印到控制台;
/// 否则根据数组索引异步写入文件`error_log.txt`,在初始化时可传参`fileName`。
/// * 使用方式:
/// ```dart
/// ErrorLog.log.printBuffer(); // 打印记录缓存
/// ErrorLog.log.clearFile(); // 清空记录文件
/// ErrorLog.log.printFile(); // 打印文件内容
/// ```
///
/// ### 上传服务器
/// 打开应用时上传一次,然后设置计时器,建议30分钟上传一次。调试模式下,没有写入文件,不会调用上传接口。
/// * 使用方式:
/// 初始化时传参`uploadFile`和`minutesWait`,获取记录的文件`ErrorLog.log.logFile`。
///
/// ### 设备信息
/// 使用 [device_info](https://pub.dartlang.org/packages/device_info),应用启动时会获取和记录。
/// * 使用方式:
/// ```dart
/// await ErrorLog.log.getDeviceInfo(); // 异步返回设备信息
/// ```
/// * 输出格式:
/// 字符串,Future
/// #### [2019-04-24 10:05:11.413469][info] 设备信息 [device_info](https://pub.dartlang.org/packages/device_info)
/// #### [androidInfo] androidId: 1a08f53b320ccfef, ...
///
class ErrorLog {
/// 实例,静态属性
static ErrorLog log;
/// 捕获错误的区域
Function _reportZone;
/// 是否调试模式
bool _debugMode;
/// 缓存记录的数组
List _logBuffer;
/// 文件记录
File _logFile;
File get logFile => _logFile;
/// 文件记录的起点
int _startIndex;
/// 文件记录的终点
int _endIndex;
/// 上传记录文件
Function _uploadFile;
/// 上传时间间隔
int _minutesWait;
/// 记录文件是否变化
bool _fileChange;
/// 记录文件的名称
String fileName;
ErrorLog({
@required Function reportZone,
@required bool debugMode,
@required Function uploadFile,
@required int minutesWait,
this.fileName = 'error_log.txt'
}) {
_reportZone = reportZone;
_debugMode = debugMode;
_uploadFile = uploadFile;
_minutesWait = minutesWait;
init();
}
/// 初始化
void init() async {
_logBuffer = [];
_startIndex = 0;
_endIndex = 0;
_fileChange = false;
FlutterError.onError = (FlutterErrorDetails details) {
reportError(details);
};
runZoned(
_reportZone,
onError: (Object obj, StackTrace stack) {
var details = makeDetails(obj, stack);
reportError(details);
}
);
_logFile = await _getLocalFile();
info('应用启动成功。');
getDeviceInfo();
if( !_debugMode ) _uploadFile(_logFile);
Timer.periodic(Duration(minutes: _minutesWait), (timer) async {
if ( _fileChange && !_debugMode ) {
await _uploadFile(_logFile);
_fileChange = false;
}
});
}
/// * 获取设备信息
/// * [device_info](https://pub.dartlang.org/packages/device_info)
Future getDeviceInfo() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
String details = '设备信息[device_info](https://pub.dartlang.org/packages/device_info)\n ';
try {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
details += '[iosInfo] identifierForVendor: ${iosInfo.identifierForVendor}, ';
details += 'isPhysicalDevice: ${iosInfo.isPhysicalDevice}, ';
details += 'localizedModel: ${iosInfo.localizedModel}, ';
details += 'model: ${iosInfo.model}, ';
details += 'name: ${iosInfo.name}, ';
details += 'systemName: ${iosInfo.systemName}, ';
details += 'systemVersion: ${iosInfo.systemVersion}. ';
details += '\n [iosInfo.utsname] machine: ${iosInfo.utsname.machine}, ';
details += 'nodename: ${iosInfo.utsname.nodename}, ';
details += 'release: ${iosInfo.utsname.release}, ';
details += 'sysname: ${iosInfo.utsname.sysname}, ';
details += 'version: ${iosInfo.utsname.version}. ';
} catch(e) {
error('无法获取 ios 设备信息。');
}
try {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
details += '[androidInfo] androidId: ${androidInfo.androidId}, ';
details += 'board: ${androidInfo.board}, ';
details += 'bootloader: ${androidInfo.bootloader}, ';
details += 'brand: ${androidInfo.brand}, ';
details += 'device: ${androidInfo.device}, ';
details += 'display: ${androidInfo.display}, ';
details += 'fingerprint: ${androidInfo.fingerprint}, ';
details += 'hardware: ${androidInfo.hardware}, ';
details += 'host: ${androidInfo.host}, ';
details += 'id: ${androidInfo.id}, ';
details += 'isPhysicalDevice: ${androidInfo.isPhysicalDevice}, ';
details += 'manufacturer: ${androidInfo.manufacturer}, ';
details += 'model: ${androidInfo.model}, ';
details += 'product: ${androidInfo.product}, ';
details += 'supported32BitAbis: ${androidInfo.supported32BitAbis}, ';
details += 'supported64BitAbis: ${androidInfo.supported64BitAbis}, ';
details += 'supportedAbis: ${androidInfo.supportedAbis}, ';
details += 'tags: ${androidInfo.tags}, ';
details += 'type: ${androidInfo.type}. ';
details += '\n [androidInfo.version] baseOS: ${androidInfo.version.baseOS}, ';
details += 'codename: ${androidInfo.version.codename}, ';
details += 'incremental: ${androidInfo.version.incremental}, ';
details += 'previewSdkInt: ${androidInfo.version.previewSdkInt}, ';
details += 'release: ${androidInfo.version.release}, ';
details += 'sdkInt: ${androidInfo.version.sdkInt}, ';
details += 'securityPatch: ${androidInfo.version.securityPatch}. ';
} catch(e) {
error('无法获取 android 设备信息。');
}
info(details);
return details;
}
/// 错误报告
void reportError(FlutterErrorDetails details) {
String errorMeta = '[' + (new DateTime.now().toString()) + '][report]';
_logBuffer.add(errorMeta + '\n' + details.toString());
if (_debugMode) {
print(errorMeta);
print(details.toString());
} else {
_writeFile();
}
}
/// 项目日志
collectLog(String line, String label) {
String contents = '[' + (new DateTime.now().toString()) + '][' + label + '] ' + line;
_logBuffer.add(contents + '\n');
if (_debugMode) {
print(contents);
} else {
_writeFile();
}
}
/// 打印文件
Future printFile() async {
_readLocalFile().then((contents) {
print(contents);
});
}
/// 打印缓存
Future printBuffer() async {
print( _logBuffer.toString() );
}
/// 清空文件
Future clearFile() async {
await _logFile.writeAsString('', mode: FileMode.write);
}
/// 实时写入文件,防止意外
Future _writeFile() async {
int len = _logBuffer.length;
if (len > _endIndex) {
_startIndex = _endIndex;
_endIndex = len;
Iterable range = _logBuffer.getRange(_startIndex, _endIndex);
await _writeLocalFile( range.join('\n') );
_fileChange = true;
}
}
/// 获取文件
Future _getLocalFile() async {
String dir = (await getApplicationDocumentsDirectory()).path;
return new File('$dir/' + fileName);
}
/// 读取文件
Future _readLocalFile() async {
String contents = await _logFile.readAsString();
return contents;
}
/// 写入文件
Future _writeLocalFile(String contents) async {
await _logFile.writeAsString(contents, mode: FileMode.append, flush: false);
}
/// 构建错误信息
FlutterErrorDetails makeDetails(Object obj, StackTrace stack) {
return FlutterErrorDetails(exception: obj, stack: stack);
}
/// 调试
void debug(String msg) {
collectLog(msg, 'debug');
}
/// 信息
void info(String msg) {
collectLog(msg, 'info');
}
/// 警告
void warn(String msg) {
collectLog(msg, 'warn');
}
/// 错误
void error(String msg) {
collectLog(msg, 'error');
}
/// 致命错误
void fatal(String msg) {
collectLog(msg, 'fatal');
}
}
参考的文档:
文件操作
异常捕获
日志处理
设备信息