虽然 flutter/原生给我们提供了日志打印的功能,但是超出一定长度以后会被截断
Json打印挤在一起看不清楚
堆栈打印深度过深多打印一些不需要的东西
实现 log 的多种展示方式
为了可以实现对日志的多种内容格式化和各种显示输出所以抽出来以下几个类
打印类核心的功能就是打印日志 所以它有一个方法就是打印的方法
而我们要打印输出的内容有 当前 log等级 log的tag 需要打印的数据 当前堆栈信息 亦或是获取的Json数据
/// 日志打印输出的接口类
abstract class IHCLogPrint {
void logPrint({
required LogType type,
required String tag,
required String message,
StackTrace? stackTrace,
Map? json,
});
}
这里定义一个IHCLogFormatter抽象类
///格式化的接口类
abstract class IHCLogFormatter {
String format(T data);
}
堆栈的格式例如这样
#0 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
#1 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
#2 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
…
会返回来很多无用的数据 而我们实际用到的也不过前五层就可以了
所以需要一个工具来剔除无用的数据和当前自己的包名
class StackTraceUtil {
///正则表达式 表示#+数字+空格的格式
static final RegExp _startStr = RegExp(r'#\d+[\s]+');
///正则表达式表示 多个非换行符+ (非空) 正则表达式中()代表子项 如果需要正则()需要转义\( \)
///了解更多 https://www.runoob.com/regexp/regexp-syntax.html
static final RegExp _stackReg = RegExp(r'.+ \(([^\s]+)\)');
/// 把StackTrace 转成list 并去除无用信息
/// [stackTrace] 堆栈信息
///#0 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
static List _fixStack(StackTrace stackTrace) {
List tempList = stackTrace.toString().split("\n");
List stackList = [];
for (String str in tempList) {
if (str.startsWith(_startStr)) {
//又是#号又是空格比较占地方 这里省去了 如果你不想省去直接传入str即可
stackList.add(str.replaceFirst(_startStr, ' '));
}
}
return stackList;
}
///获取剔除忽略包名及其其他无效信息的堆栈
/// [stackTrace] 堆栈
/// [ignorePackage] 需要忽略的包名
static List _getRealStackTrack(
StackTrace stackTrace, String ignorePackage) {
///由于Flutter 上的StackTrack上的不太一样,Android返回的是list flutter返回的是StackTrack 所以需要手动切割 再处理
List stackList = _fixStack(stackTrace);
int ignoreDepth = 0;
int allDepth = stackList.length;
//倒着查询 查到倒数第一包名和需要屏蔽的包名一致时,数据往上的数据全部舍弃掉
for (int i = allDepth - 1; i > -1; i--) {
Match? match = _stackReg.matchAsPrefix(stackList[i]);
//如果匹配且第一个子项也符合 group 0 表示全部 剩下的数字看子项的多少返回
if (match != null &&
(match.group(1)!.startsWith("package:$ignorePackage"))) {
ignoreDepth = i + 1;
break;
}
}
stackList = stackList.sublist(ignoreDepth);
return stackList;
}
/// 裁切堆栈
/// [stackTrace] 堆栈
/// [maxDepth] 深度
static List _cropStackTrace(List stackTrace, int? maxDepth) {
int realDeep = stackTrace.length;
realDeep =
maxDepth != null && maxDepth > 0 ? min(maxDepth, realDeep) : realDeep;
return stackTrace.sublist(0, realDeep);
}
///裁切获取到最终的stack 并获取最大深度的栈信息
static getCroppedRealStackTrace(
{required StackTrace stackTrace, ignorePackage, maxDepth}) {
return _cropStackTrace(
_getRealStackTrack(stackTrace, ignorePackage), maxDepth);
}
}
class StackFormatter implements ILogFormatter> {
@override
String format(List stackList) {
///每一行都设置成单独的 字符串
StringBuffer sb = StringBuffer();
///堆栈是空的直接返回
if (stackList.isEmpty) {
return "";
///堆栈只有一行那么就返回 - 堆栈
} else if (stackList.length == 1) {
return "\n\t-${stackList[0].toString()}\n";
///多行堆栈格式化
} else {
for (int i = 0; i < stackList.length; i++) {
if (i == 0) {
sb.writeln("\n\t┌StackTrace:");
}
if (i != stackList.length - 1) {
sb.writeln("\t├${stackList[i].toString()}");
} else {
sb.write("\t└${stackList[i].toString()}");
}
}
}
return sb.toString();
}
}
class JsonFormatter extends ILogFormatter
///常量
//log的type
enum LogType {
V, //VERBOSE
E, //ERROR
A, //ASSERT
W, //WARN
I, //INFO
D, //DEBUG
}
int logMaxLength=1024;
///log的type 字符串说明
List logTypeStr = ["VERBOSE", "ERROR", "ASSERT", "WARN", "INFO", "DEBUG"];
///log的type 数字说明(匹配的Android原生,ios暂不清楚)
List logTypeNum = [2, 6, 7, 5, 4, 3];
class LogConfig {
///是否开启日志
bool _enable = false;
///默认的Tag
String _globalTag = "LogTag";
///堆栈显示的深度
int _stackTraceDepth = 0;
///打印的方式
List? _printers;
LogConfig({enable, globalTag, stackTraceDepth, printers}) {
_enable = enable;
_globalTag = globalTag;
_stackTraceDepth = stackTraceDepth;
_printers?.addAll(printers);
}
@override
String toString() {
return 'LogConfig{_enable: $_enable, _globalTag: $_globalTag, _stackTraceDepth: $_stackTraceDepth, _printers: $_printers}';
}
get enable => _enable;
get globalTag => _globalTag;
get stackTraceDepth => _stackTraceDepth;
get printers => _printers;
}
class LogManager {
///config
late LogConfig _config;
///打印器列表
List _printers = [];
///单例模式
static LogManager? _instance;
factory LogManager() => _instance ??= LogManager._();
LogManager._();
///初始化 Manager方法
LogManager.init({config, printers}) {
_config = config;
_printers.addAll(printers);
_instance = this;
}
get printers => _printers;
get config => _config;
void addPrinter(ILogPrint print) {
bool isHave = _printers.any((element) => element == print);
if (!isHave) {
_printers.add(print);
}
}
void removePrinter(ILogPrint print) {
_printers.remove(print);
}
}
class LogUtil {
static const String _ignorePackageName = "log_demo/utils/log";
static void V(
{String? tag,
dynamic? message,
LogConfig? logConfig,
StackTrace? stackTrace,
Map? json}) {
_logPrint(
type: LogType.V,
tag: tag ??= "",
logConfig: logConfig,
message: message,
json: json,
stackTrace: stackTrace);
}
static void E(
{String? tag,
dynamic? message,
LogConfig? logConfig,
StackTrace? stackTrace,
Map? json}) {
_logPrint(
type: LogType.E,
tag: tag ??= "",
message: message,
logConfig: logConfig,
json: json,
stackTrace: stackTrace);
}
static void I(
{String? tag,
dynamic? message,
LogConfig? logConfig,
StackTrace? stackTrace,
Map? json}) {
_logPrint(
type: LogType.I,
tag: tag ??= "",
message: message,
json: json,
stackTrace: stackTrace);
}
static void D(
{String? tag,
dynamic? message,
LogConfig? logConfig,
StackTrace? stackTrace,
Map? json}) {
_logPrint(
type: LogType.D,
tag: tag ??= "",
logConfig: logConfig,
message: message,
json: json,
stackTrace: stackTrace);
}
static void A(
{String? tag,
LogConfig? logConfig,
dynamic? message,
StackTrace? stackTrace,
Map? json}) {
_logPrint(
type: LogType.A,
tag: tag ??= "",
message: message,
logConfig: logConfig,
json: json,
stackTrace: stackTrace);
}
static void W(
{String? tag,
dynamic? message,
LogConfig? logConfig,
StackTrace? stackTrace,
Map? json}) {
_logPrint(
type: LogType.W,
tag: tag ??= "",
message: message,
logConfig: logConfig,
json: json,
stackTrace: stackTrace);
}
static Future _logPrint({
required LogType type,
required String tag,
LogConfig? logConfig,
dynamic message,
StackTrace? stackTrace,
Map? json,
}) async {
///如果logConfig为空那么就用默认的
logConfig ??= LogManager().config;
if (!logConfig?.enable) {
return;
}
StringBuffer sb = StringBuffer();
///打印当前页面
if (message.toString().isNotEmpty) {
sb.write(message);
}
///如果传入了栈且 要展示的深度大于0
if (stackTrace != null && logConfig?.stackTraceDepth > 0) {
sb.writeln();
String stackTraceStr = StackFormatter().format(
StackTraceUtil.getCroppedRealStackTrace(
stackTrace: stackTrace,
ignorePackage: _ignorePackageName,
maxDepth: logConfig?.stackTraceDepth));
sb.write(stackTraceStr);
}
if (json != null) {
sb.writeln();
String body = JsonFormatter().format(json);
sb.write(body);
}
///获取有几个打印器
List prints = logConfig?.printers ?? LogManager().printers;
if (prints.isEmpty) {
return;
}
///遍历打印器 分别打印数据
for (ILogPrint print in prints) {
print.logPrint(type: type, tag: tag, message: sb.toString());
}
}
}
class ConsolePrint extends ILogPrint {
@override
void logPrint(
{required LogType type,
required String tag,
required String message,
StackTrace? stackTrace,
Map? json}) {
///如果要开启颜色显示 那么就是1000
///如果不开启颜色显示 那么就是1023
int _maxCharLength = 1000;
//匹配中文字符以及这些中文标点符号 。 ? ! , 、 ; : “ ” ‘ ' ( ) 《 》 〈 〉 【 】 『 』 「 」 ﹃ ﹄ 〔 〕 … — ~ ﹏ ¥
RegExp _chineseRegex = RegExp(r"[\u4e00-\u9fa5|\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]");
///用回车做分割
List strList = message.split("\n");
///判断每句的长度 如果长度过长做切割
for (String str in strList) {
///获取总长度
int len = 0;
///获取当前长度
int current = 0;
///获取截断点数据
List entry = [0];
///遍历文字 查看真实长度
for (int i = 0; i < str.length; i++) {
一个汉字再打印区占三个长度,其他的占一个长度
len += str[i].contains(_chineseRegex) ? 3 : 1;
///寻找当前字符的下一个字符长度
int next = (i + 1) < str.length
? str[i + 1].contains(_chineseRegex)
? 3
: 1
: 0;
///当前字符累计长度 如果达到了需求就清空
current += str[i].contains(_chineseRegex) ? 3 : 1;
if (current < _maxCharLength && (current + next) >= _maxCharLength) {
entry.add(i);
current = 0;
}
}
///如果最后一个阶段点不是最后一个字符就添加上
if (entry.last != str.length - 1) {
entry.add(str.length);
}
///如果所有的长度小于1023 那么打印没有问题
if (len < _maxCharLength) {
_logPrint(type, tag, str);
} else {
///按照获取的截断点来打印
for (int i = 0; i < entry.length - 1; i++) {
_logPrint(type, tag, str.substring(entry[i], entry[i + 1]));
}
}
}
}
_logPrint(LogType type, String tag, String message) {
///前面的\u001b[31m用于设定SGR颜色,后面的\u001b[0m相当于一个封闭标签作为前面SGR颜色的作用范围的结束点标记。
/// \u001b[3 文字颜色范围 0-7 标准颜色 0是黑色 1是红色 2是绿色 3是黄色 4是蓝色 5是紫色 6蓝绿色 是 7是灰色 范围之外都是黑色
/// \u001b[9 文字颜色范围 0-7 高强度颜色 0是黑色 1是红色 2是绿色 3是黄色 4是蓝色 5是紫色 6蓝绿色 是 7是灰色 范围之外都是黑色
/// 自定义颜色 \u001b[38;2;255;0;0m 表示文字颜色 2是24位 255 0 0 是颜色的RGB 可以自定义颜色
/// \u001b[4 数字 m 是背景色
/// \u001b[1m 加粗
/// \u001b[3m 斜体
/// \u001b[4m 下划线
/// \u001b[7m 黑底白字
///\u001b[9m 删除线
///\u001b[0m 结束符
//详情看 https://www.cnblogs.com/zt123123/p/16110475.html
String colorHead = "";
String colorEnd = "\u001b[0m";
switch (type) {
case LogType.V:
// const Color(0xff181818);
colorHead = "\u001b[38;2;187;187;187m";
break;
case LogType.E:
colorHead = "\u001b[38;2;255;0;6m";
break;
case LogType.A:
colorHead = "\u001b[38;2;143;0;5m";
break;
case LogType.W:
colorHead = "\u001b[38;2;187;187;35m";
break;
case LogType.I:
colorHead = "\u001b[38;2;72;187;49m";
break;
case LogType.D:
colorHead = "\u001b[38;2;0;112;187m";
break;
}
/// 这里是纯Flutter项目所以在控制台打印这样子是可以有颜色的 如果是flutter混编 安卓原生侧打印\u001b 可能是一个乱码也没有变色效果
/// 如果你不想只在调试模式打印 你可以把debugPrint换成print
debugPrint("$colorHead$message$colorEnd");
/// 如果原生侧有封装log工具直接 写一个methodChannel 传参数就好 ,如果没有,可以调用原生的log打印 传入 level tag 和message
/// kDebugMode 用这个可以判断是否在debug模式下
/// if(kDebugMode){
/// 在debug模式下打印日志
// bool? result=await CustomChannelUtil.printLog(level:logTypeNum[type.index],tag:tag,message:message);
/// }
}
}
Widget build(BuildContext context) {
LogManager.init(
config: LogConfig(enable: true, globalTag: "TAG", stackTraceDepth: 5),
printers: [ConsolePrint()]);
///打印堆栈
LogUtil.I(tag: "test", stackTrace: StackTrace.current);
///打印json
LogUtil.E(tag: "JSON", json: json);
///打印信息
LogUtil.V(tag: "LogText", message: message);
源码地址 https://gitee.com/half_city/flutter_log_util.git
本文转自 https://www.jianshu.com/p/84f74b67add3,如有侵权,请联系删除。
有需要更多完整学习资料的,可以扫描下方二维码领取资料!
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。
在这里为了方便大家系统的学习Flutter,这里特意联合了阿里P7架构师和谷歌技术团队共同整理了一份Flutter全家桶学习资料。
内容概要:Flutter技术解析与实战、Flutter进阶学习笔记、Flutter入门与实战和Flutter完整开发实战详解。
内容特点:条理清晰,含图像化表示更加易懂。
由于文章内容比较多,篇幅有限,资料已经被整理成了PDF文档,有需要 Flutter技术解析与实战 完整文档的可扫描下方卡片免费获取!
● Flutter工程体系
● 混合工程改造实战
● 混合工程与持续集成
● 快速完成混合工程搭建
● 使用混合栈框架开发
● 基于原生能力的插件扩展
● 基于外接纹理的同层渲染
● 多媒体能力扩展实践
● 富文本能力应用实践
● 应用框架设计实践
● 轻量级动态化渲染引擎的设计
● 面向切面编程的设计实践
● 高性能的动态模板渲染实践
● 数据统计框架的设计
● 性能稳定性监控方案的设计
● 高可用框架的设计与实践
● 跨端方案性能对比实践
● 基于Flutter的端结构演进与创新
● Flutter与FaaS云端一体化架构
由于文章内容比较多,篇幅有限,资料已经被整理成了PDF文档,有需要 Flutter技术解析与实战 完整文档的可以加微信) 即可免费领取!
● 这是为什么?
● 跨平台开发
● 什么是Flutter
● Flutter特性
● Flutter 构建应用的工具
● 使用 Flutter 构建的热门应用
● 构建 Flutter 应用的成本
● 使用镜像
● 系统要求
● 获取Flutter SDK
● 编辑器设置
● Android设置
● 起步: 配置编辑器
● 起步: 体验
● 体验热重载
● 创建 Flutter app
● 使用外部包(package)
● 添加一个 有状态的部件(Stateful widget)
● 创建一个无限滚动ListView
● 添加交互
● 导航到新页面
● 使用主题更改UI
第四章 Flutter开发环境搭建和调试
● 开发环境的搭建
● 模拟器的安装与调试
● 开发环境的搭建
● 模拟器的安装与调试
● 简述
● Hello Dart
● 数据类型
● 变量和常量
● 集合(List、Set、Map)
● 流程控制
● 运算符
● 异常
● 函数
● 总结
● List
● Set
● Map
● Queue
● LinkedList
● HashMap
● Map、HashMap、LinkedHashMap、SplayTreeMap区别
● 命名构造函数from和of的区别以及使用建议
● 简述
● Iterable
● forEach
● map
● any
● every
● where
● firstWhere和singleWhere和lastWhere
● join
● take
● takeWhile
● skip
● skipWhile
● follwedBy
● expand
● reduce
● elementAt
● 简述
● 函数参数
● 匿名函数(闭包,lambda)
● 箭头函数
● 局部函数
● 顶层函数和静态函数
● main函数
● Function函数对象
● 简述
● 属性访问器(accessor)函数setter和getter
● 面向对象中的变量
● 构造函数
● 抽象方法、抽象类和接口
● 类函数
● 总结
● 简述
● 类的单继承
● 基于Mixins的多继承
● 总结
● 简述
● 可选类型
● 接口类型
● 泛型
● 类型具体化
● 总结
第十二章 Flutter中的widget
● Flutter页面-基础Widget
● Widget
● StatelessWidget
● State生命周期
● 基础widget
● DefaultTextStyle
● FlutterLogo
● Icon
● Iamge.asset
● CircleAvatar
● FadeInImage
● 按钮
● FlatButton
● OutlineButton
● TextFormField
由于文章内容比较多,篇幅有限,资料已经被整理成了PDF文档,有需要 Flutter技术解析与实战 完整文档的可以加微信 即可免费领取!
目录
● 我的第一个 Flutter 应用之旅
● 容器的盒子模型
● 构建一个常用的页面框架
● 设置 App 的主色调与字体
● 来一个图文并茂的列表
● 给列表增加下拉刷新和上滑加载更多功能
● 使用cached_network_image 优化图片加载体验
● 仿一个微信价值几个亿的页面
● 开发一个常用的登录页面
● 封装一个通用的文本输入框
● 底部弹窗ModelBottomSheet详解
● 利用CustomScrollView实现更有趣的滑动效果
● 底部弹窗如何实现多项选择?
● App页面路由及路由拦截实现
● 路由参数处理
● 初识 fluro 路由管理
● 使用 fluro 的转场动画提高页面切换体验
● 使用自定义转场动画实现个性化页面切换
● 此路是我开,此树是我栽。若是没权限,403到来
● Flutter 2.0的路由把我搞蒙了
● 山路十八弯的2.0路由
● 初次见面,网络请求王者之dio
● 利用 Dio请求删除数据
● 使用 Dio的 Patch请求完成详情编辑
● 使用 Post 请求增加动态
● 一文搞定图片选择及图片上传
● 使用 GetIt 同步不同页面间数据
● Dio 封装之金屋藏娇
● Dio 之拦截器
● Dio之戛然而止
● 从源码深入了解Dio 的
● 小伙子,你买票了吗?
● 手写一个持久化的
● Dio之文件下载
● Dio 篇章总结
● 基础原理篇
● Provider篇
● Redux篇
● Mobx篇
● Getx篇
● BLOC篇
● 状态管理系列大汇总
● 使用 Animation 构建爱心三连动画
● 让你的组件拥有三维动效
● 小姐姐渐现效果 ——AnimatedOpacity 使用
● 使用 AnimatedBuilder分离组件和动画,实现动效复用