关于java:JAVAJava-日志打印规范

2次阅读

共计 16476 个字符,预计需要花费 42 分钟才能阅读完成。

为什么要标准日志

标准的日志是养成良好编程习惯的开始,也是关键时刻解决重大 BUG 的救命稻草。程序员开发的过程中能够打印 debug 日志,在简单业务中提供日志来排查问题,也能够在呈现生产问题的时候疾速问题,及时处理。无论如何理解和学习日志的标准是程序员必备的基本功。

日志作用

  • 线上问题定位。日志次要的作用,外围业务必须要具备残缺的日志以便于问题排查。
  • debug 日志调试。在开发和测试中能够通过 debug 日志调试,在要害局部增加 debug 日志有利于测试的准确性,开发也能够借助 Debug 日志进行自测。
  • 用户日志行为。次要是记录一些用户敏感操作,用于监控或者经营团队反馈客户问题应用,这些行为一半具备肯定的产品标准。
  • 扯皮。次要是第三方对接的时候,如果呈现相似对面忽然改返回参数赖账的状况下能够拿日志作为证据。或者经营误操作也能够用日志讲道理。

简略案例

间接看一些较为优良的开源框架,或者浏览一些 JDK 源码的异样解决是不错的形式,这里简略介绍一些例子:

依据具体的异样信息捕捉而日志打印:

try {File defaultAclFile = new File(fileName);  
    if (!defaultAclFile.exists()) {defaultAclFile.createNewFile();  
    }  
} catch (IOException e) { 
    // 进行具体的异样信息捕捉而日志打印
    log.warn("create default acl file has exception when update accessConfig.", e);  
}

应用 String.format 代替 + 拼接以及自定义异样的定义和抛出:

try {byte[] signature = sign(data.getBytes(charset), key.getBytes(charset), algorithm);  
    return new String(Base64.encodeBase64(signature), DEFAULT_CHARSET);  
} catch (Exception e) {
    // 应用 String.format 代替 + 拼接
    String message = String.format(CAL_SIGNATURE_FAILED_MSG, CAL_SIGNATURE_FAILED, e.getMessage());  
    log.error(message, e);  
    // 自定义异样
    throw new AclException("CAL_SIGNATURE_FAILED", CAL_SIGNATURE_FAILED, message, e);  
}

有时候异样解决有着意想不到行为,我认为这种解决看上去不错然而实际上很容易“埋雷”,如果作者没有在 Doc 中进行相干介绍,会是非常危险的行为。

