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

相关推荐
To_OC5 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic5 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy5 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志6 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble7 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯8 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜8 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司9 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
心中有国也有家9 小时前
AtomGit Flutter 鸿蒙客户端:ModalBottomSheet 实战
android·javascript·学习·flutter·华为·harmonyos
kaiking_g9 小时前
vue项目中,静态导入 vs 动态导入 详解
前端·javascript·vue.js