1
头图

HarmonyOS Next 日志知识一文搞定

日志是日常开发中必不可少的调试工具,好的日志工具和日志调用可以帮助我们快速定位开发中或者线上问题,极大提高开发效率。HarmonyOS Next除了支持TS的console打印日志外,还提供了hilog工具,可以在ArkTS层和C++层实现日志输出,此外我们还可以使用封装持久化的日志工具帮助定位解决线上问题。

接下来分别对这些工具进行详细介绍。

1、console工具

Console模块提供了一个简单的调试控制台,类似于浏览器提供的JavaScript控制台机制。
console模块按日志等级提供了:

  • console.debug
  • console.info
  • console.warn
  • console.error
  • console.assert
  • console.log

这些函数是以格式化输出方式打印不同级别日志信息,console.info和console.log作用一致,console.info是console.log的别名。函数函数为:

参数名类型必填说明
messagestring表示要打印的文本信息。
argumentsany[]表示其余要打印的信息或message的替换值。

示例:

const number = 5;
console.info('count: %d', number);  // 格式化输出替换message中的文本。
// count: 5 
console.info('count:', number);  // 打印message以及其余信息
// count: 5 
console.info('count:'); // 仅打印message
// count: 

const str = "name should be string";
console.warn('warn: %d', str);  // 格式化输出替换message中的文本。
// warn: name should be string
console.warn('warn:', str);  // 打印message以及其余信息
// warn: name should be string
console.warn('warn:'); // 仅打印message
// warn: 

const str = "value is not defined";
console.error('error: %d', str);  // 格式化输出替换message中的文本。
// error: value is not defined
console.error('error:', str);  // 打印message以及其余信息
// error: value is not defined
console.error('error:'); // 仅打印message
// error: 

断言打印的函数参数稍有不同:

参数名类型必填说明
valueObject语句结果值。若value为假(false)或者省略,则输出以"Assertion failed"开头。如果 value 为真值(true),则无打印。
argumentsObjectvalue为假(false)的后续错误消息打印。省略则不打印。

示例:

console.assert(true, 'does nothing');  // 表达式结果值为true, 无打印。
console.assert(2 % 1 == 0, 'does nothing');  // 表达式结果值为true, 无打印。

console.assert(false, 'console %s work', 'didn\'t');
// Assertion failed: console didn't work

console.assert();
// Assertion failed

console还提供了count函数,维护一个内部计数器,调用时,打印此标签名以及对应的计数次数。
示例:

console.count()
// default: 1
console.count('default')
// default: 2
console.count('abc')
// abc: 1
console.count('xyz')
// xyz: 1
console.count('abc')
// abc: 2
console.count()
// default: 3

可以使用 console.countReset 清除指定标签名的计数。

console.dir用于打印对象内容:

class bar {
  baz: boolean = true;
}
let b: bar = {baz: true}
class foo{
  bar: bar = b;
}
let c: foo = {bar: b}
class  c1{
  foo: foo = c;
}
let a: c1 = {foo: c}
console.dir(a);
// Object: {"foo":{"bar":{"baz":true}}}

console.dir(); // 无打印

console.dirxml 通过内部调用console.log()实现。此方法不会产生任何 XML 格式。使用方法与console.log()一致。也可以看成是console.log的别名。

const number = 5;
console.dirxml('count: %d', number);
// count: 5 
console.dirxml('count:', number);
// count: 5 
console.dirxml('count:');
// count: 

console.group默认将后续行的缩进增加两个空格。如果提供需要打印的信息,则首先打印信息,没有额外的缩进。

console.log("outter");
// outter
console.group();
console.log("level 1");
//   level 1
console.group("in level1");
//   in level1
console.log("level 2");
//     level 2

console.groupCollapsed使用与功能同console.group()一致。console.groupEnd将后续行的缩进减少两个空格:

console.log("outter");
// outter
console.group();
console.log("level 1");
//   level 1
console.groupEnd();
console.log("outter");
// outter

console.table以表格形式打印数据:

console.table([1, 2, 3]);
// ┌─────────┬────────┐
// │ (index) │ Values │
// ├─────────┼────────┤
// │    0    │   1    │
// │    1    │   2    │
// │    2    │   3    │ 
// └─────────┴────────┘

console.table({ a: [1, 2, 3, 4, 5], b: 5, c: { e: 5 } });

