log_manager_plugin

Flutter日志管理插件,支持控制台输出、文件存储、日志查看、压缩上报等功能。

目录

功能

  • 控制台日志输出(基于logger)
  • 文件日志存储,按天管理
  • Debug/Release模式分别配置
  • Dio网络请求日志拦截
  • 日志文件自动分块(默认10MB)
  • 自动清理过期日志(按天数)
  • 日志文件压缩
  • 内置日志查看器UI
  • 日志上报(支持文件/字符串两种方式)

安装

方式1:从 pub.dev 安装(推荐)

dependencies:
  log_manager_plugin: ^1.0.2

方式2:从 GitHub 安装

dependencies:
  log_manager_plugin:
    git:
      url: https://github.com/xiaoli55979/log_manager_plugin.git
      ref: main  # 或指定 tag: v1.0.0

然后运行:

flutter pub get

快速开始

初始化

重要:在主应用的 main.dart 中初始化一次即可,所有插件共享同一个日志实例。

import 'package:log_manager_plugin/log_manager_plugin.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 只需初始化一次
  await LogManager.instance.init(
    const LogManagerConfig(
      enabled: true,
      enableConsoleInDebug: true,
      enableConsoleInRelease: false,
      enableFileLog: true,
      maxFileSize: 10 * 1024 * 1024,   // 10MB
      maxRetentionDays: 7,              // 保留7天
      logLevel: Level.debug,
      logDirectory: 'logs',
      deleteAfterUpload: true,          // 上报后删除压缩文件
      maxBatchSize: 100 * 1024,         // 字符串上报每批100KB
    ),
  );
  
  runApp(const MyApp());
}

日志输出

LogManager.v('verbose');
LogManager.d('debug');
LogManager.i('info');
LogManager.w('warning');
LogManager.e('error', error: e, stackTrace: stackTrace);
LogManager.f('fatal');

Dio拦截器

final dio = Dio();
dio.interceptors.add(
  LogManagerInterceptor(
    requestHeader: true,      // 打印请求头
    requestBody: true,         // 打印请求体
    responseHeader: true,      // 打印响应头
    responseBody: true,        // 打印响应体
    error: true,               // 打印错误信息
    compact: true,             // 紧凑模式(超长内容截断)
    maxWidth: 90,              // 紧凑模式最大宽度
  ),
);

日志查看

按日期查看(推荐)

Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const LogViewerByDate()),
);

功能:

  • 按日期分组
  • 统计信息
  • 多选操作
  • 压缩/删除
  • 查看内容

增强版查看器

Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const EnhancedLogViewer()),
);

功能:

  • 日志级别着色
  • 搜索过滤
  • 级别过滤
  • 复制内容

基础版查看器

Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const LogViewerPage()),
);

文件管理

基础操作

// 获取所有日志文件
final files = await LogManager.getAllLogFiles();

// 获取日志目录路径
final path = LogManager.logDirectoryPath;

// 清空所有日志
await LogManager.clearAllLogs();

按日期管理

// 按日期分组获取日志文件
final grouped = await LogFileManager.instance.getLogFilesByDate();
// 返回: {'20231128': [file1, file2], '20231127': [file3]}

// 删除指定日期的日志
await LogFileManager.instance.deleteLogsByDate('20231128');

日志压缩

// 压缩所有日志
final zipFile = await LogManager.compressLogs();

// 压缩指定日期的日志
final zipFile = await LogFileManager.instance.compressLogsByDate('20231128');

// 压缩指定文件
final files = [file1, file2];
final zipFile = await LogFileManager.instance.compressSpecificLogs(files);

日志统计

// 获取统计信息
final stats = await LogFileManager.instance.getStatistics();
print('总文件数: ${stats.totalFiles}');
print('总大小: ${stats.totalSize}');
print('日期数: ${stats.dateCount}');
print('最早日期: ${stats.earliestDate}');
print('最新日期: ${stats.latestDate}');

清理旧格式日志

如果从旧版本升级,可以清理旧格式的日志文件:

await LogFileManager.instance.cleanLegacyLogFiles();

高级用法

多插件项目使用

如果你的项目中有多个插件都依赖 log_manager_plugin

// ✅ 正确做法:只在主应用中初始化一次
// main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await LogManager.instance.init(const LogManagerConfig(...));
  runApp(const MyApp());
}

// plugin_a.dart - 插件A直接使用
class PluginA {
  void doSomething() {
    LogManager.d('插件A: 开始执行');
    // ...
  }
}

