Flutter for OpenHarmony 网络日志监控:Pretty Dio Logger 接入实战

Flutter for OpenHarmony 网络日志监控:Pretty Dio Logger 接入实战 最近在把公司 Flutter 端往 OpenHarmony 上迁移时我遇到的最头疼的问题不是页面能不能跑起来而是网络层一旦出问题根本不知道请求到底发出去没有、响应回来了什么、状态码是多少。真机调试不像浏览器有 F12 面板也不可能每个开发人员都熟练配置抓包工具。后来我把 Dio 的请求日志拦截器 Pretty Dio Logger 接到项目里配合 hdc 工具看日志整个网络请求监控的效率直接起飞。这篇文章就把我在 Flutter for OpenHarmony 上集成 Pretty Dio Logger 的完整过程、参数调优和踩坑记录分享出来给同样在做 OpenHarmony 适配的朋友一个可以直接抄的作业。1. 项目背景与方案选型网络请求监控为什么选了 Pretty Dio Logger1.1 Flutter 迁移到 OpenHarmony 后调试网络请求的痛点先说背景。OpenHarmony 的适配不是把 APK 装上去就行Flutter 应用要跑在 OpenHarmony 设备上需要用到 OpenHarmony SIG 组维护的 Flutter 适配 SDK构建产物是 HAP 包通过 hdc 命令部署到开发板或者真机上。我在 RK3568 开发板上调试时就发现网络请求一旦出问题排查手段非常有限。首先是没法直接用 Charles、Fiddler 这类工具抓包尤其是走 HTTPS 的请求还得在设备上装证书、配代理。开发板本身存储和性能都有限频繁装证书、改 Wi-Fi 代理非常折腾。其次是 Flutter 层自带的 debugPrint 打印默认只输出 1000 字符左右的内容超长就会被截断JSON 响应体稍微一长就看不全更别说格式化了。第三个痛点是日志和时间线对不上尤其是并发请求多的时候一个页面同时发三四个请求日志混在一起根本分不清哪条是哪个接口回的。我也试过自己写一个 Dio 拦截器来打印日志但写着写着就变成了一个半吊子工具要处理的边界情况越来越多请求头要不要打、响应体太长怎么截断、二进制流内容会不会把控制台刷爆、日志里带敏感信息怎么办。与其造这个轮子不如直接用现成的 Pretty Dio Logger它本身就是专门为 Dio 设计的一个日志拦截器输出格式经过长期打磨比我自己写的要规范得多。1.2 Pretty Dio Logger 能帮你做什么Pretty Dio Logger 说简单点就是给 Dio 网络库加一个拦截器在请求发出之前和响应回来之后自动打印格式化的日志。它最明显的优点是输出自带颜色和信息分级虽然不是每次都必须在终端看颜色但请求方法、URL、请求头、请求体、响应状态码、响应耗时、响应体这些关键信息全都会按固定结构打印出来查找问题的时候一眼就能定位到关键行。它内部是依赖 Dio 的拦截器机制实现的本质上就是 Interceptor所以接入成本几乎为零。你不需要改业务代码里的任何请求调用只需要在创建 Dio 实例的时候把它挂到 interceptors 列表里后面的所有请求和响应都会自动被记录。对于我这种维护老项目、不想大规模改动网络层的人来说这一点非常友好。另外它支持按需开关。比如我不关心响应头只关心响应体那就在构造参数里把 responseHeader 设成 false。再比如调试阶段想看完整请求体但生产环境绝对不能打敏感信息就可以用 canLog 或者 logPrint 做环境判断。这套灵活度比很多团队自己封装的日志拦截器要完善得多。1.3 为什么不用抓包工具而是选拦截器有人可能会问抓包工具不是更通用吗这里有个关键区别抓包工具是独立于应用的能抓所有流量但抓不到应用内部已经加密或者走了非 HTTP 通道的数据而拦截器是在应用进程内部工作的能看到业务代码这一层的完整上下文。对于 Flutter 应用来说很多请求是经过 Dio 封装后发出的响应回来之后还会在拦截器里做统一状态码处理、token 刷新、错误上报等逻辑这些在抓包工具里是看不到的。所以我的做法是两层配合底层网络抓包用工具应用层日志用拦截器。Pretty Dio Logger 负责把 Dio 这一层的数据完整记录下来抓包工具负责验证 DNS、TLS 握手、具体报文。在日常开发调试中90% 以上的问题其实通过应用层日志就能定位根本不用走到抓包那一步。我还对比过其他几个方案比如 dio_logger、dio_interceptor 之类的插件但 Pretty Dio Logger 的参数设计和输出排版最成熟更新频率也比较稳定。以下是几个常见方案的简单对比方案接入工作量日志格式参数灵活度OpenHarmony 兼容性自写拦截器高自定义质量难保证高无额外依赖纯 Dart 即可dio_logger低较弱输出较简陋一般纯 Dart可用Pretty Dio Logger极低格式化完善信息分级清晰高纯 Dart可用最后我选了 Pretty Dio Logger理由非常直接它不依赖任何平台原生代码在 OpenHarmony 的 Flutter 环境里不用做额外适配这是先决条件。在此基础上它的输出格式、参数控制和社区活跃度都符合预期。2. Flutter for OpenHarmony 环境搭建与依赖接入2.1 搭建可运行 ohos 工程的 Flutter 开发环境要跑 Flutter for OpenHarmony首先要有一台装好 Flutter SDK 的电脑。我用的是 mac所以环境变量配置相对简单。需要注意的是OpenHarmony 的 Flutter 适配 SDK 虽然 API 和官方 Flutter 基本对齐但版本节奏是独立的一定要通过 OpenHarmony SIG 的文档去拉取对应的 flutter sdk不要直接用社区版 Flutter 的官方渠道否则flutter create --platforms ohos的时候会提示平台不支持。第二步是安装 DevEco Studio 和 OpenHarmony SDK。这一步很多朋友容易卡住因为我一开始也找不到 SDK 路径在哪。装完 DevEco Studio 之后需要在 Preferences - SDK Manager 里确认 OpenHarmony SDK 已经下载并且记住路径。这个路径后面要配到 FLUTTER_OHOS_HOME 或者 .bash_profile /.zshrc 里否则 flutter 命令找不到 ohos 的编译工具链。还需要把 hdc 工具配置到 PATH 环境变量里。hdc 是 OpenHarmony 的命令行调试工具作用和 adb 之于 Android 类似。打开终端输入 hdc -v 能输出版本号就说明工具链基本通了。用数据线把 RK3568、RK3588 这类开发板或者真机连到电脑后输入 hdc list targets 能看到设备序列号说明连接正常。这里顺便说一下很多人在安装阶段遇到 flutter doctor 报错不要太焦虑。flutter doctor 主要是给 Android/iOS 用的对 ohos 支持情况的检查并不完整只要 DevEco Studio 能创建 HarmonyOS 工程hdc 能连上设备你的 ohos 工具链基本就 OK 了。我的经验是以“能否成功构建出 HAP 包”作为环境合格的标准而不是以 flutter doctor 全绿为标准。2.2 创建或改造 Flutter 工程支持 OpenHarmony假设你已经有了一个 Flutter 项目想给它加上 OpenHarmony 平台支持。在正确配置环境下只需要在项目根目录执行flutter create --platformsohos .这个命令会生成 ohos 目录以及对应的工程配置文件。如果在旧版本 Flutter 上执行发现没有 ohos 平台选项八成是你拉的不是 OpenHarmony 适配版 SDK回 2.1 重新确认一下工具链来源。对于新项目可以直接用flutter create --platformsohos --org com.example --project-name my_app my_app项目生成之后在 device 上选择 OpenHarmony 设备执行 flutter run 就能跑起来。有一点要注意如果你的电脑上同时连着 Android 设备和 OpenHarmony 设备flutter run 可能会默认选 Android。我记得第一次跑的时候没注意日志怎么都不对后来发现是跑到了旁边的 Android 手机上。最好只连一个设备或者用 hdc 把不需要的设备断开。另外如果你之前构建过 Android 端ohos 目录和 android 目录互不干扰两者可以共存。构建 OpenHarmony 产物时flutter build hap 会生成 HAP 包这个包的路径一般在 build/ohos 下可以用 hdc app install 安装到设备上。如果遇到编译后磁盘占用大的问题可以删除 build 目录下的中间产物重新构建OpenHarmony 编译产生的文件很多是可以重复生成的不需要手动保留。2.3 引入 dio 与 pretty_dio_logger 依赖OpenHarmony 支持的 Flutter 工程里pubspec.yaml 的写法和官方 Flutter 基本一致。依赖添加如下dependencies: flutter: sdk: flutter dio: ^5.4.0 pretty_dio_logger: ^1.3.1指定版本的时候需要注意dio 5.x 和 dio 4.x 有些 API 不一样比如超时时间的类型从 int 毫秒变成了 Duration 对象。如果你的项目是从 dio 4.x 升上来的建议先统一到 5.x再接入 Pretty Dio Logger因为新版拦截器接口和超时配置都不太一样混着写容易踩坑。pretty_dio_logger 是纯 Dart 实现不依赖任何平台通道所以不需要在 OpenHarmony 侧做原生配置。执行 flutter pub get 之后只要没有语法错误这个包就可以直接用了。这也是我推荐它的原因之一在 OpenHarmony 这种新平台上能少一个平台相关依赖就少一个否则哪天某个原生插件没有 ohos 实现你就要自己去写平台通道。3. Pretty Dio Logger 参数详解与进阶配置3.1 最小可用配置挂上拦截器就能看到日志接入的核心代码非常少两步就走完了。第一步创建 Dio 实例第二步挂上拦截器import package:dio/dio.dart; import package:pretty_dio_logger/pretty_dio_logger.dart; Dio createDio() { final dio Dio( BaseOptions( baseUrl: https://api.example.com, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 10), ), ); dio.interceptors.add( PrettyDioLogger( requestHeader: true, requestBody: true, responseHeader: true, responseBody: true, error: true, ), ); return dio; }这样配置完成之后项目里所有通过这个 Dio 实例发出的请求都会在终端打印对应的日志。注意一定是用同一个实例如果你在多个地方分别创建 Dio 实例拦截器没挂上的那部分请求依然不会有日志输出。最小配置跑通之后我建议你先看一次完整日志长什么样确认格式满足需求再根据实际场景去调参数。这个确认过程很重要因为不同项目的接口返回差异很大有的接口返回几 KB 的 JSON有的接口返回二进制流还有的接口只有状态码没有响应体这些场景对日志打印的要求是完全不同的。3.2 核心参数逐个拆解按需开关Pretty Dio Logger 的参数每个都对应一类日志内容。我把常用的参数整理成了表格实际配置时照着这个表来选参数默认值控制内容我的建议requestHeadertrue是否打印请求头调试期开排查鉴权问题很有用requestBodytrue是否打印请求体建议开POST 请求参数一目了然responseHeadertrue是否打印响应头默认开关注 set-cookie 和分页字段时用responseBodytrue是否打印响应体核心参数调试期基本必开errortrue是否打印错误信息建议开DioException 堆栈很有用compactfalse是否压缩打印格式真机终端建议 true开发板性能有限maxWidth90每行最大宽度根据终端宽度调过长会换行乱canLognull是否允许打印的全局开关生产环境设置 falselogPrintnull自定义日志输出函数可用来脱敏、写入文件、上报日志系统这里重点说一下 compact 和 maxWidth。compact 默认为 false此时日志会打印得非常详细但会有大量空行和装饰性字符在电脑终端上看很漂亮。但 OpenHarmony 开发板的串口控制台或者 hdc shell 里查看时宽屏支持往往不好装饰性字符会把界面打得非常乱。我实测在 RK3568 开发板上调试时compact 设为 true 之后日志行数少了将近一半定位问题的效率明显提升。maxWidth 控制的是单行输出最大宽度单位是字符数。默认 90 比较适合 IDE 自带的终端但如果你用的是 256 色终端或者把它接到 CI 日志系统建议适当调整。太长会换行太短又会把 URL 和 JSON 挤成很多行。命令行窗口可以临时拉伸的话我一般设置成 120 到 160一行能放下更多信息。canLog 是一个逻辑开关示例PrettyDioLogger( canLog: () kDebugMode, )注意 canLog 不是普通 bool而是一个返回 bool 的方法每次打印的时候会调用一次。如果你希望日志系统里可以动态控制某类请求是否打印这个方法非常有用。我之前做过一个实验给 canLog 传一个返回值由全局开关控制的方法测试阶段直接改全局变量就能所有页面日志全开不用重新发版。logPrint 是这个插件最灵活的地方默认是 debugPrint会走 Flutter 的 debug 输出通道。如果你想把日志同时输出到终端和文件就可以覆盖它PrettyDioLogger( logPrint: (log) { debugPrint(log); // 自己再把 log 写入本地文件 }, )这里有一个细节logPrint 的入参类型是 String?你把它打印出来之前要注意判空否则有些环境下会因为 null 导致格式化占位符异常。我的习惯是直接在回调里写if (log null) return;。3.3 进阶用法环境区分、脱敏与日志落盘实际项目中开发环境和生产环境往往共用一套代码区别只是 baseUrl 不同这时候日志打印策略也应该是分开的。生产环境绝对不能把登录接口的账号密码明文打到日志里更不能把 token 写到默认输出流。我的做法是在初始化 Dio 时根据当前环境传入不同的 PrettyDioLogger 配置PrettyDioLogger _buildLogger() { final isProd const bool.fromEnvironment(dart.vm.product); final logger PrettyDioLogger( compact: true, maxWidth: 120, canLog: () !isProd, ); if (isProd) { return logger; } // debug 环境额外做脱敏 logger.logPrint (log) { if (log null) return; var safe log.replaceAll(RegExp(r(?token)[^\s]), ***); safe safe.replaceAll(RegExp(r(?password:)[^]), ***); debugPrint(safe); }; return logger; }这个脱敏思路可能看起来简单但在实际项目里非常实用。请求体如果是一个 JSON 字符串密码字段就可能以 password:123456 的形式出现正则一替换日志里就看不到明文了。Authorization 头如果是以 Bearer token 的形式存在要把 token 字段也替换掉。这些信息一旦通过 CI 日志系统泄漏出去后果很严重所以我在日志插件里最重视的就是脱敏。日志落盘这件事OpenHarmony 设备的存储路径和 Android 不一样建议用 path_provider 的 getApplicationDocumentsDirectory 来获取应用私有目录然后往里面写日志文件。不要直接写绝对路径不同设备存储结构不一样容易踩坑。4. 真机调试实录用 hdc 和 hilog 排查网络问题4.1 连接 OpenHarmony 设备并确认系统信息在开发板上跑起 Flutter 应用之后我看日志一般有两种方式。第一种是在电脑终端直接看 flutter run 的输出这种方式最直观日志会实时滚动还带颜色。第二种是通过 hdc shell 进到设备里用 hilog 命令过滤日志适合应用已经安装运行、但不方便重新跑 flutter run 的场景。如果你的应用是装到设备上独立运行的建议先确认设备系统版本和型号避免日志协议对不上。常用命令hdc shell param get | grep -i const.product.name hdc shell param get | grep -i const.product.version第一条命令可以查看产品名第二条可以查看版本号。这是排查设备型号差异最直接的方式。之前我在 RK3568 和 RK3588 两块开发板上跑同一个 Flutter 应用表现完全不一样一查产品名才发现系统的图形栈版本有差异。如果要看设备上已经安装了哪些应用用hdc shell bm list | grep your.package.namebm list 会列出所有 bundle 名如果你安装的 HAP 包没有出现在列表里说明没装成功那日志里当然看不到任何内容。设备标识信息可以通过hdc shell param get const.product.devudid之类的属性获取不同系统版本字段名略有差异不确定的时候就多 grep 几个关键字。4.2 一次真实请求的日志解读下面这段是 Pretty Dio Logger 在 compact: true 模式下的一段输出我模拟了一个用户登录接口的调试现场┌──────────────────────────────────────────────────────────────── │ ╭ GET http://10.0.0.2:8080/api/v1/login │ ├ headers: │ │ Authorization: Basic xxxxxx │ ├ queryParameters: │ │ username: admin │ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ │ ╭ GET http://10.0.0.2:8080/api/v1/login │ ├ statusCode: 200 │ ├ took: 63ms │ ├ headers: │ │ content-type: application/json │ ├ body: │ │ {code:0,token:eyJhbGci...} ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄第一次看到这个输出可能觉得装饰字符有点多但仔细看的话它的结构是非常合理的。上面是请求部分包含方法、URL、请求头和查询参数下面是响应部分包含状态码、耗时、响应头和响应体。我把这些信息用来定位问题时最常用的是状态码和耗时。比如状态码变成 401我第一步就去检查请求头的 Authorization 是不是被成功带上了如果是再去看 baseUrl 是否指向了正确的环境。比如 took 显示 3 秒响应体还没返回那就是网络层超时可能是开发板 Wi-Fi 信号不好也可能是服务端接口本身慢。这种定位思路比用抓包工具去一层层看报文要快得多。还有一个小技巧如果某个接口返回的 JSON 结构复杂我会先把响应体复制到本地 JSON 解析工具里格式化一遍确认字段层级再对着代码里的模型类检查字段名是否匹配。Flutter 端的 JSON 解析错误往往不是语法错误而是字段名对不上看了 Pretty Dio Logger 打印的响应体基本能一眼看出问题。4.3 拦截器顺序与业务错误处理的配合Dio 的拦截器是有顺序的添加顺序就是执行顺序。请求阶段先添加的拦截器先执行响应阶段先添加的拦截器后执行。这个顺序会直接影响你看到的日志内容。我的建议是把 PrettyDioLogger 放在拦截器列表的最后一个也就是最后打印日志dio.interceptors.add(AuthInterceptor()); dio.interceptors.add(ErrorInterceptor()); dio.interceptors.add(PrettyDioLogger());这样做的原因是日志拦截器要记录的是“最终实际发出的请求”和“最终拿到的响应”。如果其他拦截器在日志拦截器之后执行比如 AuthInterceptor 在最后补充了 token日志里就缺少最终的请求头看起来会不明所以。业务错误处理的逻辑我建议放在日志拦截器之前这样日志里错误信息是原始响应。如果错误处理器把 DioException 转成了业务错误码日志打印的就是转换后的错误对象原始的 500 状态码和响应体反而被吞掉了排查问题时信息会不完整。另外在 OpenHarmony 开发板上性能比较有限如果响应体特别大打印 JSON 本身就会消耗一定时间。建议在调试大响应接口时打开 compact 模式并且在 logPrint 里做一次长度判断超过阈值就只打印前 500 个字符加省略号。这种“限制日志长度”的思路能避免大量日志输出导致 UI 卡顿。5. 常见问题与避坑清单5.1 日志看不见、格式乱、卡顿怎么排查先列一个高频问题速查表都是我实际遇到过的现象可能原因解决方案完全没有日志输出拦截器没挂到实例上确认请求确实走的是挂了拦截器的 Dio 实例只有 debug 模式才有日志canLog 或 release 配置限制检查 canLog 返回值生产环境本来就不该打日志被截断JSON 看不全debugPrint 默认限制覆盖 logPrint自己分段打印或写入文件控制台显示乱码/换行混乱终端宽度不足或 maxWidth 不合适调整 maxWidth开启 compact日志很多但卡顿明显输出量太大阻塞 UI限制日志长度避免打印超大响应体500 错误状态码没有打印错误被其他拦截器提前处理调整拦截器顺序日志放在最后我自己踩过最隐蔽的一个坑是在 OpenHarmony 设备上运行应用时flutter run 的输出正常但断开 USB 再跑的时候hilog 里怎么都看不到日志。后来发现这跟 Flutter 的日志通道有关应用通过 hilog 输出的 tag 是 Flutter不一定等于应用的 bundle 名。查日志的时候要用hdc shell hilog | grep Flutter去过滤而不是直接 grep 包名。还有一个和网络请求本身无关但容易影响判断的坑如果开发板系统时间不对日志里的时间戳会是乱的排查超时问题时会得到错误结论。建议连接开发板后先确认时间用hdc shell date检查一下不准就先同步时间。5.2 Flutter 插件在 OpenHarmony 上的兼容性问题先说结论纯 Dart 实现的包比如 dio、pretty_dio_logger、path_provider 的 Dart 侧接口在 OpenHarmony 的 Flutter 环境里通常都能直接跑通。但凡是依赖了 Android 原生代码的插件比如某些地图 SDK、支付 SDK、push SDK如果 OpenHarmony 侧没有对应的原生实现就会报平台通道异常。我们项目里就遇到过类似问题Flutter 端接了某个地图组件在 Android 上运行正常但跑在 OpenHarmony 设备上时日志里出现一大段类似 MethodChannel 找不到实现或者调用失败的错误。这时候用 Pretty Dio Logger 是看不到的因为它只关注 HTTP 层。我的排查思路是先确认该插件是否有 ohos 适配版如果没有就要找 OpenHarmony 原生 SDK 来写平台通道。这其实也解释了为什么社区里有那么多关于“flutter 如何接入高德”这类问题本质都是第三方 SDK 的 OpenHarmony 适配问题。网络日志打印在这种场景下只能看到应用层的 HTTP 请求SDK 内部的私有加密请求是看不到的所以不要指望 Pretty Dio Logger 能解决所有问题。另外如果遇到类似you are applying flutters main gradle plugin imperatively这种报错说明你在构建时把 Android 的 Gradle 配置经验直接生搬硬套到了 OpenHarmony 工程上。OpenHarmony 工程的构建体系是 hvigor和 Gradle 不是一回事。遇到构建报错先看构建系统是不是对的别拿着 Android 的经验去处理 ohos 目录。5.3 OpenHarmony 开发中的几个高频坑别重复踩除了日志插件本身我还想顺手分享几个 OpenHarmony Flutter 开发中容易被绊倒的地方。这些不一定都和网络请求相关但都会影响调试效率。第一个是 USB 和 hdc 连接问题。开发板有时候插上 USB 之后 hdc list targets 看不到设备大概率是 USB 调试权限没打开或者驱动没装好。可以试试hdc kill再hdc start。OpenHarmony 对 USB 设备的管理用到了 usbmanager 和 libusb这些是设备侧的原生能力跟 Flutter 应用层的调试工具链没关系但如果你在做 USB 相关的业务需要单独去了解。第二个是设备标识获取。很多 App 需要拿设备的唯一标识来做埋点OpenHarmony 上获取 devudid 和 serial 的接口和 Android 的 Build.SERIAL 不一样不要照搬 Android 的写法。这个在网上能搜到很多资料但核心原则是优先用官方文档提供的接口别依赖第三方封装。第三个是定制系统相关的问题。比如有人想修改 const.product.name 来定制系统版本号这属于系统编译层面的定制做完之后一定要重新构建系统镜像不是改一个普通配置文件就生效的。这类问题和应用层日志调试没关系但在团队协作时容易混淆我在集成日志插件时就收到过一条“为什么系统版本显示不对”的错误排查半天才发现是测试同事改错了系统镜像。第四个是 Flutter 开发中常见的 UI 问题比如底部弹窗里有 TextField弹窗被键盘顶起或者输入框被遮挡。这类问题在 OpenHarmony 上一样会遇到和在 Android 上处理方式类似。但在开发板上测试时由于屏幕分辨率可能比较特殊建议多测几种键盘输入场景否则接口调试通过UI 交互又出问题又要回头排查。写在最后我在实际调试中最大的体会是工具链越简单越好。Pretty Dio Logger 本身不复杂但它能把网络请求这一层的信息透明化让我不用猜测请求到底长什么样而是直接看到请求、响应的全貌。踩过几次坑之后我现在每接一个新平台第一件事就是把日志体系搭好网络层、页面生命周期、平台通道都做到有日志可查后面再排查问题就会轻松很多。最后再分享一个小技巧把创建 Dio 的逻辑单独抽成一个 factory统一在里面挂拦截器全局只保留这一个入口。这样不管后续换日志插件、加鉴权逻辑还是改动超时配置都只需要改一个文件。配合环境变量和脱敏规则这个网络层可以稳定用很久。