try {return Long.parseLong(value);  
} catch (NumberFormatException e) {  
    // 我认为这种解决看上去不错然而实际上很容易“埋雷”return new BigDecimal(value).longValue();}

倒退历史

JAVA 的日志框架能够说是一坨麻线,并且这坨麻线归根结底都是都是一个人之手。绝对应的如果咱们应用 lombok 工具,会发现又有 @Log4j、@Slf4j、@Log4j2 这些注解,这些日志不仅自身名字难记,还长的差不多,用到的时候更是不晓得用那个,甚至也不晓得为什么用这个。这里先遍及一下倒退到目前整个框架的成绩图:

上面开始理解这个图是怎么进去的 =-=。

System.out以及System.err

算是最为古老的 JAVA 打印日志的形式,这个打印有点是简略疾速,毛病是不能进行任何格局配置,也没输入问题,效率极低。

Log4j

1996 年诞生自欧洲电子平安市场的我的项目决定本人开发出一套日志跟踪 API,后续这套 API 独立成为了 Log4j 这个我的项目。

Ceki Gülcü作为 Log4j 的次要开发,为日志开发的基础架构提供了很多参考。Log4j 至今仍然有大量的公司采纳,影响非常深远。

JUL(Java Util Logging)

Sun 公司对于 Log4j 非常眼红,在回绝 Log4j 融入 Java 中的申请之后,本人开发了日志框架,当然根本能够看作是照搬。然而因为 Log4j 进去很多年了,所以市场反馈不现实,大家还是用 Log4j。

JCL(Jakarta Commons Logging)

比拟关键点来了,Apach 和 Sun 开始抢夺日志规范,Apach 后续开发出 JCL,用意用 JCL 翘掉 Sun 开发的日志工具。Jakarta Commons Logging 在公布之后还提供了对立的日志接口,其实就是一个简略的日志打印形象层。

然而想法很美妙事实很骨感,JCL 和 JUL 的下场也是相似,也没有受到很好的市场反馈,前面被 Apach 雪藏。

Slf4j(Simple Logging Facade for Java)

在这样的背景之下,Ceki Gülcü因为集体起因从 Apach 到职,并且希图想要靠本人制订一套取代 JCL 的标准,最终命名为 Sfl4j,从后果来看的确要比 JCL 胜利许多,然而咱们要留神 Slf4jJCL一样只是一套规范,于是前面 Ceki Gülcü 又开发了桥接包兼容 JCL,Log4j,顺带把 JUC 也一起集成进去。本人一个人把所有事件干完了,真的是 god 一样的人物。

到目前为止咱们能够大抵整顿下面提到的关系,蓝色代表这具体的实体工具或者产品,而黄色局部代表标准。

咱们再退出 桥接包(红色标识桥接包),又呈现了上面的变动,Slf4j 标准 + 兼容包的成绩如下:

然而有时候具体应用框架的时候会呈现另一种状况,尽管产品都是应用 Slf4j 实现然而理论的日志打印细节都是各自托管的,因为 Slf4j 只是简略了对立入口,并没有做任何日志对立的操作。比方这里咱们应用 SpringBoot 的框架,假如它应用 log4j,而咱们本人的业务代码应用 slf4j,就会呈现两种不同日志格局的呈现。

所以 Slf4j 后续又做了兼容解决,目标就是不仅能够接入各种日志,还能够接管其余日志框架的所有工作,没错这里还是应用桥接。不过桥接的是相当于把所有兼容日志框架的日志打印依照 sfl4j 的日志格局进行兼容治理。

到这里 Slf4j 把所有的其余产品兼容了一个遍,也的确做到了一个框架代替其余日志框架的成果。

Logback

SLf4j 一串三把几个框架全串起来了,然而 Sl4j 毕竟还是标准,不是具体的产品,于是 Ceki Gülcü 又撸了一个 Logback 进去,这个 LogBack 能够看作是 Log4j 的降级,不仅性能能够比 Log4j 快十倍,同时也完满代替 Slf4j。为了简略记忆这里简略概括为 Logback = Slf4j + Log4j(改进)

Log4j2

Ceki Gülcü的老东家的 Apach 看到以前的员工蹬鼻子上脸天然不乐意,为了对标 Logback 开发出 Log4j 2,毫不意外的,它涵盖了基本上所有的 Logback 的个性,同时还搞出拆散的操作,没错就是在 Slf4j 的根底上做了一点改良而已,把兼容其余日志框架的接口作为一个包(Api),把日志产品自身又造成一个包(Core)。

最初画进去这个图有点可怕,搭建能够保留下来多看几遍(为了不便观看,这里把辅助线去掉了)

整个 Java 的日志框架倒退出 4 框架和三个接口,次要应用的框架是:Log4j2Slf4jLogbackJUL(Sun 开发),接口是一大堆桥接包,用于把其余框架的标准和接口全副桥接到本人的产品中,

留神:其实这里能够看到一个比拟有意思的细节,那就是 JCL(Jakarta Commons Logging) 在无声无息中退出历史舞台,因为在过来的确十分坑,Apach 开发的 Log4j2 也只接入了 JUL 而不是本人过来开发的 JCL。

那么在零碎在抉择日志计划的时候,如何抉择呢?

• 显然第一点是应用日志接口的 API 而不是间接应用日志产品的 API 这一条也是必须的,也是合乎依赖倒置准则的,咱们应 该依赖日志的形象,而不是日志的实现

• 日志产品的依赖只增加一个,如果依赖多个日志产品只会让本人的利用解决日志显得更简单,不可对立管制。

• 把日志产品的依赖设置为 Optionalruntime scope其中 Optional 是为了 依赖不会被传递,比方别的人援用你这个 jar,就会被迫应用不想用的日志依赖。

比方避免依赖传递能够应用上面的形式:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>${log4j.version}</version>
    <optional>true</optional>
</dependency>

而 scope 设置为 runtime,是能够保障日志的产品的依赖只有在运行时须要,编译的时候不须要,同样能够保障开发人员误用 API:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>${log4j.version}</version>
    <scope>runtime</scope>
</dependency>

以上就是 Java 的日志烂摊子历史,心愿读者能够从中排汇教训而不是教训。

什么时候记录日志?

记录日志次要查看上面几个点:

  • 初始化参数:初始化参数在各种框架外面能够看到一些内容,而在本人开发的业务中则应用打印业务参数浏览相干内容。
  • 编程语言提醒异样:次要和业务开发人员对于异样的解决以及异样的容忍度,这里依据不同的日志级别打印相干日志。
  • 业务流程预期不符:分支条件进入到预期之外的状况下须要打印相干日志。
  • 系统核心角色,组件要害动作:次要是外围业务的触发动作,然而须要防止十分高频率的打印,同时对于日志进行提炼。
  • 第三方服务近程调用:微服务中的第三方组件可信度都不高,记录日志是必要的,有时候面对突发的问题比拟能够排查出比拟要害的信息。

日志实际

咱们能够参考上面的格局进行日志输入格式化:

2019-12-01 00:00:00.000|pid|log-level|[svc-name,trace-id,span-id,user-id,biz-id]|thread-name|package-name.class-name : log message
  • 工夫
  • pid,pid
  • log-level,日志级别
  • svc-name,利用名称
  • trace-id,调用链标识
  • span-id,调用层级标识
  • user-id,用户标识
  • biz-id,业务标识
  • thread-name,线程名称
  • package-name.class-name,日志记录器名称
  • log message,日志音讯体

以 Logback 为例,咱们能够应用上面的格局进行标准化打印:

%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}|${PID:-}|%level|${LOG_LEVEL_PATTERN:-%5p}|%t|%-40.40logger{39}: %msg%n

