开源鸿蒙 Flutter for OpenHarmony:logger 实战(本地日志 + 全局异常捕获)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

离线笔记做到 Day8,功能看起来已经很“像样”了,但只要开始真机跑,就会遇到一个很现实的问题:用户说“闪退/点不了/没反应”,你没有任何线索

尤其是 OpenHarmony 场景下:

  • 有些问题只在真机/模拟器出现
  • 没接入远程日志平台(我们这个项目是纯离线)
  • print 很难统一格式,也不方便在手机上复制出来发给你排查

Day9 的目标:把日志做成“可用工具”

  • 统一日志格式(带时间、级别)
  • 在应用内保存一份“最近 300 行日志”
  • 加一个“应用日志”页面:复制/清空/手动写日志/模拟异常
  • 全局捕获未处理异常:FlutterError / PlatformDispatcher / runZonedGuarded

1. 今天用到的第三方库:logger(为什么不用 print)

print 的问题不在于它不能用,而在于:

  • 没级别(info/warn/error)
  • 没统一格式(时间、tag、堆栈)
  • 打出来之后,手机上很难“拿到一份完整日志”

logger 就是做这件事的:把日志当成一个正式能力来用。

添加依赖:

flutter pub add logger

2. 实现一个 AppLogger:既写控制台,也写“内存日志”

📌 文件:lib/shared/app_logger.dart

我们做一个单例 AppLogger,有两个输出方向:

  1. logger 输出到控制台(调试时看)
  2. 自己维护一个 lines(最多 300 行),用于 App 内展示/复制
class AppLogger {
  AppLogger._();

  static final AppLogger instance = AppLogger._();

  final _logger = Logger(
    printer: PrettyPrinter(
      methodCount: 0,
      errorMethodCount: 20,
      colors: false,
      printEmojis: false,
      dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart,
    ),
  );

  final lines = <String>[].obs;
}

然后封装 3 个最常用的方法:i/w/e

void i(String message) {
  _logger.i(message);
  _add('I', message);
}

void w(String message, {Object? error, StackTrace? stackTrace}) {
  _logger.w(message, error: error, stackTrace: stackTrace);
  _add('W', _format(message, error, stackTrace));
}

void e(String message, {Object? error, StackTrace? stackTrace}) {
  _logger.e(message, error: error, stackTrace: stackTrace);
  _add('E', _format(message, error, stackTrace));
}

📌 _add 里做两件事:

  • 拼一个本地时间戳(更适合读)
  • 超过 300 行就裁掉最旧的
void _add(String level, String text) {
  final now = DateTime.now();
  final ts =
      '${now.year}-${_two(now.month)}-${_two(now.day)} ${_two(now.hour)}:${_two(now.minute)}:${_two(now.second)}';
  lines.add('[$ts][$level] $text');
  if (lines.length > 300) {
    lines.removeRange(0, lines.length - 300);
  }
}

3. 全局异常捕获:把“没抓住的异常”也写进日志

很多异常不是你主动 try/catch 的,它会直接冒到框架层。
这一步就是把三条常用的“兜底入口”接上。

📌 文件:lib/main.dart

3.1 FlutterError.onError(Flutter 框架错误)

FlutterError.onError = (details) {
  FlutterError.presentError(details);
  AppLogger.instance.e(
    'FlutterError',
    error: details.exception,
    stackTrace: details.stack,
  );
};

3.2 PlatformDispatcher.instance.onError(更底层的 uncaught)

PlatformDispatcher.instance.onError = (error, stack) {
  AppLogger.instance.e('PlatformDispatcher', error: error, stackTrace: stack);
  return true;
};

3.3 runZonedGuarded(兜底一层)

runZonedGuarded(
  () => runApp(const NotesApp()),
  (error, stack) =>
      AppLogger.instance.e('Zone', error: error, stackTrace: stack),
);

到这里,哪怕你忘了 try/catch,至少也能在“应用日志”页看到异常堆栈。


4. 做一个“应用日志”页:复制/清空/模拟异常

📌 文件:lib/features/debug/ui/app_logs_page.dart

4.1 日志列表:用 Obx 实时刷新

linesRxList<String>,所以用 Obx 渲染:

Expanded(
  child: Obx(() {
    final lines = logger.lines;
    if (lines.isEmpty) return const Center(child: Text('暂无日志'));
    return ListView.builder(
      itemCount: lines.length,
      itemBuilder: (_, i) => SelectableText(lines[i]),
    );
  }),
)

4.2 顶部按钮:复制/清空

复制时直接把整份 dump() 放到剪贴板:

await Clipboard.setData(ClipboardData(text: logger.dump()));

清空就一句:

logger.clear();

4.3 “模拟异常”按钮(方便自测)

这一步不是给用户用的,是开发时确认“全局兜底有没有生效”:

Timer.run(() => throw StateError('Day9 模拟异常'));

点一下,日志页应该能看到一条 error 级别的堆栈信息。

📷 截图位(建议 2~3 张)

![Day9-日志入口按钮](图_day9_log_entry.png)
![Day9-日志页列表与复制](图_day9_log_page.png)
![Day9-模拟异常日志出现](图_day9_throw_log.png)

5. 给日志页加入口:放在列表页右上角

📌 文件:lib/features/note/ui/notes_list_page.dart

IconButton(
  onPressed: () {
    Navigator.of(context).push(
      MaterialPageRoute(builder: (_) => const AppLogsPage()),
    );
  },
  icon: const Icon(Icons.bug_report),
  tooltip: '日志',
),

这样真机遇到问题时,让对方打开日志页点“复制”,直接把文本发过来,排查成本会小很多。


📷
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6. 自测清单(Day9)

  • 打开日志页,点“写日志” → 列表出现一条 info 记录
  • 点“模拟异常” → 列表出现 error + 堆栈
  • 点“复制” → 能把日志完整复制到剪贴板(随便粘贴到备忘录验证)
  • 点“清空” → 列表清空
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