# OpenHarmony日志打印规范

## 简介

公共流水日志指通过系统日志打印接口(HiLog)统一输出到日志服务的日志。日志用来记录用户操作、系统运行状态等，是一个系统的重要组成部分，然而由于日志不是核心功能，所以日志质量常常不被开发人员所重视。

尽管日志记录是必要的，但它对性能有明显的负面影响，如果不保持合理的简洁性，则会很快失去其有用性。每个领域在打印日志的时候都应该尽可能的合理，不要将别人的日志冲掉，就像你希望别人不冲掉你的日志一样。

## 日志级别

- **【规则】根据实际情况正确的使用日志打印等级**

**说明：** 日志级别要符合日志内容的实际级别，日志级别说明如下：

FATAL：重大致命异常，表明程序或功能即将崩溃，故障无法恢复。

ERROR：程序或功能发生了错误，该错误会影响功能的正常运行或用户的正常使用，可以恢复但恢复代价较高，如重置数据等。

WARN：发生了较为严重的非预期情况，但是对用户影响不大，程序可以自动恢复或通过简单的操作就可以恢复的问题。

INFO：用来记录业务关键流程节点，可以还原业务的主要运行过程；用来记录非正常情况信息，但这些情况都是可以预期的(如无网络信号、登录失败等)。这些日志都应该由该业务内处于支配地位的模块来记录，避免在多个被调用的模块或低级函数中重复记录。

DEBUG：比INFO级别更详细的流程记录，通过该级别的日志可以更详细地分析业务流程和定位分析问题。DEBUG级别的日志在正式发布版本中默认不会被打印，只有在调试版本或打开调试开关的情况下才会打印。

## 日志内容

- **【规则】日志打印内容使用英文描述，单词拼写无误，符合语法规范，准确表述日志的含义**

**说明：** 日志内容应该简明扼要地描述发生的事情，阅读日志的人可以通过日志直接知道表述的含义，尽量不要通过产生日志的代码才能知道；符合语法语义的日志打印也有利于后续日志分析工具对日志进行自动化解析。如：

“1234” 除了开发人员自己没人知道什么意义;

“Error happened” 哪里发生了什么错误，错误的原因值等都没有打印出来，不利于问题的定位分析。

- **【规则】日志中禁止打印隐私信息**

**说明：** 硬件序列号、个人账号、密码、身份等隐私信息禁止在日志中打印。隐私信息的范围遵从国家和地区的政策要求。

- **【规则】日志中禁止打印其它与业务无关的信息**

**说明：** 禁止打印如issue单号、需求单号、公司部门名称、开发人员自己的姓名、工号、名字缩写、当天的天气心情等任何与业务代码无关的信息。

- **【规则】日志中禁止打印重复信息**

**说明：** 禁止在不同的地方打印的内容完全一样的日志，在问题定位时无法准确找到代码位置。

- **【规则】禁止在日志打印语句中调用业务接口函数**

**说明：** 日志打印不应该对业务的正常流程产生任何影响，定位问题走读代码时通常会忽略日志代码对业务逻辑的影响。

- **【规则】禁止将开发调试过程中使用的日志打印提交到代码仓中**

**说明：** 为了定位问题可能会在代码中的每一步增加一行日志打印，或将各种变量内容打印出来(可能含用户隐私),
这些代码在提交到代码仓之前必须删除。

- **【规则】日志中禁止打印文件名、行号信息**

**说明：** 文件名、行号信息会暴露代码的内部实现。

- **【规则】日志中禁止重复打印TAG信息**

**说明：** 应该使用HiLog接口提供的tag参数。

- **【建议】日志打印长度不要过长，尽可能使日志记录显示在一行以内**

**说明：** 一行的长度通常是指在100个字符左右，尽量不要打印超过160个字符以上的日志。

## 打印时机

- **【规则】高频代码的正常流程中禁止打印日志**

**说明：** 如被高频调用的接口函数，大数据量处理的循环中，高频的软硬件中断处理中，协议数据流处理中，多媒体音视频流处理中，显示屏幕刷新处理中等等，这些地方基本都是只要系统不休眠就会一直运行的代码，这些代码正常处理禁止打印日志，但可在错误分支中打印日志。开发调试过程中在这里增加的日志在提交代码时必须清除掉或使用开关关闭。

- **【规则】可能重复发生的日志需要进行频率限制**