logback.xml 的设置如下:

<configuration><property name="LOG_PATH"
          value="${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}"/>


<springProperty scope="context" name="APP_NAME"
                source="spring.application.name" defaultValue="spring-boot-fusion"/>
<!-- 全局对立 pattern -->
<property name="LOG_PATTERN"
          value="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}|${PID:-}|%level|${LOG_LEVEL_PATTERN:-%5p}|%t|%-40.40logger{39}: %msg%n"/>
<!-- 输入模式 file, 滚动记录文件,先将日志文件指定到文件,当合乎某个条件时,将日志记录到其余文件 -->
<appender name="fileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 被写入的文件名,能够是绝对目录,也能够是相对目录,如果下级目录不存在会主动创立,没有默认值。-->
    <file>${LOG_PATH}/${APP_NAME}-info.log</file>
    <!-- 滚动策略  基于工夫的分包策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <!-- yyyy-MM-dd 工夫策略则为一天一个文件 -->
        <FileNamePattern>${LOG_PATH}/${APP_NAME}-info.%d{yyyy-MM-dd-HH}.%i.log</FileNamePattern>
        <!-- 日志文件保留小时数 -->
        <MaxHistory>48</MaxHistory>
        <maxFileSize>1GB</maxFileSize>
        <totalSizeCap>20GB</totalSizeCap>
    </rollingPolicy>
    <!--  layout 负责把事件转换成字符串,格式化的日志信息的输入 -->
    <layout class="ch.qos.logback.classic.PatternLayout">
        <pattern>${LOG_PATTERN}</pattern>
    </layout>
    <!-- 级别过滤器,依据日志级别进行过滤。如果日志级别等于配置级别,过滤器会依据 onMath 和 onMismatch 接管或回绝日志 -->
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <!-- 设置过滤级别 -->
        <level>INFO</level>
        <!-- 用于配置合乎过滤条件的操作 -->
        <onMatch>ACCEPT</onMatch>
        <!-- 用于配置不合乎过滤条件的操作 -->
        <onMismatch>DENY</onMismatch>
    </filter>
