日志系统统一:跨平台日志收集与输出(109)
在跨平台应用开发中,构建一套统一的日志系统是保障应用可观测性与线上问题快速定位的基石。ArkUI-X 提供了从 ArkTS 层到各原生平台层的完整日志拦截与输出方案。
一、 跨平台统一日志工具封装
在 ArkTS 业务层,建议封装一个统一的日志工具类。该类内部通过平台检测机制,自动适配不同平台的底层日志接口(如鸿蒙的hilog与其他平台的console),确保业务代码中日志调用的一致性。
核心代码示例(统一日志拦截与分发):
import { PlatformDetector } from './PlatformDetector'; // 假设的平台检测工具 import { hilog } from '@kit.PerformanceAnalysisKit'; class CrossPlatformLogger { private static readonly DOMAIN: number = 0x0000; static info(tag: string, message: string) { if (PlatformDetector.isHarmonyOS()) { hilog.info(CrossPlatformLogger.DOMAIN, tag, `%{public}s`, message); } else { console.info(`[INFO] [${tag}] ${message}`); } } static error(tag: string, message: string) { if (PlatformDetector.isHarmonyOS()) { hilog.error(CrossPlatformLogger.DOMAIN, tag, `%{public}s`, message); } else { console.error(`[ERROR] [${tag}] ${message}`); } } }二、 Android 侧 LogInterface 注入与拦截
在 Android 平台,ArkUI-X 框架支持通过注入ILogger接口来拦截框架层及 ArkTS 侧输出的日志。开发者可以在 Android 的Application初始化阶段完成注入,将跨平台日志统一接管至 Android 原生的日志系统中。
核心代码示例(Android 日志接口实现与注入):
// 1. 实现 ILogger 接口 public class AppLogInterface implements ILogger { @Override public boolean isDebuggable() { return true; } @Override public void i(String tag, String msg) { Log.i(tag, msg); } @Override public void e(String tag, String msg) { Log.e(tag, msg); } // ... 实现其他级别日志方法 } // 2. 在 MyApplication 中注入 public class MyApplication extends StageApplication { @Override public void onCreate() { super.onCreate(); StageApplicationDelegate appDelegate = new StageApplicationDelegate(); appDelegate.setLogInterface(new AppLogInterface()); // 注入日志接口 appDelegate.setLogLevel(ILogger.LOG_DEBUG); // 设置拦截等级 } }三、 命令行日志查看(ACE Tools)
在开发与调试阶段,开发者无需依赖各平台的复杂原生工具,可直接使用 ArkUI-X 提供的ace log命令行工具。该工具支持一键滚动展示当前连接设备(Android、iOS 或 HarmonyOS)上正在运行的跨平台应用进程日志,极大提升了多端调试的效率。
四、 线上日志安全与持久化策略
在生产环境中,日志系统需兼顾性能与数据安全:
- 动态日志降级:默认仅拦截并输出
ERROR和FATAL级别日志。若需排查线上疑难杂症,可通过远程配置动态下发开关,临时将日志级别开放至INFO或DEBUG(需注意开放低级别日志可能带来的性能损耗与崩溃风险)。 - 敏感信息脱敏:在日志输出前,必须对用户隐私数据、Token、密码等进行正则匹配与掩码脱敏处理。
- 异步落盘:对于需要持久化的关键日志,应将其写入操作投递至后台 IO 线程,避免频繁的磁盘 I/O 阻塞主 UI 线程。
五、 跨平台结构化日志与 JSON 格式化输出
在大型分布式架构与统一日志聚合平台(如 ELK Stack)中,纯文本格式的日志难以被自动提取关键字段。ArkTS 侧的日志输出应全面拥抱结构化数据,将TraceId、UserId、StatusCode等关键上下文信息封装为 JSON 格式,以便后端系统高效检索。
核心代码示例(JSON 结构化日志封装):
class StructuredLogger { static info(tag: string, message: string, context?: Record<string, Object>) { const logPayload = { level: 'INFO', tag: tag, msg: message, timestamp: Date.now(), traceId: 'req-123456', // 从全局上下文中获取 TraceId extra: context || {} }; // 统一输出 JSON 字符串,便于后续日志解析 CrossPlatformLogger.info(tag, JSON.stringify(logPayload)); } } // 业务调用示例 StructuredLogger.info('NetworkModule', 'API Request Failed', { url: '/api/v1/user', statusCode: 500 });六、 原生侧 Bridge 日志拦截与统一透传
跨平台通信(Bridge)是问题频发的重灾区。为了确保 ArkTS 与原生侧的通信链路完全可追溯,建议在 Android/iOS 原生侧的 Bridge 插件中增加日志拦截器,将方法调用的入参、出参及耗时统一输出,并复用前文配置的ILogger接口进行透传。
核心代码示例(Android Bridge 日志拦截):
// 在原生 Bridge 插件中拦截方法调用 public class SafeBridgePlugin extends BridgePlugin { @Override public void onMethodCall(String method, JSONObject params, ResultCallback callback) { long startTime = System.currentTimeMillis(); // 统一透传至跨平台日志系统 AppLogInterface.logBridgeCall(method, params); super.onMethodCall(method, params, (result) -> { long duration = System.currentTimeMillis() - startTime; AppLogInterface.logBridgeResult(method, duration, result); callback.onResult(result); }); } }七、 本地日志异步落盘与文件轮转机制
当应用处于离线状态或发生致命崩溃(Crash)时,内存中的日志会随之丢失。必须建立可靠的本地日志持久化机制,将关键日志异步写入本地文件,并配合文件轮转策略防止磁盘空间被撑爆。
核心代码示例(基于 TaskPool 的异步落盘):
import { taskpool } from '@kit.ArkTS'; @Concurrent async function writeLogToFile(logLine: string, filePath: string): Promise<void> { // 在后台 IO 线程执行文件追加写入操作,绝不阻塞主 UI 线程 // 实际开发中可结合 fileIo 进行追加写入及文件大小检测(超 10MB 则重命名归档) console.info(`[IO Thread] Writing log to ${filePath}`); } // 在统一日志工具中触发落盘 CrossPlatformLogger.error('CrashModule', 'App Freeze Detected'); taskpool.execute(writeLogToFile, '[ERROR] App Freeze Detected', '/data/storage/app_crash.log');八、 线上日志动态采集与隐私脱敏合规
在生产环境中,日志系统不仅要“能写”,还要“安全地写”。对于线上疑难杂症,需支持通过远程配置(如动态下发开关)临时提升日志级别;同时,必须在日志落盘前对敏感信息进行合规处理。
核心代码示例(正则脱敏与动态开关):
class SecureLogger { // 敏感信息脱敏正则:隐藏手机号中间四位、Token 等 private static readonly SENSITIVE_REGEX: RegExp = /(\d{3})\d{4}(\d{4})/g; static safeLog(tag: string, message: string) { // 1. 检查远程下发的日志开关(默认生产环境关闭 DEBUG) if (!RemoteConfig.isLogEnabled()) return; // 2. 执行脱敏处理 const safeMessage = message.replace(SecureLogger.SENSITIVE_REGEX, '$1****$2'); // 3. 输出安全日志 CrossPlatformLogger.info(tag, safeMessage); } }