Flutter学习LogUtil封装与实现实例详解

目录

  • 一. 为什么要封装打印类
  • 二. 需要哪些类
  • 三. 打印输出的抽象类
  • 四. 格式化日志内容
    • 格式化堆栈
    • 堆栈裁切工具类
    • 格式化堆栈信息
    • 格式化JSON
  • 五. 需要用到的常量
    • 六. 为了控制多个打印器的设置做了一个配置类
      • 七. Log的管理类
        • 九. 调用LogUtil
          • 十. 定义一个Flutter 控制台打印输出的方法
            • 十一. 现在使用前初始化log打印器一次
              • 使用

            一. 为什么要封装打印类 虽然 flutter/原生给我们提供了日志打印的功能,但是超出一定长度以后会被截断
            Json打印挤在一起看不清楚
            堆栈打印深度过深多打印一些不需要的东西
            实现 log 的多种展示方式
            Flutter学习LogUtil封装与实现实例详解
            文章图片

            Flutter学习LogUtil封装与实现实例详解
            文章图片

            Flutter学习LogUtil封装与实现实例详解
            文章图片


            二. 需要哪些类 为了可以实现对日志的多种内容格式化和各种显示输出所以抽出来以下几个类
            • 一些常量的字符串表示
            • 对日志内容的打印输出抽象类
            • 对日志内容格式化的抽象类
            • 日志工具的config类
            • 日志工具的管理类
            • 日志工具的Util类

            三. 打印输出的抽象类 打印类核心的功能就是打印日志 所以它有一个方法就是打印的方法
            而我们要打印输出的内容有 当前 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.htmlstatic final RegExp _stackReg = RegExp(r'.+ \(([^\s]+)\)'); /// 把StackTrace 转成list 并去除无用信息/// [stackTrace] 堆栈信息///#0LogUtil._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 {@overrideString 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(); }}


            格式化JSON
            class JsonFormatter extends ILogFormatter {@overrideString format(Map data) {///递归调用循环遍历data 在StringBuffer中添加StringBufferString finalString = _forEachJson(data, 0); finalString = "\ndata:$finalString"; return finalString; }/// [data]传入需要格式化的数据/// [spaceCount]需要添加空格的长度 一个数字是两个空格/// [needSpace] 需不需要添加空格/// [needEnter] 需不需要回车String _forEachJson(dynamic data, int spaceCount,{bool needSpace = true, needEnter = true}) {StringBuffer sb = StringBuffer(); int newSpace = spaceCount + 1; if (data is Map) {///如果它是Map走这里///是否需要空格sb.write(buildSpace(needSpace ? spaceCount : 0)); sb.write(needEnter ? "{\n" : "{"); data.forEach((key, value) {///打印输出 keysb.write("${buildSpace(needEnter ? newSpace : 0)}$key: "); ///递归调用看value是什么类型 如果字符长度少于30就不回车显示sb.write(_forEachJson(value, newSpace,needSpace: false,needEnter: !(value is Map ? false : value.toString().length < 50))); ///不是最后一个就加,if (data.keys.last != key) {sb.write(needEnter ? ",\n" : ","); }}); if (needEnter) {sb.writeln(); }sb.write("${buildSpace(needEnter ? spaceCount : 0)}}"); } else if (data is List) {///如果他是列表 走这里sb.write(buildSpace(needSpace ? spaceCount : 0)); sb.write("[${needEnter ? "\n" : ""}"); for (var item in data) {sb.write(_forEachJson(item, newSpace,needEnter: !(item.toString().length < 30))); ///不是最后一个就加的,if (data.last != item) {sb.write(needEnter ? ",\n" : ","); }}sb.write(needEnter ? "\n" : ""); sb.write("${buildSpace(needSpace?spaceCount:0)}]"); } else if (data is num || data is bool) {///bool 或者数组不加双引号sb.write(data); } else if (data is String) {///string 或者其他的打印加双引号 如果他是回车就改变他 按回车分行会错乱sb.write("\"${data.replaceAll("\n", r"\n")}\""); } else {sb.write("$data"); }return sb.toString(); }///构造空格String buildSpace(int deep) {String temp = ""; for (int i = 0; i < deep; i++) {temp += ""; }return temp; }}


            五. 需要用到的常量
            ///常量//log的typeenum LogType {V, //VERBOSEE, //ERRORA, //ASSERTW, //WARNI, //INFOD, //DEBUG}int logMaxLength=1024; ///log的type 字符串说明List logTypeStr = ["VERBOSE", "ERROR", "ASSERT", "WARN", "INFO", "DEBUG"]; ///log的type 数字说明(匹配的Android原生,ios暂不清楚)List< int> logTypeNum = [2, 6, 7, 5, 4, 3];


            六. 为了控制多个打印器的设置做了一个配置类
            class LogConfig {///是否开启日志bool _enable = false; ///默认的TagString _globalTag = "LogTag"; ///堆栈显示的深度int _stackTraceDepth = 0; ///打印的方式List? _printers; LogConfig({enable, globalTag, stackTraceDepth, printers}) {_enable = enable; _globalTag = globalTag; _stackTraceDepth = stackTraceDepth; _printers?.addAll(printers); }@overrideString toString() {return 'LogConfig{_enable: $_enable, _globalTag: $_globalTag, _stackTraceDepth: $_stackTraceDepth, _printers: $_printers}'; }get enable => _enable; get globalTag => _globalTag; get stackTraceDepth => _stackTraceDepth; get printers => _printers; }

            【Flutter学习LogUtil封装与实现实例详解】
            七. Log的管理类
            class LogManager {///configlate LogConfig _config; ///打印器列表List< ILogPrint> _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); }}


            九. 调用LogUtil
            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); }///如果传入了栈且 要展示的深度大于0if (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()); }}}


            十. 定义一个Flutter 控制台打印输出的方法
            class ConsolePrint extends ILogPrint {@overridevoid logPrint({required LogType type,required String tag,required String message,StackTrace? stackTrace,Map? json}) {///如果要开启颜色显示 那么就是1000///如果不开启颜色显示 那么就是1023int _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.htmlString 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换成printdebugPrint("$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); /// }}}


            十一. 现在使用前初始化log打印器一次
            Widget build(BuildContext context) {LogManager.init(config: LogConfig(enable: true, globalTag: "TAG", stackTraceDepth: 5),printers: [ConsolePrint()]);


            使用
            ///打印堆栈LogUtil.I(tag: "test", stackTrace: StackTrace.current); ///打印jsonLogUtil.E(tag: "JSON", json: json); ///打印信息 LogUtil.V(tag: "LogText", message: message);

            以上就是Flutter学习LogUtil封装与实现实例详解的详细内容,更多关于Flutter LogUtil 封装实现的资料请关注脚本之家其它相关文章!

              推荐阅读