软件教程 2025年08月6日
0 收藏 0 点赞 778 浏览 5591 个字
摘要 :

文章目录 一、DLog背景与需求 二、DLog使用示例详解 三、深入解析DLog源码 四、总结 Flutter开发过程中,在排查冷启动相关的疑难杂症时,日志模块提供的信息就显得很有……




  • 一、DLog背景与需求
  • 二、DLog使用示例详解
  • 三、深入解析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的不同日志打印方法(diwe分别对应调试、信息、警告、错误日志),输出的日志格式为[日期时间][日志类型][平台][类名.函数名 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;
  }
}
  1. 日志开关与颜色设置:通过enableLog来控制是否启用日志打印功能,enableColor用于控制是否开启颜色显示。
  2. 日志打印方法:DLog提供了diwe四种方法,分别对应不同级别的日志打印,这些方法最终都会调用_printLog方法进行实际的日志处理。
  3. 获取调用信息_getCallerInfo方法通过解析StackTrace.current信息,获取调用日志打印方法的类名、函数名、文件名以及行号。它会从堆栈跟踪信息中提取相关内容,利用正则表达式匹配类名、方法名、文件名和行号。
  4. 获取当前平台_getPlatform方法用于判断当前运行的平台,返回Web或者具体的操作系统名称(如iosandroid),如果获取失败则返回空字符串。
  5. 内部打印逻辑_printLog方法负责组装日志信息,根据是否是Web平台以及是否开启颜色显示,生成不同格式的日志内容。在Web平台上,直接使用developer.log打印日志;在原生平台上,如果开启颜色显示,则会在日志内容前后添加相应的颜色代码。

四、总结

目前在现有的Flutter第三方库中,很难找到支持日志精准定位代码位置的工具,而iOS原生系统默认支持类似功能。DLog的出现,弥补了Flutter在这方面的不足,为开发者提供了极大的便利。

微信扫一扫

支付宝扫一扫

版权: 转载请注明出处:https://www.zuozi.net/6870.html

管理员

相关推荐
2025-08-06

文章目录 一、Promise基础回顾 二、Promise 与 axios 结合使用场景及方法 (一)直接返回 axios …

268
2025-08-06

文章目录 一、模块初始化时的内部机制 二、常见导出写法的差异分析 (一)写法一:module.exports…

106
2025-08-06

文章目录 一、ResizeObserver详解 (一)ResizeObserver是什么 (二)ResizeObserver的基本用法 …

682
2025-08-06

文章目录 一、前期准备工作 (一)下载相关文件 (二)安装必要工具 二、处理扣子空间生成的文件…

338
2025-08-06

文章目录 一、官方文档 二、自动解包的数据类型 ref对象:无需.value即可访问 reactive对象:保持…

370
2025-08-06

文章目录 一、Hooks的工作原理 二、在if语句中使用Hook会出什么岔子? 三、React官方的Hook使用规…

842
发表评论
暂无评论

还没有评论呢,快来抢沙发~

助力内容变现

将您的收入提升到一个新的水平

点击联系客服

在线时间:08:00-23:00

客服QQ

122325244

客服电话

400-888-8888

客服邮箱

122325244@qq.com

扫描二维码

关注微信客服号