日志规范
一、启用
脚手架使用日志功能,引入
lombok
依赖:xml<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
在使用的类上加上注解:
@Slf4j
,会根据实际使用的日志框架生成log日志对象打印日志的地方
javalog.info("信息打印{}"+l); log.error("信息出错:{}", e); 这里为什么不是e.getMessage,后面会有详细解释。
二、级别设置
- 框架用环境变量形式配置日志级别,开发环境默认是
debug
级别,生产环境默认是error
级别,配置是:
jsx
logging:
level:
com.hisense.pangea: ${LOG_PANGEA_LEVEL:debug}
org.springframework: ${LOG_FRAME_LEVEL:debug}
- 日志级别可以在环境变量里用如上变量,直接修改打印级别,生产环境请不要轻易改变日志级别。
三、日志打印规范
1.日志中记录什么?
日志应该不多不少,能够从日志中得到所有需要的信息。在实践中经常发生日志不够的情况,例如:
- 请求出错时不能通过日志直接来定位问题,而需要开发人员再临时增加日志并要求请求的发送者重新发送同样的请求才能定位问题;
- 无法确定服务中的后台任务是否按照期望执行;
- 无法确定服务的内存数据结构的状态;
- 无法确定服务的异常处理逻辑(如重试)是否正确执行;
- 无法确定服务启动时配置是否正确加载;
2.输出主要内容
日志输出主要在文件中,应包括以下内容:
* 日志时间
* 日志级别主要使用
* 调用链标识(可选)
* 线程名称
* 日志记录器名称
* 日志内容
* 异常堆栈(不一定有)
3.关于级别
级别 | 说明 |
---|---|
DEBUG | DEUBG 级别的主要输出调试性质的内容,该级别日志主要用于在开发、测试阶段输出。该级别的日志应尽可能地详尽,开发人员可以将各类详细信息记录到DEBUG里,起到调试的作用,包括参数信息,调试细节信息,返回值信息等等,便于在开发、测试阶段出现问题或者异常时,对其进行分析。 |
INFO | INFO日志主要记录系统关键信息,旨在保留系统正常工作期间关键运行指标,开发人员可以将初始化系统配置、业务状态变化信息,或者用户业务流程中的核心处理记录到INFO日志中,方便日常运维工作以及错误回溯时上下文场景复现。建议在项目完成后,在测试环境将日志级别调成 INFO,然后通过 INFO 级别的信息看看是否能了解这个应用的运用情况,如果出现问题后是否这些日志能否提供有用的排查问题的信息。 |
WARN | WARN 级别的主要输出警告性质的内容,这些内容是可以预知且是有规划的,比如,某个方法入参为空或者该参数的值不满足运行该方法的条件时。在 WARN 级别的时应输出较为详尽的信息,以便于事后对日志进行分析。 |
ERROR | ERROR 级别主要针对于一些不可预知的信息,诸如:错误、异常等,比如,在 catch 块中抓获的网络通信、数据库连接等异常,若异常对系统的整个流程影响不大,可以使用 WARN 级别日志输出。在输出 ERROR 级别的日志时,尽量多地输出方法入参数、方法执行过程中产生的对象等数据,在带有错误、异常对象的数据时,需要将该对象一并输出。 |
3.1 INFO
和DEBUG
的选择
DEBUG级别比INFO低,包含调试时更详细的了解系统运行状态的东西,比如变量的值等等,都可以输出到DEBUG日志里。
INFO是在线日志默认的输出级别,反馈系统的当前状态给最终用户看的。输出的信息,应该对最终用户具有实际意义的。从功能角度上说,Info输出的信息可以看作是软件产品的一部分,所以需要谨慎对待,不可随便输出。如果这条日志会被频繁打印或者大部分时间对于纠错起不到作用,就应当考虑下调为DEBUG级别。
- 由于info及debug日志打印量远大于ERROR,出于前文日志性能的考虑,如果代码为核心代码,执行频率非常高,务必推敲日志设计是否合理,是否需要下调为DEBUG级别日志。
- 注意日志的可读性,不妨在写完代码review这条日志是否通顺,能否提供真正有意义的信息。
- 日志输出是多线程公用的,如果有另外一个线程正在输出日志,上面的记录就会被打断,最终显示输出和预想的就会不一致。
3.2 WARN
,ERROR
的选择
WARN代表可恢复的异常,此次失败不影响下次业务的执行,几次失败可容忍,频率高的时候需要提醒,记录ERROR的结果是线上时不时出现容忍范围内的报警,这时报警是无意义的。但反之不记录ERROR日志,真正出现问题则不会有实时报警,错过最佳处理时机。
ERROR级别的日志意味着系统中发生了非常严重的问题,必须有人马上处理,比如数据库不可用,系统的关键业务流程走不下去等等。
4. 日志内容
- 禁用
System.out.println
和System.err.println
- 变参替换日志拼接
- 输出日志的对象,应在其类中实现快速的
toString
方法,以便于在日志输出时仅输出这个对象类名和hashCode
- 预防空指针:不要在日志中调用对象的方法获取值,除非确保该对象肯定不为
null
,否则很有可能会因为日志的问题而导致应用产生空指针异常。
例如:
jsx
// 不建议
log.debug( "数据(id={}), 对象值: {}" , id , object.getObjName() );
// 建议
log.debug( "数据(id={}), 对象值: {}" , id , object);
4.1 日志打印到指定文件
在logback-spring.xml
文件打开文件打印级别,指向的name
是根据当前的启动分支区分启用的打印策略,以下有具体的配置说明:
xml
<!--指向日志文件的保存地址 -->
<property name="log.path" value="logs/"/>
<!--区分启动环境,打印策略 -->
<springProfile name="dev">
<root level="info">
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="DEBUG_FILE"/>
</root>
</springProfile>
<!--输出到控制台 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- 此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--输出到文档 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${log.path}/web_debug.log</file>
<!--日志文档输出格式 -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -%msg%n</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志归档 -->
<fileNamePattern>${log.path}/web-debug-%d{yyyy-MM-dd}.%i.log
</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文档保留天数 -->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文档只记录debug级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
4.2 容易遗漏的日志:
- 系统的配置参数:系统在启动过程中通常会首先读启动参数,可以在系统启动后将这些参数输出到日志中,方便确认系统是按照期望的参数启动的;
- 后台定期执行的任务:如定期更新缓存的任务,可以记录任务开始时间,任务结束时间,更新了多少条缓存配置等等,这样可以掌握定期执行的任务的状态;
- 异常处理逻辑:如对于分布式存储系统来说,当系统在一个存储节点上读数据失败时,需要去另一个数据节点上进行重试,可以将读数据失败这件事情记录下来,之后可以通过对日志的分析确认是否某些节点的磁盘可能存在故障。
4.3 日志的可读性
日志时给人读的,不仅仅是让自己明白,也要让没有接触过我们源代码的其他程序员也能够一目了然。在日志中打印特殊的标识符号,例如“++++++++++”, “===========”,“—————”,这些符号令人眼花缭乱。这是一种不好的编程习惯。 另外,把日志分类输出到不同的文件也有利于我们排除干扰,迅速找到我们需要的信息。而且,最好在打印日志时输出英文,防止中文不支持而打印出乱码的情况。
4.4 日志的时效性
有的时候我们并不能及时的发现问题。需要追溯之前的日志。所以我们是需要保留一段时间以内的日志便于追溯。
5. 异常堆栈
异常堆栈一般会出现在 ERROR
或者 WARN
级别的日志中,异常堆栈含有方法调用链的系统,以及异常产生的根源。异常堆栈的日志属于上一行日志的,在日志收集时需要将其划至上一行中。
6. 注意事项
- 不要记录日志后又抛出异常。如捕获异常后又抛出了自定义业务异常,此时无需记录错误日志,由最终捕获方进行异常处理。不能又抛出异常,又打印错误日志,不然会造成重复输出日志。
- 不要使用标准输出,包括
System.out.println()
和System.error.println()
语句。因为这个只会打印到控制台,而不会记录到日志文件中,不方便管理日志。此外,标准输出不会显示类名和行号信息,一旦代码中大量出现标准输出的代码,且日志中打印有标准输出的内容,很难定位日志内容和日志打印的位置,根本无法排查问题 - 不要出现
printStackTrace
,它其实也是利用System.err
输出到了Tomcat控制台 - 禁止在线上环境开启
debug
级别日志输出 - 不要在大循环中打印日志
- 避免重复打印日志,浪费磁盘空间
- 日志内容尽量避免打印一大段消息的内容:如果实在需要记录,则可以截取其中一些重要的信息来记入日志