// ┌─────────┬───┬───┬───┬───┬───┬───┬────────┐
// │ (index) │ 0 │ 1 │ 2 │ 3 │ 4 │ e │ Values │
// ├─────────┼───┼───┼───┼───┼───┼───┼────────┤
// │    a    │ 1 │ 2 │ 3 │ 4 │ 5 │   │        │
// │    b    │   │   │   │   │   │   │   5    │
// │    c    │   │   │   │   │   │ 5 │        │
// └─────────┴───┴───┴───┴───┴───┴───┴────────┘

console.time 启动可用于计算操作持续时间的计时器。可使用console.timeEnd()关闭计时器并打印经过的时间(单位:ms)。

console.time('abc');
console.timeEnd('abc');
// abc: 225.438ms

console.timeLog 对于先前通过调用 console.time() 启动的计时器,打印经过时间和其他data参数:

console.time('timer1');
console.timeLog('timer1', 17);
// timer1: 365.227ms 17
console.timeEnd('timer1');
// timer1: 513.22ms

console.trace 用于打印当前堆栈。

console.trace();
// Trace:
//     xxxxxxxxxx(当前堆栈信息)
console.trace("Show the trace");
// Trace: Show the trace
//     xxxxxxxxxx(当前堆栈信息)

console.traceHybridStack 在主线程/worker线程中可打印当前线程混合堆栈信息:

console.traceHybridStack();
// TraceHybridStack:
//     xxxxxxxxxx(当前线程混合堆栈信息)

2、hilog

hilog日志系统,使应用/服务可以按照指定级别、标识和格式字符串输出日志内容,帮助开发者了解应用/服务的运行状态,更好地调试程序。

2.1 ArkTS中hilog

首先需要导入模块:

import { hilog } from '@kit.PerformanceAnalysisKit';

hilog也是类似,提供了不同级别的日志打印函数:

  • hilog.debug:DEBUG级别的日志在正式发布版本中默认不被打印,只有在调试版本或打开调试开关的情况下才会打印
  • hilog.info
  • hilog.warn
  • hilog.error
  • hilog.fatal

参数说明:

参数名类型必填说明
domainnumber日志对应的领域标识,范围是0x0~0xFFFF。

建议开发者在应用内根据需要自定义划分。
tagstring指定日志标识,可以为任意字符串,建议用于标识调用所在的类或者业务行为。tag最多为31字节,超出后会截断,不建议使用中文字符,可能出现乱码或者对齐问题。
formatstring格式字符串,用于日志的格式化输出。格式字符串中可以设置多个参数,参数需要包含参数类型、隐私标识。

隐私标识分为{public}和{private},缺省为{private}。标识{public}的内容明文输出,标识{private}的内容以<private>过滤回显。
argsany[]与格式字符串format对应的可变长度参数列表。参数数目、参数类型必须与格式字符串中的标识一一对应。

说明一下隐私标识符作用:
输出一条FATAL信息,格式字符串为"%{public}s World %{private}d"。其中变参%{public}s为明文显示的字符串;%{private}d为隐私的整型数。
hilog.fatal(0x0001, "testTag", "%{public}s World %{private}d", "hello", 3);
字符串"hello"填入%{public}s,整型数3填入%{private}d,输出日志:
08-05 12:21:47.579 2695 2703 F A00001/testTag: hello World <private>如果发现输出日志是private,记得检查这里

hilog还提供了isLoggable函数,在打印日志前调用该接口,用于检查指定领域标识、日志标识和级别的日志是否可以打印。参数说明如下:

参数名类型必填说明
domainnumber日志对应的领域标识,范围是0x0~0xFFFF。

建议开发者在应用内根据需要自定义划分。
tagstring指定日志标识,可以为任意字符串,建议用于标识调用所在的类或者业务行为。tag最多为31字节,超出后会截断,不建议使用中文字符,可能出现乱码或者对齐问题。
levelLogLevel日志级别。

如果返回true,则该领域标识、日志标识和级别的日志可以打印,否则不能打印。

2.2 C++中hilog

在C++代码中开发者可以通过使用这些接口实现日志相关功能,输出日志时可以指定日志类型、所属业务领域、日志TAG标识、日志级别等。

在应用开发过程中,可在关键代码处输出日志信息。在运行应用后,通过查看日志信息来分析应用执行情况(如应用是否正常运行、代码运行时序、运行逻辑分支是否正常等)。

HiLog日志系统,提供给系统框架、服务、以及应用,用于打印日志,记录用户操作、系统运行状态等。

HiLog中定义了DEBUG、INFO、WARN、ERROR、FATAL五种日志级别,并提供了对应的方法输出不同级别的日志,接口如下表所示