</appender>
</configuration>

这里有几个重点进行介绍:

  1. 工夫点具体到毫秒,HH:mm:ss.SSS 准确到具体的工夫更有利于排查问题。
  2. 不同级别的日志进行分类存储,能够应用 additivity=”false” 防止日志的反复打印。
  3. 举荐应用@lombok.extern.slf4j.Slf4j +logback 的计划,logback 自身也是 slft4j 的作者,log4j 的作者做进去的货色,算是最终的集大成产品。
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
    <scope>provided</scope>
</dependency>
  1. 禁用 System.out.println 和 System.err.println,同时对于要间接打印的对象内容,为对象设置 @ToString 注解或者应用 IDE 主动生成皆可。
  2. 打印日志不倡议应用 + 而是更倡议应用 {} 占位符的形式进行打印:
// 正确示例,必须应用参数化信息的形式
log.debug("order is paying with userId:[{}] and orderId : [{}]",userId, orderId);
// 谬误示例,不要进行字符串拼接, 那样会产生很多 String 对象,占用空间,影响性能。及日志级别高于此级别也会进行字符串拼接逻辑。log.debug("order is paying with userId:" + userId + "and orderId:" + orderId);

外置解决方案

外置解决方案比拟常见的就是本人搭一套 ELK 日志采集零碎采集各个系统模块的日志,Kibana 提供了界面剖析工具剖析相干的内容,不便又好用。

ELK

ELK 日志收集服务是一种比拟常见的日志收集过滤框架,ELK 是 Elasticsearch、Logstash、Kibana 三大开源框架首字母大写简称。

  • Elasticsearch 是一个基于 Lucene、分布式、通过 Restful 形式进行交互的近实时搜寻平台框架,
  • Logstash 是 ELK 的地方数据流引擎,用于从不同指标(文件 / 数据存储 /MQ)收集的不同格局数据,通过过滤后反对输入到不同目的地(文件 / MQ / Redis / Elasticsearch / Kafka 等)。
  • Kibana 能够将 Elasticsearch 的数据通过敌对的页面展现进去,提供实时剖析的性能

收集的日志内容参考案例如下:

2019-11-26 15:01:03.332|1543|INFO|[example-server-book-service,28f019d57b8336ab,630697c7f34ca4fa,105,45982043|XNIO-1 task-42]|c.p.f.w.logging.AccessLoggingFilter     :
> url: http://liweichao.com:10011/actuator/health
> http-method: GET
> request-header: [Accept:"text/plain, text/*, */*", Connection:"close", User-Agent:"Consul Health Check", Host:"liweichao.com:10011", Accept-Encoding:"gzip"]
> request-time: 2019-11-26 15:01:03.309
> querystring: -
> payload: -
> extra-param: -


< response-time: 2019-11-26 15:01:03.332
< take-time: 23
< http-status: 200
< response-header: [content-type:"application/vnd.spring-boot.actuator.v2+json;charset=UTF-8", content-size:"15"]
< response-data: {"status":"UP"}

SLS 阿里云日志服务

阿里云日志服务(简称 SLS)是针对日志类数据的一站式服务。无需开发就能够进行日志数据采集、生产以及查问剖析性能。

次要分为上面的性能:

Project:项目管理的根底单元,日志服务倡议一个环境一个 Project,日志须要依据环境服务调用产生。

logstore:不同的日志格局能够设置不同的正告格局。日志库(Logstore)是日志服务中日志数据的采集、存储和查问单元。每个日志库隶属于一个我的项目,且每个我的项目能够创立多个日志库。

分区:Logstore 读写日志必然保留在某一个分区(Shard)上。每个日志库(Logstore)分若干个分区。

更多内容能够搜寻 SLS 阿里云日志服务理解,这里就不过多开展介绍了。

应用倡议

如果容许,能够应用 lombok 退出注解的形式应用日志变量实例。

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> 
<dependency> 
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId> 
    <version>1.18.10</version> 
    <scope>provided</scope> 