// plugin_b.dart - 插件B直接使用
class PluginB {
  void doSomething() {
    LogManager.i('插件B: 处理数据');
    // ...
  }
}

// ❌ 错误做法:不要在每个插件中都初始化
// 这会导致配置被覆盖,且浪费资源

说明:即使未初始化就调用日志方法,也不会报错,只是不会输出日志。

运行时更新配置

// 动态修改配置
await LogManager.instance.updateConfig(
  LogManagerConfig(
    enableConsoleInRelease: true,  // 临时开启Release日志
    logLevel: Level.trace,          // 调整日志级别
  ),
);

获取当前配置

final config = LogManager.config;
print('当前日志级别: ${config.logLevel}');
print('文件日志: ${config.enableFileLog}');

日志上报

方式1:文件上传

适合支持 multipart/form-data 的接口。

// 设置回调
LogReporter.instance.setUploadCallback((zipFile) async {
  try {
    final dio = Dio();
    final formData = FormData.fromMap({
      'file': await MultipartFile.fromFile(zipFile.path, filename: 'logs.zip'),
      'deviceId': 'xxx',
      'timestamp': DateTime.now().toIso8601String(),
    });
    
    final response = await dio.post('https://your-api.com/upload', data: formData);
    return response.statusCode == 200;
  } catch (e) {
    return false;
  }
});

// 上报所有日志
await LogReporter.instance.uploadLogs();

// 上报指定日期
await LogReporter.instance.uploadLogsByDate('20231128');

// 自定义参数
await LogReporter.instance.uploadLogs(
  deleteAfterUpload: false,  // 保留压缩文件
);

方式2:字符串分批上传

适合只支持 JSON 格式的接口。

// 设置回调
LogReporter.instance.setUploadStringCallback((batches) async {
  try {
    final dio = Dio();
    for (final batch in batches) {
      await dio.post('https://your-api.com/upload-string', data: {
        'batchIndex': batch.batchIndex,
        'totalBatches': batch.totalBatches,
        'content': batch.content,
        'fileName': batch.fileName,
        'date': batch.date,
      });
    }
    return true;
  } catch (e) {
    return false;
  }
});

// 上报所有日志
await LogReporter.instance.uploadLogsAsString();

// 上报指定日期
await LogReporter.instance.uploadLogsByDateAsString('20231128');

// 自定义批次大小
await LogReporter.instance.uploadLogsAsString(
  maxBatchSize: 50 * 1024,  // 每批50KB
);

配置优先级

上报参数的优先级:方法参数 > 配置项 > 默认值

// 在初始化时统一配置
await LogManager.instance.init(
  const LogManagerConfig(
    deleteAfterUpload: false,  // 默认保留压缩文件
    maxBatchSize: 200 * 1024,  // 默认每批200KB
  ),
);

// 调用时可以临时覆盖
await LogReporter.instance.uploadLogs(
  deleteAfterUpload: true,  // 这次删除
);

配置参数

参数 类型 默认值 说明
enabled bool true 是否启用
enableConsoleInDebug bool true Debug模式控制台输出
enableConsoleInRelease bool false Release模式控制台输出
enableFileLog bool kDebugMode 文件日志(默认只在Debug模式)
maxFileSize int 10MB 单文件最大大小
maxRetentionDays int 7 保留天数
logLevel Level debug 日志级别
logDirectory String 'logs' 日志目录
deleteAfterUpload bool true 上报成功后删除压缩文件
maxBatchSize int 100KB 字符串上报每批大小

日志级别

日志级别从低到高:

  • Level.trace - 追踪(最详细)
  • Level.debug - 调试
  • Level.info - 信息
  • Level.warning - 警告
  • Level.error - 错误
  • Level.fatal - 致命(最严重)

级别过滤规则

配置的 logLevel 会同时影响控制台和文件输出:

const LogManagerConfig(
  logLevel: Level.warning,  // 只输出 warning、error、fatal
)

// 这些日志会被过滤掉(不输出到控制台和文件)
LogManager.v('trace 日志');   // ❌ 被过滤
LogManager.d('debug 日志');   // ❌ 被过滤
LogManager.i('info 日志');    // ❌ 被过滤

// 这些日志会正常输出
LogManager.w('warning 日志'); // ✅ 输出
LogManager.e('error 日志');   // ✅ 输出
LogManager.f('fatal 日志');   // ✅ 输出

建议配置

  • 开发环境Level.debugLevel.trace - 查看详细信息
  • 生产环境Level.infoLevel.warning - 只记录重要信息,减少日志量

