Flutter进阶:日志信息的快速定位解决方案 DLog

一、需求来源

flutter 中冷启动疑难杂症排查基本完全依靠日志模块信息。准确快速的定位代码位置就变得异常重要,否则打印一堆错误日志,定位不到具体的位置也无法彻底快速的解决问题。随最近花了点时间通过解析 StackTrace.current 信息,自动获取日志的类名、函数名和日志输出代码行数。

格式:[日期时间][日志类型][平台][类名.函数名 Line:行]: 日志内容

二、使用示例

注意打印输出:

dart 复制代码
void onTest() {
  // DLog.d("AAA");
  try {
    var map = {};
    jsonDecode(map["a"]);
  } catch (e) {
    debugPrint("$this $e");//flutter: _MetaDataDemoState#d5486 type 'Null' is not a subtype of type 'String'
    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'
  }
}

关闭颜色(andriod studio 不支持)

打开颜色(VSCode支持)

dart 复制代码
 /// 开启颜色
  DLog.enableColor = true;

三、源码

dart 复制代码
//
//  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;
  }
}

最后、总结

现有的第三方库没发现支持日志定位代码准确位置的库。但在iOS原生中这是默认支持的功能,一直在寻找flutter中的同等功能代码,在今天终于完美解决,分享给大家。

github

相关推荐
LucianaiB10 小时前
【元宝搜索开发者征文大赛】AI时代,学得慢就不用学?用元宝搜索打造一个教程保质期,确保每一个教程符合你的现状
前端·人工智能·ai编程
好的佩奇10 小时前
Dart 之扩展类型
android·flutter·dart
excel10 小时前
JavaScript 对象池:减少高频对象创建与 GC 压力
前端
陈随易12 小时前
纯 Go 写的 JS 引擎,跑分超过了 C 语言的 QuickJS
前端·后端·程序员
子兮曰14 小时前
Chrome 权限元素实战:144 与 151 的渐进增强写法
前端·javascript·chrome
IT_陈寒14 小时前
Redis连接池泄漏那次,我差点把生产环境搞崩了
前端·人工智能·后端
强尼银手14 小时前
我花一天做了个格斗游戏原型,判定全对,然后被「美术」劝退了
前端·javascript
石小石Orz14 小时前
AI 正在蚕食我们的编码能力
前端·后端·算法
陈随易15 小时前
专注飞机 FocusFlight,两个人把番茄钟做成了一趟航班
前端·后端·程序员
总要学点什么15 小时前
第98篇:三维特效-材质线效果——给一条线换十种材质,虚线的图案只填了一半
开发语言·前端·javascript·ecmascript·gis·webgl·材质