</dependency>

应用的中央如下:

import lombok.extern.slf4j.Slf4j; 
@Slf4j public class LogTest 
{public static void main(String[] args) {log.info("this is log test"); 
        } 
}

日志级别抉择

日志次要的级别如下,日志等级从小到大别离如下:

  • DEBUG:DEBUG 日志次要是开发是阶段应用,应用场景通常是开发和测试阶段对于一些要害操作是否执行的输入,开发人员能够把各种内容具体记录到 Debug 信息,尽可能的在开阶段发现和排查问题。
  • INFO:INFO 日志蕴含了要害的日志信息,次要作用是保留工作期间的信息,开发人员能够保留要害日志便于运维提取要害逻辑的执行日志信息,因为 INFO 日志会在线上日志控制台实时打印,所以须要保留最为要害的信息,倡议在实现之后本地调整为 INFO 级别测试。
  • WARN:正告信息和 ERROR 并不是很好辨别,然而实际上只有把 WARN 和 ERROR 的日志级别思考为有影响然而对于业务流程影响不是特地大的行为进行正告辨别即可,比方参数存在异样的状况,须要进行后续日志剖析。
  • ERROR:遇到重大影响业务执行的场景就须要打印 Error 日志,如果影响不是特地大,只是须要关注问题的状况则打印 WARN 级别日志。

DEBUG / INFO 的抉择

DEBUG 级别自身比 INFO 级别低,并且线上通常开启 INFO 级别日志,DEBUG 日志用来本地和测试最为适合,而 INFO 则是给运维或者反馈给经营的无力证据,INFO 级别日志不能输入无意义或者无价值的信息,肯定是要害信息才会输入 INFO 日志。

  • 如果代码为外围代码,执行频率十分高,务必斟酌日志设计是否正当。
  • 日志的可读性,本人 review 代码。
  • 留神日志公有化在多线程环境下的打印会相互打断。

WARN / ERROR 的抉择

和上文的形容相似,当遇到用户的敏感操作或者出现意外后果然而不产生事变的状况能够应用 WARN 进行正告,如果存疑能够后续查看 WARN 日志排查。而 ERROR 是须要技术上线排查问题的比较严重的状况应用,所以开发过程须要审慎思考 ERROR 的打印地位。

ERROR 的外围要点是上面几个:

  • 产生了什么问题,哪些性能受到影响
  • 获取帮忙信息:间接帮忙信息或帮忙信息的存储地位
  • 通过报警晓得解决方案或者找何人解决

标准倡议

1. 失当的日志级别

  • error:比较严重的问题,影响失常业务运行
  • warn:对业务影响不大,然而须要 开发留神
  • info:用于日常排查问题的要害信息,接口入参和出参等等
  • trace:详细信息,日志文件级别
  • debug:仅仅用于开发或者测试查看重要的外部逻辑细节,然而和线上的业务关系不是特地亲密

2. 日志打印出参入参

但凡和接口无关的日志,以及 要害办法 的入参和返回值都倡议加上日志。

3. 适合的格局格局

参考模板:

"%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n"

4. 多分支条件倡议分支首行打印

if…else… 或者 switch等如果分支条件比拟多的状况下倡议在进入分支之前打印一下,当然本人调试的时候也能够用这个法子判断走的是哪个分支:

String type = ???;
if(log.isDebugEnbale()){log.debug("以后分支类型为:{}", type);
}
if(type == "xxx"){}else if(type == "aaa"){

}

// 或者
switch(type){
    case "xxx":
        // ....
        break;
    case "xxx":
        // ....
        break;
}

5. 日志级别判断

这一条针对 debug 和 trace 这种低级别日志,同时为了缩小线上调用日志打印没有日志节约的状况:

User user = new User(666L, "xxxx", "xxxx");
if (log.isDebugEnabled()) {log.debug("userId is: {}", user.getId());
}

6. 应用日志框架 SLF4J 中的 API

人家 lombok 都给了一个 @Slf4j 的注解,所以用起来把。