**说明：** 当事实证明某些日志记录可能会发生多次时，最好实施一种频率限制机制，防止出现具有相同（或非常相似）信息的大量重复日志副本。

- **【规则】在基本不可能发生的点必须要打印日志**

**说明：** 根据墨菲定律，只要有可能发生的是就一定会发生，一旦发生就是疑难杂症。

- **【建议】日志字符串应在日志打印时再生成**

**说明：** 日志字符串越迟生成越好，最好是在日志打印那一刻才生成，这样当日志被关闭时也就不会生成这个字符串，从而最大程度地减少对系统的开销。

## 日志形式

- **【规则】事件记录的日志使用who do what 主谓宾的形式打印**

- **【规则】状态变化的日志打印使用state\_name:s1-&gt;s2, reason:msg的形式打印**

- **【规则】参数值的日志打印使用name1=value1, name2=value2…的形式打印**

- **【规则】代码运行成功的日志使用xxx successful的形式打印**

- **【规则】代码运行失败的日志使用xxx failed, please xxx的形式打印，且需包含可能的解决方案**

  如："Connect to server failed, please check network configuration"。

## 常见模式日志打印要求

### 流程类日志

- **【建议】日志中应当记录业务的关键流程节点日志，包括业务的开始点，关键条件分支节点，错误处理点，业务结束点等**

### 数据库类日志

- **【建议】日志中应当记录对数据库的各种操作及其相关信息**

**说明：**  数据库的常规操作包括:
增、删、改、查；操作的发起者、操作类型、操作成功还是失败也要在日志中记录；但操作及返回结果中的内容通常不应记录以防止泄露用户隐私；查询结果的数量可以打印。

- **【建议】对于性能敏感型业务的数据库操作日志中需记录操作消耗的时间**

**说明：**  数据库操作涉及I/O读写, 对于性能敏感性业务，数据库操作通常会是性能的关键节点，记录时间可以作为性能调优的参考依据。

- **【建议】日志中应当记录数据库的JOB相关信息**

**说明：** JOB的名称，执行内容，启动时间，结束时间，执行结果应当在日志中记录。

### 文件类日志

- **【建议】日志中应当记录对文件的各种操作及其相关信息**

**说明：**  文件的常规操作包括: 创建、打开、读取、写入、关闭、删除、获取属性，操作的发起者、操作类型、操作结果需要记录日志；但文件内容不可记录以防止泄露用户隐私或暴露系统安全漏洞，系统文件名可以打印，用户文件名不可以打印； 文件句柄值可以打印。

- **【建议】批量文件操作只打印一条日志而不是打印多条日志**

**说明：** 如批量删除大量文件，不要每删除一个文件就打印一条日志，只要记录删除的文件数即可，如果文件所在目录是系统创建，还要打印目录名称。

### 关键对象/对象池日志

**说明：** 关键对象/对象池可能是一个类或者一个结构体，也可能是一个队列或堆栈的数据结构，也可能是一个简单的原始类型变量，它处于系统的关键地位，系统的调度控制、状态记录、信息流转等动作都依赖它。

- **【建议】日志中应当记录关键对象的操作过程，操作结果，状态变化**

**说明：** 对象的操作包括：创建、加载、卸载、释放等，对关键对象的操作需记录操作主体，操作类型，操作结果；状态类的需记录状态变化的前后值。

### 线程日志

- **【建议】日志中需记录线程的各种操作及相关信息**

**说明：** 线程的操作包括: 创建、启动、暂停、终止。日志中需记录操作线程的操作类型，操作结果、线程号，线程名称(重要线程一定要设置线程名)。

- **【建议】线程陷入死循环或死锁等错误时要记录日志**

**说明：** 对于有等锁处理、异步处理、循环处理等逻辑的线程在线程发生死锁或死循环时要有检测机制，并在错误发生时打印日志。

- **【建议】消息处理型的线程需要打印消息处理相关的日志**

**说明：** 包括消息名称、消息处理结果，消息处理时长；对于高频消息，只需要打印消息处理结果错误时的日志即可

### 并发控制日志

**说明：** 并发控制的对象可能是锁、临界区、信号量等。

- **【建议】日志中需记录并发控制对象的操作及其相关信息**

**说明：** 并发控制的操作包括：创建、占用、释放、等待等。日志中需记录操作的类型，操作对象的名称、操作的结果、操作的位置等信息。

### 共享内存日志

