日志输出工具梳理,支持输出到控制台、本地等,支持颜色分级显示,效果如下:
控制台效果:
输出到文件效果:
所需依赖库:
logger: ^1.1.0
path_provider: ^2.0.10
所有代码:
import 'package:basic_frame/util/logger/my_log_output.dart';
import 'package:logger/logger.dart';
///log输出:输出到控制台和本地缓存文件
///输出形式如下:
/// ┌──────────────────────────
/// │ Error info
/// ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
/// │ Method stack history
/// ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
/// │ Log message
/// └──────────────────────────
class LoggerUtil {
final String defaultTag = 'daemon_li';
///栈方法追溯数量
final int _methodCount = 5;
///显示的日志级别
final Level _level = Level.verbose;
LoggerUtil._internal();
factory LoggerUtil() => _instance;
static late final LoggerUtil _instance = LoggerUtil._internal();
Logger? logger;
///初始化log输出设置
void _init() {
logger ??= Logger(
//输出该级别以上的日志
level: _level,
filter: _MyLogFilter(),
//log输出到控制台和本地缓存文件,
output: MyLogOutput(),
printer: PrettyPrinter(
methodCount: _methodCount, //方法调用追溯数量
printTime: true,
));
}
///最低级log:1
void v(message, {String? tag}) {
_init();
tag = tag == null ? defaultTag: '${defaultTag}_$tag';
logger!.v('$tag: $message');
}
///debug级:2
void d(message, {String? tag}) {
_init();
tag = tag == null ? defaultTag: '${defaultTag}_$tag';
logger!.d('$tag: $message');
}
///普通级信息输出:3
void i(message, {String? tag}) {
_init();
tag = tag == null ? defaultTag: '${defaultTag}_$tag';
logger!.i('$tag: $message');
}
///警告级:3
void w(message, {String? tag}) {
_init();
tag = tag == null ? defaultTag: '${defaultTag}_$tag';
logger!.w('$tag: $message');
}
///严重级:5
void e(message, {String? tag}) {
_init();
tag = tag == null ? defaultTag: '${defaultTag}_$tag';
logger!.e('$tag: $message');
}
///超级严重级:6
void wtf(message, {String? tag}) {
_init();
tag = tag == null ? defaultTag: '${defaultTag}_$tag';
logger!.wtf('$tag: $message');
}
}
///日志输出过滤器,可用于控制输出仅仅符合指定级别条件的log
class _MyLogFilter extends LogFilter {
@override
bool shouldLog(LogEvent event) {
//仅仅输出某一级别日志
// if (event.level == Level.debug) {
// return true;
// } else {
// return false;
// }
//默认输出所有级别log
return true;
}
}
//////////////////自定义输出工具////////////////////////////////
import 'dart:io';
import 'package:logger/logger.dart';
import 'package:path_provider/path_provider.dart';
///日志输出到控制台和文件中
///log输出方法在异步方法中被调用,会造成方法调用无法回溯
///输出到文件需要初始化获取文件路径,获取文件路径是异步方法,这里移到内部初始化,避免影响到方法栈回溯
class MyLogOutput extends LogOutput {
IOSink? _sink;
//log文件父级目录
Directory? _directory;
//log文件后缀
final fileSuffix = '.txt';
@override
void output(OutputEvent event) {
// ignore: avoid_print
event.lines.forEach(print);
_logToFile(event);
}
void _logToFile(OutputEvent event) {
//输出到文件的log,为避免部分颜色相关字符乱码,需要处理
OutputEvent newEvent = event;
switch(event.level) {
case Level.info:
var lines = event.lines.map((e) => e.substring(10, e.length -4)).toList();
newEvent = OutputEvent(event.level, lines);
break;
case Level.debug:
//debug不含特殊编码字符,无需处理
break;
default:
var lines = event.lines.map((e) => e.substring(11, e.length -4)).toList();
newEvent = OutputEvent(event.level, lines);
}
//错误级别以上log输出到文件显示方法栈回溯
//一般性log输出到文件不显示方法栈回溯;
OutputEvent lastEvent;
switch(newEvent.level) {
case Level.verbose:
case Level.debug:
case Level.info:
case Level.warning:
final length = newEvent.lines.length;
final date = newEvent.lines.elementAt(length - 4);
final content = newEvent.lines.elementAt(length - 2);
lastEvent = OutputEvent(newEvent.level, ['$date$content']);
break;
default:
{
lastEvent = newEvent;
}
}
//初始化输出文件路径
if (_directory == null || !_directory!.existsSync()) {
getExternalStorageDirectory().then((direct) {
_directory = Directory('${direct!.path}/log');
if (!_directory!.existsSync()) {
_directory!.createSync(recursive: true);
}
_write(lastEvent);
});
} else {
_write(lastEvent);
}
}
///此方法调用需保证[_directory]已初始化
void _write(OutputEvent event) {
//以日期作为log文件名
var file = File(_getFileLogPath(DateTime.now()));
if (!file.existsSync()) {
//文件不存在后,重写获取指向新文件的句柄
_sink = file.openWrite(
mode: FileMode.writeOnlyAppend,
);
}
//这里避免_sink未初始化
_sink ??= file.openWrite(
mode: FileMode.writeOnlyAppend,
);
_sink?.writeAll(event.lines, '\n');
_sink?.writeln();
_deleteLogFile(_directory!.path);
}
@override
void destroy() async {
await _sink?.flush();
await _sink?.close();
}
///输出log文件缓存,仅仅保留两天的log
void _deleteLogFile(String directory) {
//获取最近两天log文件名称
final now = DateTime.now();
final nowLogPath = _getFileLogPath(now);
final yesterdayLogPath = _getFileLogPath(DateTime.fromMillisecondsSinceEpoch(now.millisecondsSinceEpoch - 24 * 60 * 60 * 1000));
final target = Directory(directory);
//筛选log目录下所有文件
if (target.existsSync()) {
target.listSync().forEach((element) {
//不是最近两天的文件都删除
if (element.path != nowLogPath && element.path != yesterdayLogPath) {
element.deleteSync();
}
});
}
}
String _getFileLogPath(DateTime time) {
final nowLogName = '${time.year}-${time.month}-${time.day}$fileSuffix';
return '${_directory?.path}/$nowLogName';
}
}
release版应用包获取debug运行时log
- 手机连接电脑;
- 终端输入命令flutter logs;