实际上是因为 slf4j 也是 log4j 的作者写的并且做了门面兼容广受好评,而后 ….. 而后 apach 就学过来了,适配加门面是吧,我也会!最初后果是 Java 的日志零碎开源组件极度凌乱,并且烂的和一坨 shit 一样。从这一状况也能够看出定规范是十分重要的。

7. 占位符而不是 + 号

和 java 编译为 class 的时候会应用 StringBuffer 做字符串拼接操作。发现不论是大小我的项目,甚至到了框架也时常看见 + 号拼接的状况,尽管高版本的 JDK 这种编译优化下的影响实际上曾经很小了,然而集体还是不太喜爱这种 + 号拼接的写法,不够优雅。

正确用法

logger.info("Processing trade with id: {} and symbol : {}", id, symbol);

应用 + 操作符进行字符串的拼接,有肯定的 性能损耗

logger.info("Processing trade with id:" + id + "and symbol:" + symbol);

8. 倡议应用异步的形式来输入日志

  • 日志最终会输入到文件或者其它输入流中的,如果是 IO 性能会有要求的倡议应用异步,能够显著晋升 IO 性能。
  • 应用异步的形式来输入日志。以 logback 为例,要配置异步,应用 AsyncAppender
<appender name="FILE_ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="ASYNC"/>
</appender>

9. 不要应用 e.printStackTrace()

不要应用的理由:

  • e.printStackTrace()打印出的堆栈日志跟业务代码日志是交织混合在一起的,通常排查异样日志不太不便。
  • e.printStackTrace()语句产生的字符串记录的是堆栈信息,如果信息太长太多,字符串常量池所在的内存块没有空间了, 即内存满了,那么,用户的申请就卡住啦~

应该应用如下的正确用法:

try{// 业务代码解决}catch(Exception e){log.error("你的程序有异样啦",e);
}

10. 异样日志不要只打一半

比方上面的日志就没有任何价值:

try {// 业务代码解决} catch (Exception e) {
    // 谬误
    LOG.error('你的程序有异样啦');
}

其实解决形式很简略:

try{// 业务代码解决}catch(Exception e){log.error("你的程序有异样啦",e);
}

另外须要留神,e.getMessage()不会记录具体的堆栈异样信息,只会记录谬误根本形容信息,不利于排查问题。此外,如果应用 Hutool 工具,外面有一个异样信息提取的工具类比拟不便。

11. 不要囫囵吞枣

这一条注意事项很简略,然而很要害,尽量避免应用 Exception 全盘接管,而是须要思考针对具体的异样给出更多有用的日志信息,这样能够缩小线上问题的排查工夫。

12. 禁止在线上环境开启 debug

不仅仅是零碎会有很多 debug 日志呈现,还存在框架的 debug 日志输入问题,线上开启 debug 很容易打满磁盘,并且容易造成 CPU 的磁盘 IO 期待,长此以往会间接影响零碎服务。

13. 不要嵌套异样

嵌套异样是最容易吞噬异样的场景,很多时候办法代码块层层嵌套会遗记外面捕捉异样,外层又捕捉异样然而理论基本拿不到异样,如果异样捕捉和解决凌乱,那么自身就会大大增加问题排查难度。

本条的倡议是在编写设计办法或者类之前,须要提前思考异样如何解决,实现整个调用之后须要及时的回顾代码。

上面是对应反例:

try{
  // 业务代码解决
  try{// 业务代码解决}catch(Exception e){log.error("你的程序有异样啦",e);
    }
}catch(Exception e){log.error("你的程序有异样啦",e);
}

14. 不要记录异样又抛出

记录之后抛出异样是十分危险的操作,因为外层可能会因为内层捕捉异样之后不会再次解决,如果是自定义异样更是难以排查问题,此外这样做法会导致 堆栈二次打印,十分节约零碎性能,

反例如下:


try{// 业务代码解决}catch(Exception e){log.error("IO exception", e);
        throw new MyException(e);
    }

15. 日志文件拆散

能够把不同类型的日志拆散进来,比方access.log,或者 error 级别error.log,都能够独自打印到一个文件外面。依据业务模块拆分也是一种方法,这样各自负责的模块能清晰看到日志。