**说明：** 共享内存是软件系统中常用的进程间通信方法，它常用于在模块间共享数据或传递数据。共享内存所存放的数据可以是配置数据、数据库数据等。

- **【建议】日志中需记录对共享内存的操作及相关信息**

**说明：** 对共享内存的操作包括：创建、删除、数据设置、数据查询、销毁等。日志中需记录对共享内存操作的操作者，操作类型、操作结果。

### 接口交互日志

接口包括系统的内外部接口，内部接口指系统内部子系统、子模块之间的接口。形式可能包括模块间消息发送、IPC接口、RPC接口等。

- **【建议】日志需记录接口交互相关的信息**

**说明：** 接口交互相关的信息包括：接口的调用者、消息内容(不能涉及用户隐私)、处理结果、返回值(不能涉及用户隐私)。

### 状态机日志

- **【建议】日志需记录状态机的操作及状态转换信息**

**说明：** 状态机的操作包括：创建、开始、状态转换、结束、销毁等。状态机通常受外部条件激励(如消息、资源等)变换状态，状态机的状态变化前后的状态名称、导致变化的外部激励条件等信息也应该被记录。

### 其它操作系统资源

这里说的主要操作系统资源指Socket、定时器等不在前面小节已专门提及的资源(如文件、线程等)

- **【建议】日志中需要记录socket连接建立的过程和结果、连接维持的情况、连接断开的情况及原因**

- **【建议】日志中需要记录定时器启动、复位、销毁、超时处理过程**

- **【建议】使用其它类似操作系统中提供的资源也应参照遵循类似上述的日志记录原则**

## HiLog接口使用规范

- **【规则】每个业务须有独立的Domain ID**

**说明：** 系统各领域使用HiLog API打印日志须先到DFX申请标识业务的Domain ID。Domain ID用于度量和管控单个业务日志质量，支持开发人员调试使用Domain ID过滤出自身业务日志分析提高调试效率，不允许在不申请Domain ID情况下，直接使用其它领域的Domain ID。对测试代码，要求使用专门为测试配置的Domain ID 0xD000F00 打印日志。

系统Domain ID的范围为：**0xD000000\~0xD0FFFFF**

- **【建议】每个业务内部基于Domain ID分配机制在领域内按照层次、模块的粒度划分使用**

**说明：**  Domain ID为32位整数，以16机制形式表达，分配范围0xD0xxxyy。其中D0为domain域标识，xxx高12位为DFX分配值，yy低8位业务领域内部使用。要求Domain ID 内部分配能够定界到组织或模块,反应领域内具体组织或模块日志质量，同时DFX会基于 Domain ID对日志打印进行管控，防止因为单个模块日志打印多影响领域内其它模块的日志输出，如BT业务内按照层次模块进一步划分：

    APP       | BT-App1 BT-App2
    ---------------------------------------
    Framework | BT-Service1 BT-Service2
    ---------------------------------------
    HAL       | BT-HAL
    ---------------------------------------
    Kernel    | BT-Driver1  BT-Driver2

BT内的Domain ID可以进一步划分:
| Domain名称  | Domain ID |
| ----------- | --------- |
| BT          | 0xD000100 |
| BT-App1     | 0xD000101 |
| BT-App2     | 0xD000102 |
| BT-Service1 | 0xD000103 |
| BT-Service2 | 0xD000104 |
| BT-HAL      | 0xD000105 |
| BT-Driver1  | 0xD000106 |
| BT-Driver2  | 0xD000107 |

- **【规则】日志服务会对每个业务的日志量进行流量管控，修改默认的流量阈值需要经过评审**

**说明：** 日志服务中会对每个领域的日志流量进行控制，默认每个Domain ID的日志流量阈值是**10240字节/秒**, 如果需要修改默认阈值需经过DFX领域审查。

- **【规则】正确填写日志格式化隐私参数标识{public},{private}**

**说明：** 隐私参数标识{public}，{private}用来标识每个参数日志内容是否含隐私敏感信息。HiLog API会自动对标识{public}参数内容以明文输出，对标识{private}参数内容以&lt;private&gt;过滤回显，禁止不分析日志打印内容随意设置隐私参数标识。如：

源码:

```txt
    HiLog.info(LABEL, "Device Name:%{public}s, IP:%{private}s.", DeviceName, ip);
```

日志输出:

```txt
    11-11 09:19:00.932   1513-1513     C00500/testTag  com.example.hilogDemo  I     Device Name:MyPad001, IP:<private>.
```