目录
4.不能直接使用日志系统(Log4j、Logback)中的 API,而是使用日志框架SLF4J中的API。
7.不要使用e.printStackTrace(),异常日志不要只打一半,要输出全部错误信息
1.日志级别
常见的日志级别有5种,分别是error、warn、info、debug、trace。日常开发中,我们需要选择恰当的日志级别
error:错误日志,指比较严重的错误,对正常业务有影响,需要运维配置监控的;
warn:警告日志,一般的错误,对业务影响不大,但是需要开发关注;
info:信息日志,记录排查问题的关键信息,如调用时间、出参入参等等;
debug:用于开发DEBUG的,关键逻辑里面的运行时数据;
trace:最详细的信息,一般这些信息只记录到日志文件中。
2.日志要打印出方法的入参、出参
我们并不需要打印很多很多日志,只需要打印可以快速定位问题的有效日志。
哪些算得的上有效关键的日志呢?比如说,方法进来的时候,打印入参。再然后呢,在方法返回的时候,就是打印出参,返回值。入参的话,一般就是userId或者bizSeq这些关键信息。正例如下:
public String testLogMethod(Document doc, Mode mode){ log.debug(“method enter param:{}”,userId); String id = "666"; log.debug(“method exit param:{}”,id); return id; } |
3. 使用统一的日志格式
必须使用logback-spring.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property name="LOG_PATH" value="${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}" />
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH}spring.log}"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>${LOG_FILE}.%i</fileNamePattern>
</rollingPolicy>
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<File>${LOG_PATH}error.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>10</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<!-- 异步输出 -->
<appender name ="ASYNC" class= "ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold >0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref ="FILE"/>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
<appender-ref ref="FILE_ERROR" />
</root>
</configuration>
4.不能直接使用日志系统(Log4j、Logback)中的 API,而是使用日志框架SLF4J中的API。
SLF4J 是门面模式的日志框架,有利于维护和各个类的日志处理方式统一,并且可以在保证不修改代码的情况下,很方便的实现底层日志框架的更换。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(TianLuoBoy.class); |
5. 使用参数占位{},而不是用+拼接。
反例:
logger.info("Processing trade with id: " + id + " and symbol: " + symbol); |
上面的例子中,使用+操作符进行字符串的拼接,有一定的性能损耗。
正例如下:
logger.info("Processing trade with id: {} and symbol : {} ", id, symbol); |
我们使用了大括号{}来作为日志中的占位符,比于使用+操作符,更加优雅简洁。并且,相对于反例,使用占位符仅是替换动作,可以有效提升性能。
6. 建议使用异步的方式来输出日志。
日志最终会输出到文件或者其它输出流中的,IO性能会有要求的。如果异步,就可以显著提升IO性能。
以logback为例吧,要配置异步很简单,使用AsyncAppender就行
<!-- 异步输出 --> <appender name ="ASYNC" class= "ch.qos.logback.classic.AsyncAppender"> <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 --> <discardingThreshold >0</discardingThreshold> <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 --> <queueSize>512</queueSize> <!-- 添加附加的appender,最多只能添加一个 --> <appender-ref ref ="FILE"/> </appender> |
7.不要使用e.printStackTrace(),异常日志不要只打一半,要输出全部错误信息
反例:
try{ // 业务代码处理 } catch(Exception e){ e.printStackTrace(); } |
try { //业务代码处理 } catch (Exception e) { // 错误 LOG.error('你的程序有异常啦'); } |
try { //业务代码处理 } catch (Exception e) { // 错误 LOG.error('你的程序有异常啦', e.getMessage()); } |
正例:
try{ // 业务代码处理 } catch(Exception e){ log.error("你的程序有异常啦",e); } |
理由:
e.printStackTrace()打印出的堆栈日志跟业务代码日志是交错混合在一起的,通常排查异常日志不太方便。
e.printStackTrace()语句产生的字符串记录的是堆栈信息,如果信息太长太多,字符串常量池所在的内存块没有空间了,即内存满了,那么,用户的请求就卡住啦~
8.禁止在线上环境开启 debug日志
因为一般系统的debug日志会很多,并且各种框架中也大量使用 debug的日志,线上开启debug不久可能会打满磁盘,影响业务系统的正常运行。
9.不要记录了异常,又抛出异常
反例如下:
log.error("IO exception", e);
throw new MyException(e);
这样实现的话,通常会把栈信息打印两次。这是因为捕获了MyException异常的地方,还会再打印一次。
这样的日志记录,或者包装后再抛出去,不要同时使用!否则你的日志看起来会让人很迷惑。
10.避免重复打印日志
重复打印日志会浪费磁盘空间。如果你已经有一行日志清楚表达了意思,避免再冗余打印,反例如下:
if(user.isVip()){ log.info("该用户是会员,Id:{}",user,getUserId()); //冗余,可以跟前面的日志合并一起 log.info("开始处理会员逻辑,id:{}",user,getUserId()); //会员逻辑 }else{ //非会员逻辑 } |
如果你是使用log4j日志框架,务必在log4j.xml中设置 additivity=false,因为可以避免重复打印日志
正例:
<logger name="com.taobao.dubbo.config" additivity="false">
11.核心功能模块,打印较完整的日志
我们日常开发中,如果核心或者逻辑复杂的代码,建议添加详细的注释,以及较详细的日志。
日志要多详细呢?脑洞一下,如果你的核心程序哪一步出错了,通过日志可以定位到,那就可以啦。
12.基础包用打印error日志代替抛出异常
13.线上日志要使用英文描述
14.测试环境可以使用debug级别打印日志
15.需要打印日志的场景(待补充)
- 系统初始化流程
- 编程语言提示异常
- 业务流程预期不符
- 系统核心角色,组件关键动作
- 作为服务提供方(打印入参、出参)
- 作为服务调用方(打印入参、返参)
- 定时任务开始记录
- 定时任务运行相关记录
- 定时任务结束时成功或失败记录
- 大批量数据执行进度
- 关键变量以及正在做的重要事情
- 异步任务开始执行记录
- 异步任务关键流程和重要参数日志
- 程序中重要的状态信息的变化
- else逻辑要打印关键参数日志,要知道为什么走到else逻辑
- 在启动应用或启动相应配置时打印日志
- 配置信息变更日志