16. 防止反复打印日志

如果日志能够用一行示意,那就尽量用一行表白含意。

log.info("该用户是会员,Id:{}",user,getUserId());
  // 冗余,能够跟后面的日志合并一起
  log.info("开始解决会员逻辑,id:{}",user,getUserId());

17. 外围功能模块日志

如果是外围功能模块的日志,其实多打印一些内容是能够承受的,然而须要留神打印的日志必须要第一工夫能够定位到问题所在。

标准倡议参考

上面是标准的日志打印:

2018-05-22 15:35:53.850 TRACE TDWZLog [0x00001b10] <36> <TDWZProtocol::Init>,TDWZProtocol::Init
2018-05-22 15:35:53.850 TRACE TDWZLog [0x00001b10] <89> <TDWZProtocol::Init>,End in processing TDWZProtocol::Init
2018-05-22 15:35:53.853 TRACE TDWZLog [0x00001b10] <142>    <TDWZProtocol::Connect>,Connect Execute finish
2018-05-22 15:35:53.853 TRACE TDWZLog [0x00002f10] <149>    <GetAlarmEventPro>,Enter GetAlarmEventPro func
2018-05-22 15:39:36.382 WARN TrackLog [0x000029fc] - [internal WARN htrace_server_convert_msgstring_to_contextintls(493) ] detect input id error, trace_id span_id,this chain may not be tracked.
2018-05-22 15:39:36.383 WARN TrackLog [0x000029fc] - [internal WARN htrace_server_receive(195) ] can not detect trace_id in context, this chain may not be tracked.
2018-05-22 15:39:36.383 TRACE TDWZLog [0x000029fc] <231>    <TDWZProtocol::DisConnect>,TDWZProtocol::DisConnect
2018-05-22 15:39:37.502 TRACE TDWZLog [0x00002f10] <225>    <GetAlarmEventPro>,End Get AlarmEventPro Func
2018-05-22 15:39:37.503 TRACE TDWZLog [0x000029fc] <241>    <TDWZProtocol::DisConnect>,close socket
2018-05-22 15:39:37.503 TRACE TDWZLog [0x000029fc] <242>    <TDWZProtocol::DisConnect>,Execute DisConnect function succeed.

TRACE 日志记录示例

DRV_LOG_TRACE("Connect Execute start");
DRV_LOG_TRACE("Connect Execute finish");
DRV_LOG_TRACE("DisConnect func");
DRV_LOG_TRACE("Execute DisConnect function succeed.");
DRV_LOG_TRACE("Enter UploadEvent Func");
DRV_LOG_TRACE("extInfo = %s", Extension);
DRV_LOG_TRACE("Send a Msg");
DRV_LOG_TRACE("- Connect Execute start");
DRV_LOG_TRACE("- Connect Execute finish");
DRV_LOG_TRACE("- Enter GetAlarmEventPro func");
DRV_LOG_TRACE("- Receive an info");
DRV_LOG_TRACE("- End Get AlarmEventPro Func");
DRV_LOG_TRACE("- DisConnect func");
DRV_LOG_TRACE("- Execute DisConnect function succeed.");
DRV_LOG_TRACE("- Enter UploadEvent Func");
DRV_LOG_TRACE("- Leave UploadEvent Func");
DRV_LOG_TRACE("- ============ 电网报警触发");
DRV_LOG_TRACE("- ============ 开始发送电流电压值");
DRV_LOG_TRACE("- ============ 距离超过分钟再次发送电流电压值");

INFO 日志记录

DRV_LOG_INFO("- UpdataEvent  nchal= %d,EventID = %d.",iChannelNo,nEventType);
DRV_LOG_INFO("- do not support doControl");
DRV_LOG_INFO("- channelId = %s, nStatusType = %d", channelId.c_str(), nStatusType)

DEBUG 日志记录

