简介
日志记录的目的
1).对程序运行情况进行记录和监控
2).在必要是可详细了解程序的内部运行状态
3).记录RPC调用请求响应,以便排除夸系统访问问题。
5).支持分布式日志收集,以便在分布式部署中能集中查看和分析日志
6).对系统行影响尽量小
“
java 日志框架选择
1)log4j
2)log4j2
3)logback
为了飞马眼的flume插件不用维护多份,统一使用log4j2进行日志打印
日志级别
1)DEBUG
调试,主要用于开发过程中日志,可以详细到走了哪些流程,流程中的变量是什么,日常生产环境不开启debug日志,当遇到无法断定的问题,info日志也无法查出问题,再开启定位问题
2)INFO
信息,记录主要业务流程信息,能在数据异常的情况下,判断代码走了哪些分支,主业务流程和不稳定流程都应该记录
3)WARN
警告,不会导致流程异常,可能存在潜在问题、不需要立即人为介入,可以支持每月盘点时再分析的问题。
4)ERROR
异常,预料之外的问题,程序没法走下去,或不应该走下去,都可以抛出异常日志
5)FATAL
致命,系统崩溃级别的异常,需要立即调查,例如数据库无法连接、zookeeper连接不上、程序启动失败等。
最佳实践
1.在一个对象中通常只使用一个Logger对象,Logger应该是private static final的,只有在少数需要在构造函数中传递logger的情况下才使用private final。
static final Logger logger = LoggerFactory.getLogger(Main.class);
2.应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
3.输出Exceptions的全部Throwable信息,因为logger.error(msg)和logger.error(msg,e.getMessage())这样的日志输出方法会丢失掉最重要的StackTrace信息。
void foo(){
try{
} catch(Exception e){
logger.error(e.getMessage());//错误
logger.error("bad things",e.getMessage());//错误
logger.error("bad things",e);//正确
}
}
4.对trace/debug/info级别的日志输出,尽量使用占位符的方式。
说明:logger.debug(“Processing trade with id: ” + id + ” symbol: ” + symbol); 如果日志级别是warn,上述日志不会打印,但是会执行字符串拼接操作,如果symbol是对象,会执行toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
//正例:(占位符)
logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);
5.不允许记录日志后又抛出异常,因为这样会多次记录日志,只允许记录一次日志。
void foo(){
try{
//do something...
} catch(Exception e){
logger.error("bad things",e);
throw new BussException("bad things",e);
}
}
6.异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么往上抛。
void foo(){
try{
//do something...
} catch(Exception e){
throw new BussException("各类参数或者对象toString" + "_" + e.getMessage(), e);
}
}
7.可以使用warn日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。注意日志输出的级别,error级别只记录系统逻辑出错、异常等重要的错误信息。如非必要,请不要在此场景打出error级别。
if(tradeNo == null){
logger.warn("继续履约,交易单号为:null ");//正确
logger.error("继续履约,交易单号为:null ");//错误
throw new BusinessException(BreakStatusEnum.BREAK_PARAMETER_ERROR.getCode(),"交易单号错误");
}
8.不允许出现System print(包括System.out.println和System.error.println)语句。
void foo(){
try{
//do something...
} catch(Exception e){
System.out.println(e.getMessage()); //错误
System.err.println(e.getMessage()); //错误``
logger.error("bad things",e);//正确
}
}
9.不允许出现printStackTrace。
void foo(){
try{
//do something...
} catch(Exception e){
e.printStackTrace(); //错误
logger.error("bad things",e);//正确
}
}
10.必须存在%eye,除开发环境,必须要包含<appender-ref ref="async_flume"/>
。
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
<Console name="CONSOLE" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5p %t %c.%M:%L %fmeye - %m%n" />
</Console>
<RollingFile name="FILE" fileName="${log.path}/inservice.log" append="true"
filePattern="${log.path}/config.log.%d{yyyy-MM-dd}">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %t %-5p [%c][%M:%L] %fmeye - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
<RollingFile name="ERROR_FILE" fileName="${log.path}/inservice_err.log" append="true"
filePattern="${log.path}/inservice_err.log.%d{yyyy-MM-dd}">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %t %-5p [%c][%M:%L] %fmeye - %m%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
</RollingFile>
<FMEyeAsync name="async_flume" hosts="${flume.host}" depName="${flume.depName}"
application="${flume.application}" unsafeMode="true"
blocking="false" bufferSize="2048">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%l] [%rms] %fmeye - %m" />
</FMEyeAsync>
</appenders>
<loggers>
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
<appender-ref ref="async_flume"/>
</root>
</loggers>
</configuration>
11.远程过程调用(RPC)必须在日志中输出请求的request和response
void foo(){
log.info("开始调用订单转权接口:{}", BeanJsonUtil.bean2Json(transWhouseIn));
DataResponse<TransWhouseOut> executeResult = tradeOrderService.transWhouse(transWhouseIn);
TransWhouseOut transWhouseOut = executeResult.getData();
log.info("调用订单转权后返回的数据为:{}", BeanJsonUtil.bean2Json(executeResult));
}
参考资料
[Log日志规范]http://www.cnblogs.com/kofxxf/p/3713472.html
[阿里java编码规范]http://git.dazong.com/TradeDept/DeptDoc/blob/master/%E6%A0%87%E5%87%86%E8%A7%84%E8%8C%83/%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83/%E9%98%BF%E9%87%8Cjava%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83.pdf
[SLF4J]http://blog.youkuaiyun.com/ydpiaoyun/article/details/6717969
[SLF4J]https://www.slf4j.org/index.html
[java日志规范]https://www.linkedin.com/pulse/java%E6%97%A5%E5%BF%97%E8%A7%84%E8%8C%83-ding-lau
[log4j2]http://logging.apache.org/log4j/2.x/manual/api.html
鸣谢
本文摘录自飞马大宗贸易部*斌文