方法/宏接口描述
bool OH_LOG_IsLoggable(unsigned int domain, const char *tag, LogLevel level)检查指定domain、tag和日志级别的日志是否可以打印。

如果指定日志可以打印则返回true;否则返回false。
int OH_LOG_Print(LogType type, LogLevel level, unsigned int domain, const char *tag, const char *fmt, ...)输出指定domain、tag和日志级别的日志,并按照printf格式类型和隐私指示确定需要输出的变参。

打印成功则返回日志总字节数;失败则返回-1。
#define OH_LOG_DEBUG(type, ...) ((void)OH_LOG_Print((type), LOG_DEBUG, LOG_DOMAIN, LOG_TAG, __VA_ARGS__))DEBUG级别写日志,宏封装接口。
#define OH_LOG_INFO(type, ...) ((void)OH_LOG_Print((type), LOG_INFO, LOG_DOMAIN, LOG_TAG, __VA_ARGS__))INFO级别写日志,宏封装接口。
#define OH_LOG_WARN(type, ...) ((void)OH_LOG_Print((type), LOG_WARN, LOG_DOMAIN, LOG_TAG, __VA_ARGS__))WARN级别写日志,宏封装接口。
#define OH_LOG_ERROR(type, ...) ((void)OH_LOG_Print((type), LOG_ERROR, LOG_DOMAIN, LOG_TAG, __VA_ARGS__))ERROR级别写日志,宏封装接口。
#define OH_LOG_FATAL(type, ...) ((void)OH_LOG_Print((type), LOG_FATAL, LOG_DOMAIN, LOG_TAG, __VA_ARGS__))FATAL级别写日志,宏封装接口。
void OH_LOG_SetCallback(LogCallback callback)注册函数,注册后可通过LogCallback回调获取本进程所有的hilog日志。

OH_LOG_IsLoggable()和OH_LOG_Print()使用的domain、tag和level应保持一致。

  • domain:用于指定输出日志所对应的业务领域,取值范围为0x0000~0xFFFF,开发者可以根据需要进行自定义。
  • tag:用于指定日志标识,可以为任意字符串,建议标识调用所在的类或者业务行为。tag最多为31字节,超出后会截断,不建议使用中文字符,可能出现乱码或者对齐问题。
  • level:用于指定日志级别。取值见LogLevel。
  • fmt:格式字符串,用于日志的格式化输出。日志打印的格式化参数需按照“%{private flag}specifier”的格式打印。
隐私标识符(private flag)说明
private表示日志打印结果不可见,输出结果为<private>。
public表示日志打印结果可见,明文显示参数。
缺省值默认为private,日志打印结果不可见。
格式说明符(specifier)说明示例
d/i支持打印number、bool和bigint类型。123
s支持打印string、undefined和null类型。"123"
  • 格式字符串中可以设置多个参数,例如格式字符串为“%s World”,“%s”为参数类型为string的变参标识,具体取值在args中定义。
  • args:可以为0个或多个参数,是格式字符串中参数类型对应的参数列表。参数的数量、类型必须与格式字符串中的标识一一对应。

日志打印最多打印4096字节,超出限制文本将被截断。

要在C++中使用hilog首先需要链接对应动态库,以CMakeLists.txt为例:

target_link_libraries(entry PUBLIC libhilog_ndk.z.so)

接着导入头文件:#include "hilog/log.h"

定义对应的宏:

#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x3200  // 全局domain宏,标识业务领域
#define LOG_TAG "MY_TAG"   // 全局tag宏,标识模块日志tag

接着就可以使用日志输出了:OH_LOG_ERROR(LOG_APP, "Failed to visit %{private}s, reason:%{public}d.", url, errno);

3、最佳实践

一般我们会在应用层封装对应的日志工具,可以根据debug或release环境控制打印的日志级别等。在Android和iOS中已经应用很成熟了。

但是不管是hilog还是console输出的日志都只能在控制台看,线上问题如何通过日志定位呢?其实在Android和iOS侧已经有对应的解决方案,基于mmap工具将日志持久化到手机存储,等到需要使用的时候进行回捞。之前一直使用的是开源库mars中提供的持久化日志工具,目前也移植到了HarmonyOS 平台,今天先不再展开。

4、总结

本文介绍了HarmonyOS 系统提供的console和hilog系统日志框架,详细介绍了其中API,最后介绍了在实际开发中的日志使用方式和持久化日志的思路。


轻口味
28.1k 声望4.5k 粉丝

移动端十年老人,主要做IM、音视频、AI方向,目前在做鸿蒙化适配,欢迎这些方向的同学交流:wodekouwei