DRV_LOG_DEBUG("- 输入报警状况: 电网编号:%d, 报警数量:%d, 报警内容:%s.",datas.data1.chn,datas.data1.alarm_num,datas.data1.alarms);
DRV_LOG_DEBUG("- 输入报警状况: 电网编号:%d, 报警数量:%d, 报警内容:%s.",datas.data2.chn,datas.data2.alarm_num,datas.data2.alarms);
DRV_LOG_DEBUG("- 输入报警状况: 电网编号:%d, 报警数量:%d, 报警内容:%s.",datas.data3.chn,datas.data3.alarm_num,datas.data3.alarms);
DRV_LOG_DEBUG("- 输入报警状况: 电网编号:%d, 报警数量:%d, 报警内容:%s.",datas.data4.chn,datas.data4.alarm_num,datas.data4.alarms);
DRV_LOG_DEBUG("- ============datas.data1.huab = %d",datas.data1.huab);
DRV_LOG_DEBUG("- ============datas.data1.hiab = %d",datas.data1.hiab);
DRV_LOG_DEBUG("- ============datas.data2.huab = %d",datas.data2.huab);
DRV_LOG_DEBUG("- ============datas.data2.hiab = %d",datas.data2.hiab);
DRV_LOG_DEBUG("- ============datas.data3.huab = %d",datas.data3.huab);
DRV_LOG_DEBUG("- ============datas.data3.hiab = %d",datas.data3.hiab);
DRV_LOG_DEBUG("- ============datas.data4.huab = %d",datas.data4.huab);
DRV_LOG_DEBUG("- ============datas.data4.hiab = %d",datas.data4.hiab);
DRV_LOG_DEBUG("- Alarm is : %s",szEvent.c_str());
DRV_LOG_DEBUG("- GetChannelExtInfo channelId=%s", channelId.c_str());
DRV_LOG_DEBUG("- nChan = %d, szInfo = %s", nChan, szInfo);

WARN 日志记录

DRV_LOG_WARN("[0x%08x] - invaild event msg,discard it", DRV_INVALID_ARG);
DRV_LOG_WARN("[0x%08x] - Can't find channel by channelId");
DRV_LOG_WARN("[0x%08x] - [DWSdk.errorcode=0x%08x]Connect device failed", DRV_CONNECT_FAILED, sdkErrCode);
DRV_LOG_WARN("[0x%08x] - [DWSdk.errorcode=0x%08x]dw_start_receive failed", DRV_ERROR, sdkErrCode);
DRV_LOG_WARN("[0x%08x] - [DWSdk.errorcode=0x%08x]Communicate failed, socket recv error", DRV_ERROR, DW_SOCKET_RECV_ERROR);
DRV_LOG_WARN("[0x%08x] - [DWSdk.errorcode=0x%08x>other error", DRV_ERROR, iGetResult);
DRV_LOG_WARN("[0x%08x] - [DWSdk.errorcode=0x%08x>other error", DRV_ERROR, iGetResult);
DRV_LOG_WARN("[0x%08x] - SetEventCallBack should be called first", DRV_ERROR);

ERROR 日志记录

DRV_LOG_ERROR("Init DwSDK filded;<errCode=%d>", initRet);  
DRV_LOG_ERROR("Connect device failed");
DRV_LOG_ERROR("Create thread failed");
DRV_LOG_ERROR("dw_start_receive failed");
DRV_LOG_ERROR("Communicate failed, socket recv error");
DRV_LOG_ERROR("other error<errCode=%d>", iGetResult);
DRV_LOG_ERROR("SetEventCallBack should be called first");
DRV_LOG_ERROR("[0x%08x] - [DWSdk.errorcode=0x%08x]Init DwSDK filded", DRV_INIT_FAILED, initRet);
DRV_LOG_ERROR("- [HPR.errorcode=0x%08x]Create thread failed", HPR_GetLastError());

[0x%08x] 的含意能够参考例子,printf("0x%08x", 0x1234);,格式化之后会变为 0x00001234

参考资料

这份 Java 日志格局标准,拿走不谢!-51CTO.COM

诧异!我定的日志标准被 CTO 在全公司推广了 – 腾讯云开发者社区 - 腾讯云 (tencent.com)

正文完
 0