Flutter如何通过DLog实现日志信息快速定位
Flutter开发过程中,在排查冷启动相关的疑难杂症时,日志模块提供的信息就显得很有必要。准确且快速地定位到问题代码所在位置,能够极大地提高我们解决问题的效率。然而,如果只是打印出一堆错误日志,却无法精准定位,那这些日志的价值就大打折扣了。经过一番探索,我找到了一个有效的解决方案——DLog,通过解析StackTrace.current信息,它能自动获取日志的类名、函数名以及日志输出代码行数,下面就给大家详细介绍一下。
一、DLog背景与需求
在Flutter开发中,日志是我们排查问题的重要依据。但以往的日志在定位问题时存在一定的局限性,很难快速确定问题出在代码的哪个具体位置。比如在处理冷启动相关问题时,由于涉及的代码逻辑复杂,大量的日志信息让开发者难以快速定位到关键问题点。为了解决这个困扰,DLog应运而生,它的目标就是让开发者能够更高效地从日志中找到问题根源。
二、DLog使用示例详解
下面通过一段具体的代码示例,来看看DLog是如何发挥作用的。
class _MetaDataDemoState extends State<MetaDataDemo> { bool get hideApp => "$widget".toLowerCase().endsWith(Get.current); final scrollController = ScrollController(); late final items = <ActionRecord>[ (e: "onTest", action: onTest), ]; @override void didUpdateWidget(covariant MetaDataDemo oldWidget) {... } @override Widget build(BuildContext context) {... } Widget buildBody() {... } Widget buildSectionBox({ required List<ActionRecord> items, }) {... } void onTest() { // DLog.d("AAA"); try { var map = {}; jsonDecode(map["a"]); } catch (e) { debugPrint("$this $e"); //flutter:_MetaDataDemoState#d548 DLog.d("$e"); //[Log] [2025-03-27 10:13:00.725182][DEBUG][ios][_MetaDataDemoState.onTest Line:161]: type 'Null' is not a subtype of type 'String' DLog.i("$e"); //[Log] [2025-03-27 10:13:00.725901][INFO][ios][_MetaDataDemoState.onTest Line:162]: type 'Null' is not a subtype of type 'String' DLog.w("$e"); //[Log] [2025-03-27 10:13:00.726502][WARN][ios][_MetaDataDemoState.onTest Line:163]: type 'Null' is not a subtype of type 'String' DLog.e("$e"); //[Log] [2025-03-27 10:13:00.727041][ERROR][ios][_MetaDataDemoState.onTest Line:164]: type 'Null' is not a subtype of type 'String' } } }
在上述代码的onTest
方法中,我们模拟了一个可能会出现错误的场景。当使用debugPrint
打印错误信息时,输出的内容仅能显示出类的相关信息,但无法精准定位到具体行数。而使用DLog的不同日志打印方法(d
、i
、w
、e
分别对应调试、信息、警告、错误日志),输出的日志格式为[日期时间][日志类型][平台][类名.函数名 Line:行]: 日志内容
,能够清晰地展示出错误发生的具体位置,例如[Log] [2025-03-27 10:13:00.725182][DEBUG][ios][_MetaDataDemoState.onTest Line:161]: type 'Null' is not a subtype of type 'String'
,这对于快速定位和解决问题非常有帮助。
另外,DLog还支持颜色显示功能(仅VSCode支持,Android studio不支持),如果想要开启颜色显示,可以通过以下代码设置:
/// 开启颜色 DLog.enableColor = true;
开启后,不同类型的日志会以不同颜色展示,更加直观地区分日志级别,方便查看。
三、深入解析DLog源码
DLog的实现原理其实并不复杂,下面我们一起来看看它的源码。
// // ddlog.dart // ddlog // // Created by shang on 7/4/21 3:53 PM. // Copyright © 7/4/21 shang. All rights reserved. // import 'dart:developer' as developer; import 'dart:io' show Platform; import 'package:flutter/foundation.dart'; class DLog { /// 是否启用日志打印 static bool enableLog = true; /// 开启颜色 static bool enableColor = false; // ANSI 颜色代码 static const String _ansiReset = 'x1B[0m'; static const String _ansiRed = 'x1B[31m'; static const String _ansiGreen = 'x1B[32m'; static const String _ansiYellow = 'x1B[33m'; static const String _ansiBlue = 'x1B[34m'; // static const String _ansiGray = 'x1B[37m'; // Web 控制台颜色样式 static const String _webRed = 'color: red'; static const String _webGreen = 'color: #4CAF50'; static const String _webYellow = 'color: #FFC107'; static const String _webBlue = 'color: #2196F3'; // static const String _webGray = 'color: #9E9E9E'; // 打印调试日志 static String d(dynamic message) { return _printLog('DEBUG', message, _ansiBlue, _webBlue); } // 打印信息日志 static String i(dynamic message) { return _printLog('INFO', message, _ansiGreen, _webGreen); } // 打印警告日志 static String w(dynamic message) { return _printLog('WARN', message, _ansiYellow, _webYellow); } // 打印错误日志 static String e(dynamic message) { return _printLog('ERROR', message, _ansiRed, _webRed); } // 获取调用信息 static (String className, String functionName, String fileName, int lineNumber) _getCallerInfo() { try { final frames = StackTrace.current.toString().split('n'); // 第一帧是当前方法,第二帧是日志方法(d/i/w/e),第三帧是调用者 if (frames.length > 2) { final frame = frames[3]; // 获取调用者的帧 // 匹配类名和方法名 final classMatch = RegExp(r'#d+s+([^.]+).(w+)').firstMatch(frame); final className = classMatch?.group(1) ?? 'Unknown'; final functionName = classMatch?.group(2) ?? 'unknown'; // 匹配文件名和行号 final fileMatch = RegExp(r'((.+?):(d+)(?::d+)?)').firstMatch(frame); final fileName = fileMatch?.group(1) ?? 'unknown'; final lineNumber = int.tryParse(fileMatch?.group(2) ?? '0') ?? 0; return (className, functionName, fileName, lineNumber); } } catch (e) { debugPrint('Error getting caller info: $e'); } return ('', '', '', 0); } // 获取当前平台 static String _getPlatform() { if (kIsWeb) { return 'Web'; } try { return Platform.operatingSystem; } catch (e) { // 如果 Platform 不可用,返回 Unknown return ''; } } // 内部打印方法 static String _printLog(String level, dynamic message, String ansiColor, String webColor) { if (!enableLog ||!kDebugMode) { return ""; } final (className, functionName, fileName, lineNumber) = _getCallerInfo(); final now = DateTime.now(); final timeStr = now.toString(); final platform = _getPlatform(); final logMessage = kIsWeb ? '[$timeStr][$level][$platform]: $message' : '[$timeStr][$level][$platform][$className.$functionName Line:$lineNumber]: $message'; if (kIsWeb) { return _printLogWeb(level, logMessage, webColor); } else { return _printLogNative(level, logMessage, ansiColor); } } // Web 平台的打印实现 static String _printLogWeb(String level, String message, String webColor) { developer.log(message); return message; } // 原生平台的打印实现 static String _printLogNative(String level, String message, String ansiColor) { final sb = StringBuffer(); if (enableColor) { sb.write(ansiColor); } sb.write(message); if (enableColor) { sb.write(_ansiReset); } final result = sb.toString(); developer.log(sb.toString()); return result; } }
- 日志开关与颜色设置:通过
enableLog
来控制是否启用日志打印功能,enableColor
用于控制是否开启颜色显示。 - 日志打印方法:DLog提供了
d
、i
、w
、e
四种方法,分别对应不同级别的日志打印,这些方法最终都会调用_printLog
方法进行实际的日志处理。 - 获取调用信息:
_getCallerInfo
方法通过解析StackTrace.current
信息,获取调用日志打印方法的类名、函数名、文件名以及行号。它会从堆栈跟踪信息中提取相关内容,利用正则表达式匹配类名、方法名、文件名和行号。 - 获取当前平台:
_getPlatform
方法用于判断当前运行的平台,返回Web
或者具体的操作系统名称(如ios
、android
),如果获取失败则返回空字符串。 - 内部打印逻辑:
_printLog
方法负责组装日志信息,根据是否是Web平台以及是否开启颜色显示,生成不同格式的日志内容。在Web平台上,直接使用developer.log
打印日志;在原生平台上,如果开启颜色显示,则会在日志内容前后添加相应的颜色代码。
四、总结
目前在现有的Flutter第三方库中,很难找到支持日志精准定位代码位置的工具,而iOS原生系统默认支持类似功能。DLog的出现,弥补了Flutter在这方面的不足,为开发者提供了极大的便利。