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

相关推荐
前端之虎陈随易3 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·vue.js·人工智能·typescript·node.js
一路向北he3 小时前
字节钢铁军团--“提供情境,而非控制”
java·开发语言·前端
kyriewen3 小时前
豆包和千问同时关了智能体,我用它们搭的 3 个自动化全废了——迁移方案整理
前端·javascript·ai编程
前端一小卒4 小时前
我用 TypeScript 从零手写了一个 Claude Code,然后发现它的核心只有 30 行
前端·agent
GitLqr4 小时前
Flutter 3.44 插件内置 Kotlin (KGP) 双向兼容适配指南
android·flutter·dart
大圣编程5 小时前
Python中continue语句的用法是什么?
开发语言·前端·python
yuhaiqiang5 小时前
随手 vibecoding 的浏览器插件已经 6000 多次下载,聊聊他的产品设计
前端·后端·面试
之歆6 小时前
Vue商品详情与放大镜组件
前端·javascript·vue.js
再吃一根胡萝卜6 小时前
如何把小米 MiMo 接入 CodeBuddy,打造私有 Agent
前端
负责的蛋挞8 小时前
异步HttpModule的实现方式
java·服务器·前端