目录
[1. 📂 简介](#1. 📂 简介)
[2. 🔱 核心架构设计](#2. 🔱 核心架构设计)
[3. 💠 使用步骤](#3. 💠 使用步骤)
[3.1 添加依赖](#3.1 添加依赖)
[3.2 初始化](#3.2 初始化)
[3.3 打印日志](#3.3 打印日志)
[4. ⚛️ 工程化封装](#4. ⚛️ 工程化封装)
[4.1 第一层:优雅的调用入口](#4.1 第一层:优雅的调用入口)
[4.1.1 快捷函数 (log_utils.dart)](#4.1.1 快捷函数 (log_utils.dart))
[4.1,2 日志管理器 (log_manager.dart)](#4.1,2 日志管理器 (log_manager.dart))
[4.2 第二层:配置与中控逻辑](#4.2 第二层:配置与中控逻辑)
[4.2.1 灵活的配置 (log_config.dart)](#4.2.1 灵活的配置 (log_config.dart))
[4.2.2 逻辑中控 (log_core.dart)](#4.2.2 逻辑中控 (log_core.dart))
[4.3 第三层:性能优化与实现](#4.3 第三层:性能优化与实现)
[4.3.1 异步缓冲区机制 (file_logger.dart)](#4.3.1 异步缓冲区机制 (file_logger.dart))
[4.3.2 控制台美化 (console_logger.dart)](#4.3.2 控制台美化 (console_logger.dart))
[5. ✅ 小结](#5. ✅ 小结)
1. 📂 简介
在 Flutter 项目开发中,日志系统是调试和线上排查的"眼睛"。很多开发者习惯直接使用 print 或 debugPrint,但在中大型项目中,这显然不够:
-
无法持久化:线上用户报错,没日志等于盲飞。
-
性能隐患:高频的 IO 操作会阻塞 UI 线程。
-
职责混乱:打印逻辑散落在业务代码各处,难以统一开关。
今天分享一套三层架构 的日志模块设计方案,支持异步缓冲区写入、多处理器分发 以及自动清理机制。
2. 🔱 核心架构设计
为了降低维护成本,我们将日志系统分为三层,各司其职:
-
第一层:门面层 (Facade):提供最简单的全局函数和单例入口,让开发者"无感"调用。
-
第二层:核心层 (Core):负责配置解析、级别过滤、逻辑分发。
-
第三层:实现层 (Handler):具体的落地执行者,如控制台着色打印、文件分片存储。

3. 💠 使用步骤
3.1 添加依赖
# pubspec.yaml
dependencies:
# 用于国际化(i18n)和本地化(l10n)操作,如日期、时间、数字格式化等。
intl: ^0.20.2
# 用于在 Flutter 应用中进行日志记录。它支持多种输出形式,如控制台、文件、以及彩色日志。
logger: ^1.1.0
# 提供了在设备上查找常用文件目录的方法,如获取应用的文档目录、临时目录等。
path_provider: ^2.1.3
3.2 初始化
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 根据编译模式选择配置
await LogManager().initialize(
config: kReleaseMode ? LogConfig.production() : LogConfig.defaultConfig()
);
runApp(const MyApp());
}
3.3 打印日志
logI("App Running...");
4. ⚛️ 工程化封装
4.1 第一层:优雅的调用入口
我们不希望在业务代码里看到复杂的初始化逻辑,因此通过 log_utils.dart 提供一套快捷函数。
4.1.1 快捷函数 (log_utils.dart)
///
/// Description: 日志工具类
/// CreateDate: 2025/12/17 11:41
/// Author: agg
///
void logD(String message, {String? tag = "MoJieGlass"}) {
LogManager.debug(tag != null ? '[$tag] $message' : message);
}
void logI(String message, {String? tag = "MoJieGlass"}) {
LogManager.info(tag != null ? '[$tag] $message' : message);
}
void logE(String message, {String? tag = "MoJieGlass"}) {
LogManager.error(tag != null ? '[$tag] $message' : message);
}
4.1,2 日志管理器 (log_manager.dart)
LogManager 采用单例模式,作为整个系统的"总线",负责生命周期管理。
///
/// Description: 日志管理器(单例)
/// CreateDate: 2025/12/17 11:40
/// Author: agg
///
class LogManager {
// 声明静态变量
static final LogManager _instance = LogManager._internal();
// 工厂构造函数
factory LogManager() => _instance;
// 私有命名构造函数
LogManager._internal();
LogCore? _logCore;
LogConfig? _config;
/// 初始化日志系统
Future<void> initialize({LogConfig? config}) async {
if (_logCore != null) {
await _logCore!.dispose();
}
_config = config ?? LogConfig.defaultConfig();
_logCore = LogCore(config: _config!);
await _logCore!.initialize();
}
/// 获取日志核心实例
LogCore get logger {
if (_logCore == null) {
throw StateError('LogManager not initialized. Call initialize() first.');
}
return _logCore!;
}
/// 获取配置
LogConfig? get config => _config;
/// 是否已初始化
bool get isInitialized => _logCore != null;
/// 快捷访问方法
static void verbose(String message) {
_instance.logger.verbose(message);
}
static void debug(String message) {
_instance.logger.debug(message);
}
static void info(String message) {
_instance.logger.info(message);
}
static void warning(String message) {
_instance.logger.warning(message);
}
static void error(String message) {
_instance.logger.error(message);
}
static void wtf(String message) {
_instance.logger.wtf(message);
}
/// 网络日志
static void network(
String method,
String url, {
dynamic data,
Map<String, dynamic>? headers,
int? statusCode,
String? response,
int duration = 0,
}) {
_instance.logger.network(
method,
url,
data: data,
headers: headers,
statusCode: statusCode,
response: response,
duration: duration,
);
}
/// 销毁资源
Future<void> dispose() async {
await _logCore?.dispose();
_logCore = null;
_config = null;
}
}
4.2 第二层:配置与中控逻辑
这一层是整个模块的大脑,决定了日志"去哪里"和"存多久"。
4.2.1 灵活的配置 (log_config.dart)
通过 LogConfig,我们可以轻松实现:开发环境看控制台,生产环境关掉控制台只存文件。
///
/// Description: 日志配置类
/// CreateDate: 2025/12/17 11:19
/// Author: agg
///
class LogConfig {
/// 是否启用文件日志
final bool enableFileLog;
/// 是否启用控制台日志
final bool enableConsoleLog;
/// 最小日志级别(低于此级别的日志不会被记录)
final Level minLevel;
/// 日志目录路径,为空则使用默认目录
final String? logDirectory;
/// 单个日志文件最大大小(字节),默认 5MB
final int maxFileSize;
/// 内存缓冲区最大日志条数
final int maxBufferSize;
/// 日志文件名前缀
final String filePrefix;
/// 日志文件扩展名
final String fileExtension;
/// 保留日志文件的天数
final int daysToKeep;
/// 默认配置
factory LogConfig.defaultConfig() => LogConfig(
enableFileLog: true,
enableConsoleLog: true,
minLevel: Level.verbose,
maxFileSize: 5 * 1024 * 1024,
maxBufferSize: 100,
daysToKeep: 7,
);
/// 生产环境配置
factory LogConfig.production() => LogConfig(
enableFileLog: true,
enableConsoleLog: true,
// 生产环境关闭控制台日志
minLevel: Level.info,
// 生产环境只记录info及以上级别,10MB文件大小
maxFileSize: 10 * 1024 * 1024,
maxBufferSize: 200,
// 生产环境保留更久
daysToKeep: 30,
);
const LogConfig({
required this.enableFileLog,
required this.enableConsoleLog,
this.minLevel = Level.verbose,
this.logDirectory,
this.maxFileSize = 5 * 1024 * 1024,
this.maxBufferSize = 100,
this.filePrefix = 'app',
this.fileExtension = 'log',
this.daysToKeep = 7,
});
/// 验证配置
void validate() {
if (maxFileSize <= 0) {
throw ArgumentError('maxFileSize must be greater than 0');
}
if (maxBufferSize <= 0) {
throw ArgumentError('maxBufferSize must be greater than 0');
}
if (daysToKeep <= 0) {
throw ArgumentError('daysToKeep must be greater than 0');
}
}
}
4.2.2 逻辑中控 (log_core.dart)
LogCore 持有两个处理器:ConsoleLogger 和 FileLogger。
///
/// Description: 核心日志类
/// CreateDate: 2025/12/17 11:39
/// Author: agg
///
class LogCore {
final LogConfig config;
late ConsoleLogger _consoleLogger;
late FileLogger _fileLogger;
bool _isInitialized = false;
bool _isDisposed = false;
LogCore({required this.config}) {
config.validate();
_initLoggers();
}
void _initLoggers() {
if (config.enableConsoleLog) {
_consoleLogger = ConsoleLogger();
}
if (config.enableFileLog) {
_fileLogger = FileLogger(
customDirectory: config.logDirectory,
maxFileSize: config.maxFileSize,
maxBufferSize: config.maxBufferSize,
filePrefix: config.filePrefix,
fileExtension: config.fileExtension,
daysToKeep: config.daysToKeep,
);
}
}
/// 初始化日志系统
Future<void> initialize() async {
if (_isDisposed) {
throw StateError('LogCore has been disposed');
}
try {
if (config.enableFileLog) {
await _fileLogger.initialize();
}
_isInitialized = true;
} catch (e) {
_isInitialized = false;
// 文件日志初始化失败不影响控制台日志
if (config.enableConsoleLog) {
_consoleLogger.log(
level: Level.error,
message: 'Failed to initialize file logger: $e',
);
}
}
}
void log({required Level level, required String message}) {
if (!_isInitialized || _isDisposed) return;
if (level.index < config.minLevel.index) return;
try {
// 控制台日志
if (config.enableConsoleLog) {
_consoleLogger.log(level: level, message: message);
}
// 文件日志
if (config.enableFileLog) {
_fileLogger.log(level: level, message: message);
}
} catch (e) {
// 日志记录失败不应影响应用运行
print('Logging failed: $e');
}
}
void verbose(String message) {
log(level: Level.verbose, message: message);
}
void debug(String message) {
log(level: Level.debug, message: message);
}
void info(String message) {
log(level: Level.info, message: message);
}
void warning(String message) {
log(level: Level.warning, message: message);
}
void error(String message) {
log(level: Level.error, message: message);
}
void wtf(String message) {
log(level: Level.wtf, message: message);
}
/// 网络请求日志
void network(
String method,
String url, {
dynamic data,
Map<String, dynamic>? headers,
int? statusCode,
String? response,
int duration = 0,
}) {
final message =
'''
[NETWORK] $method $url
Duration: ${duration}ms
Status: $statusCode
${headers != null ? 'Headers: $headers' : ''}
${data != null ? 'Request: $data' : ''}
${response != null ? 'Response: $response' : ''}
'''
.trim();
info(message);
}
/// 获取日志文件
Future<List<File>> getLogFiles() async {
if (!_isInitialized || _isDisposed || !config.enableFileLog) {
return [];
}
return await _fileLogger.getLogFiles();
}
/// 获取最近日志
Future<String> getRecentLogs({int lines = 100, String? filePath}) async {
if (!_isInitialized || _isDisposed || !config.enableFileLog) {
return '';
}
return await _fileLogger.getRecentLogs(lines: lines, filePath: filePath);
}
/// 强制刷新缓冲区
Future<void> flush() async {
if (!_isInitialized || _isDisposed || !config.enableFileLog) {
return;
}
await _fileLogger.flush();
}
/// 销毁资源
Future<void> dispose() async {
if (_isDisposed) return;
_isDisposed = true;
try {
await _fileLogger.dispose();
} catch (e) {
// 忽略清理错误
}
try {
_consoleLogger.dispose();
} catch (e) {
// 忽略清理错误
}
}
}
4.3 第三层:性能优化与实现
最考验功力的地方在于 FileLogger 的实现。频繁打开/关闭文件是非常耗电且影响性能的。
4.3.1 异步缓冲区机制 (file_logger.dart)
为了极致性能,我们引入了 Buffer (缓冲区)。日志产生后不立即写磁盘,而是先存在内存里。
-
触发条件1:缓冲区达到 100 条日志。
-
触发条件2:定时器每隔 1s 强制刷新。
///
/// Description: 文件日志处理器
/// CreateDate: 2025/12/17 11:37
/// Author: agg
///
class FileLogger {
final String? customDirectory;
final int maxFileSize;
final int maxBufferSize;
final String filePrefix;
final String fileExtension;
final int daysToKeep;File? _currentFile; final List<String> _buffer = []; Timer? _periodicTimer; late String _logDirectory; final DateFormat _dateFormat = DateFormat('yyyy-MM-dd'); final DateFormat _timeFormat = DateFormat('HH:mm:ss.SSS'); bool _isInitialized = false; bool _isDisposed = false; FileLogger({ this.customDirectory, this.maxFileSize = 5 * 1024 * 1024, this.maxBufferSize = 100, this.filePrefix = 'app', this.fileExtension = 'log', this.daysToKeep = 7, }); /// 初始化文件日志 Future<void> initialize() async { if (_isDisposed) { throw StateError('FileLogger has been disposed'); } try { Directory directory; if (customDirectory != null && customDirectory!.isNotEmpty) { directory = Directory(customDirectory!); } else { Directory? appDir; if (Platform.isAndroid) { appDir = await getExternalStorageDirectory(); } else { appDir = await getApplicationDocumentsDirectory(); } directory = Directory('${appDir?.path}/logs'); // Android 日志路径: /sdcard/Android/data/<package_name>/files/logs/ // iOS 日志路径: <AppHome>/Documents/logs } _logDirectory = directory.path; if (!await directory.exists()) { await directory.create(recursive: true); } await _setupLogFile(); await _cleanOldLogs(); _isInitialized = true; // 写入初始化日志 await _writeToBuffer( '========== Log System Initialized ==========', skipBuffer: true, ); // 启动定时器1s定期刷新缓冲区 _periodicTimer = Timer.periodic(Duration(seconds: 1), (_) { if (_buffer.isNotEmpty) { _flushBuffer(); } }); } catch (e) { _isInitialized = false; rethrow; } } /// 设置日志文件 Future<void> _setupLogFile() async { final now = DateTime.now(); final dateStr = _dateFormat.format(now); final baseName = '${_logDirectory}/$filePrefix\_$dateStr'; // 检查是否存在今天的日志文件 String filePath = '$baseName.$fileExtension'; File file = File(filePath); // 如果文件存在且超过大小限制,创建带时间戳的新文件 if (await file.exists()) { final length = await file.length(); if (length >= maxFileSize) { final timeStr = DateFormat('HH-mm-ss').format(now); filePath = '${baseName}_$timeStr.$fileExtension'; file = File(filePath); } } _currentFile = file; } /// 写入日志 Future<void> log({required Level level, required String message}) async { if (!_isInitialized || _isDisposed) return; try { final timestamp = _timeFormat.format(DateTime.now()); var levelStr = ""; if (level == Level.info) { levelStr = "INFOS"; } else { levelStr = level.name.toUpperCase(); } await _writeToBuffer('[$timestamp][$levelStr] $message'); } catch (e) { // 文件日志失败时不应影响应用运行 print('File logger error: $e'); } } /// 写入缓冲区 Future<void> _writeToBuffer( String logEntry, { bool skipBuffer = false, }) async { _buffer.add('$logEntry\n'); // 如果缓冲区满了或者需要立即写入,则刷新到文件 if (_buffer.length >= maxBufferSize || skipBuffer) { await _flushBuffer(); } } /// 刷新缓冲区到文件 Future<void> _flushBuffer() async { if (_buffer.isEmpty || _currentFile == null || _isDisposed) return; try { await _currentFile!.writeAsString( _buffer.join(), mode: FileMode.append, flush: true, ); _buffer.clear(); } catch (e) { // 如果写入失败,保留缓冲区内容下次再试 print('Failed to flush log buffer: $e'); } } /// 清理旧日志文件 Future<void> _cleanOldLogs() async { try { final directory = Directory(_logDirectory); if (!await directory.exists()) return; final files = await directory .list() .where((entity) => entity is File) .cast<File>() .where((file) => file.path.endsWith('.$fileExtension')) .toList(); final cutoffDate = DateTime.now().subtract(Duration(days: daysToKeep)); for (final file in files) { try { final stat = await file.stat(); if (stat.modified.isBefore(cutoffDate)) { await file.delete(); } } catch (e) { // 忽略单个文件删除失败 } } } catch (e) { // 清理失败不应影响主要功能 } } /// 获取所有日志文件 Future<List<File>> getLogFiles() async { if (!_isInitialized || _isDisposed) return []; final directory = Directory(_logDirectory); if (!await directory.exists()) return []; try { final files = await directory .list() .where((entity) => entity is File) .cast<File>() .where((file) => file.path.endsWith('.$fileExtension')) .toList(); // 按修改时间排序(最新的在前) files.sort((a, b) { try { final statA = a.statSync(); final statB = b.statSync(); return statB.modified.compareTo(statA.modified); } catch (e) { return 0; } }); return files; } catch (e) { return []; } } /// 获取最近日志 Future<String> getRecentLogs({int lines = 100, String? filePath}) async { if (!_isInitialized || _isDisposed) return ''; try { File file; if (filePath != null) { file = File(filePath); } else { final files = await getLogFiles(); if (files.isEmpty) return 'No log files found'; file = files.first; } if (!await file.exists()) return 'Log file not found'; final content = await file.readAsString(); final linesList = content.split('\n'); final start = linesList.length > lines ? linesList.length - lines : 0; return linesList.sublist(start).join('\n'); } catch (e) { return 'Error reading logs: $e'; } } /// 强制刷新缓冲区 Future<void> flush() => _flushBuffer(); /// 销毁资源 Future<void> dispose() async { if (_isDisposed) return; _isDisposed = true; _periodicTimer?.cancel(); await _flushBuffer(); _buffer.clear(); _currentFile = null; }}
4.3.2 控制台美化 (console_logger.dart)
通过集成 logger 库,我们可以让输出带上边框、时间戳和颜色,方便快速定位问题。
///
/// Description: 控制台日志处理器
/// CreateDate: 2025/12/17 11:34
/// Author: agg
///
class ConsoleLogger {
late Logger _logger;
ConsoleLogger() {
_initLogger();
}
void _initLogger() {
_logger = Logger(
printer: PrettyPrinter(
methodCount: 0,
errorMethodCount: 8,
lineLength: 120,
colors: false,
printEmojis: false,
printTime: false,
noBoxingByDefault: true,
),
filter: DevelopmentFilter(),
level: Level.verbose,
);
}
/// 记录日志到控制台
void log({
required Level level,
required String message,
dynamic error,
StackTrace? stackTrace,
}) {
try {
final fullMessage = error != null ? '$message\nError: $error' : message;
switch (level) {
case Level.verbose:
_logger.v(fullMessage);
break;
case Level.debug:
_logger.d(fullMessage);
break;
case Level.info:
_logger.i(fullMessage);
break;
case Level.warning:
_logger.w(fullMessage);
break;
case Level.error:
_logger.e(fullMessage);
break;
case Level.wtf:
_logger.wtf(fullMessage);
break;
case Level.nothing:
break;
}
} catch (e) {
// 防止日志记录本身导致应用崩溃
print('Console logger error: $e');
}
}
/// 销毁资源
void dispose() {
// Logger库没有dispose方法,这里预留接口
}
}
5. ✅ 小结
这套方案通过三层架构实现了职责分离,通过异步缓冲区解决了性能瓶颈,通过 LogConfig 适配了多环境需求。
另外,由于本人能力有限,如有错误,敬请批评指正,谢谢。