一、需求来源
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中的同等功能代码,在今天终于完美解决,分享给大家。