文件管理策略

  • 按天创建日志文件:log_20231128_001.txt
  • 单文件超过限制自动创建新文件:log_20231128_002.txt
  • 自动删除超过保留天数的文件
  • 应用启动和跨天时自动清理

完整使用示例

import 'package:flutter/material.dart';
import 'package:log_manager_plugin/log_manager_plugin.dart';
import 'package:dio/dio.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 1. 初始化日志系统
  await LogManager.instance.init(
    const LogManagerConfig(
      enabled: true,
      enableConsoleInDebug: true,
      enableConsoleInRelease: false,
      enableFileLog: true,
      maxFileSize: 10 * 1024 * 1024,
      maxRetentionDays: 7,
      logLevel: Level.debug,
      deleteAfterUpload: true,
      maxBatchSize: 100 * 1024,
    ),
  );
  
  // 2. 配置Dio拦截器
  final dio = Dio();
  dio.interceptors.add(
    LogManagerInterceptor(
      requestHeader: true,
      requestBody: true,
      responseBody: true,
      compact: true,
    ),
  );
  
  // 3. 配置日志上报
  LogReporter.instance.setUploadCallback((zipFile) async {
    try {
      final formData = FormData.fromMap({
        'file': await MultipartFile.fromFile(zipFile.path),
      });
      final response = await dio.post('https://api.example.com/logs', data: formData);
      return response.statusCode == 200;
    } catch (e) {
      return false;
    }
  });
  
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('日志管理示例')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: () {
                  // 输出各级别日志
                  LogManager.d('这是调试日志');
                  LogManager.i('这是信息日志');
                  LogManager.w('这是警告日志');
                  LogManager.e('这是错误日志');
                },
                child: const Text('输出日志'),
              ),
              ElevatedButton(
                onPressed: () {
                  // 打开日志查看器
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => const LogViewerByDate(),
                    ),
                  );
                },
                child: const Text('查看日志'),
              ),
              ElevatedButton(
                onPressed: () async {
                  // 上报日志
                  final success = await LogReporter.instance.uploadLogs();
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text(success ? '上报成功' : '上报失败')),
                  );
                },
                child: const Text('上报日志'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

更多示例

查看 example 目录获取完整示例代码。

常见问题

1. 多个插件都使用这个日志插件,需要每个插件都初始化吗?

不需要! 只在主应用的 main.dart 中初始化一次即可。

// 主应用 main.dart
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await LogManager.instance.init(const LogManagerConfig(...));  // 只初始化一次
  runApp(const MyApp());
}

// 插件A、插件B、插件C... 直接使用
LogManager.d('来自插件A的日志');
LogManager.i('来自插件B的日志');

所有插件共享同一个日志实例,日志会统一管理和存储。

2. 日志文件存储在哪里?

  • iOS: Application Support/logs/
  • Android: 应用数据目录/logs/

可以通过 LogManager.logDirectoryPath 获取完整路径。

3. 如何在Release模式下查看日志?

await LogManager.instance.init(
  const LogManagerConfig(
    enableConsoleInRelease: true,  // 开启Release控制台输出
  ),
);

4. 日志文件太大怎么办?

调整配置参数:

const LogManagerConfig(
  maxFileSize: 5 * 1024 * 1024,  // 减小单文件大小到5MB
  maxRetentionDays: 3,            // 只保留3天
)

5. 如何只记录/上报错误日志?

方法1:配置日志级别(推荐)

const LogManagerConfig(
  logLevel: Level.error,  // 只记录 error 和 fatal
)

这样只有 LogManager.e()LogManager.f() 的日志会被写入文件。

方法2:运行时动态调整

// 正常情况记录所有日志
await LogManager.instance.init(const LogManagerConfig(logLevel: Level.debug));

// 生产环境只记录错误
await LogManager.instance.updateConfig(const LogManagerConfig(logLevel: Level.error));

6. 上报失败怎么办?

日志文件会保留在本地,可以稍后重试:

final success = await LogReporter.instance.uploadLogs(
  deleteAfterUpload: false,  // 失败时不删除
);

注意事项

  1. 初始化时机:必须在 WidgetsFlutterBinding.ensureInitialized() 之后初始化
  2. 文件权限:iOS/Android 会自动处理,无需额外配置
  3. 性能影响:文件写入是异步的,不会阻塞主线程
  4. 日志级别logLevel 配置会同时过滤控制台和文件输出,低于该级别的日志不会被记录
  5. 日志安全:上报前建议加密敏感信息
  6. 网络请求:Dio拦截器会记录完整请求响应,注意数据量

License

MIT