一、日志打印级别
- DEBUG(调试)
开发调试日志。一般来说,在系统实际运行过程中,不会输出该级别的日志。因此,开发人员可以打印任何自己觉得有利于了解系统运行状态的东东。不过很多场景下,过多的DEBUG日志,并不是好事,建议是按照业务逻辑的走向打印。 - INFO(通知)
INFO日志级别主要用于记录系统运行状态等关联信息。该日志级别,常用于反馈系统当前状态给最终用户。所以,在这里输出的信息,应该对最终用户具有实际意义,也就是最终用户要能够看得明白是什么意思才行。 - WARN(警告)
WARN日志常用来表示系统模块发生问题,但并不影响系统运行。 此时,进行一些修复性的工作,还能把系统恢复到正常的状态。 - ERROR(错误)
此信息输出后,主体系统核心模块正常工作,需要修复才能正常工作。 就是说可以进行一些修复性的工作,但无法确定系统会正常的工作下去,系统在以后的某个阶段,很可能会因为当前的这个问题,导致一个无法修复的错误(例如宕机),但也可能一直工作到停止也不出现严重问题。
二、日志打印规范
1. 【强制】应用中不可直接使用日志系统 (Log 4 j 、 Logback) 中的 API ,而应依赖使用日志框架
SLF 4 J 中的 API ,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
- 【强制】日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。
可以结合实际业务需求,基于按天,和按照容量配置appender。例如,按天保存接口对接基本关键数值记录日志,按照容量保存接口对接详细日志。
- 【强制】应用中的扩展日志 ( 如打点、临时监控、访问日志等 )
- 命名方式:appName _ logType _ logName . log 。
- 日志类型( logType),推荐分类有stats / desc / monitor / visit 等。
- 日志描述(logName)。
这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。
正例: mppserver 应用中单独监控时区转换异常,如:mppserver _ monitor _ timeZoneConvert . log
- 【强制】对 trace / debug / info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
说明: logger . debug( " Processing trade with id : " + id + " and symbol : " + symbol)。如果日志级别是 warn ,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,会执行 toString() 方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
正例: ( 占位符 )
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
- 【强制】避免重复打印日志,浪费磁盘空间,务必在 log 4 j . xml 中设置 additivity = false 。
正例:
- 【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
正例: logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);
- 【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志 ; 有选择地输出 info 日志 ; 如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
- 【参考】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。注意日志输出的级别, error 级别只记录系统逻辑出错、异常等重要的错误信息。如非必要,请不要在此场景打出 error 级别。
三、日志打印技巧
问题排查的日志
- 对接外部的调用封装:
程序中对接外部系统与模块的依赖调用前后都记下日志,方便接口调试。出问题时也可以很快理清是哪块的问题
LOG.debug("Calling external system:" + parameters);
Object result = null;
try {
result = callRemoteSystem(params);
LOG.debug("Called successfully. result is " + result);
} catch (Exception e) {
LOG.warn("Failed at calling xxx system . exception : " + e);
}
- 状态变化:
程序中重要的状态信息的变化应该记录下来,方便查问题时还原现场,推断程序运行过程
boolean isRunning = true;
LOG.info("System is running");
//...
isRunning = false;
LOG.info("System was interrupted by " + Thread.currentThread().getName());
- 系统入口与出口:
这个粒度可以是重要方法级或模块级。记录它的输入与输出,方便定位
void execute(Object input) {
LOG.debug("Invoke parames : " + input);
Object result = null;
//business logical
LOG.debug("Method result : " + result);
}
- 业务异常:
任何业务异常都应该记下来
try {
//business logical
} catch (IOException e) {
LOG.warn("Description xxx" , e);
} catch (BusinessException e) {
LOG.warn("Let me know anything",e);
} catch (Exception e) {
LOG.error("Description xxx", e);
}
void invoke(Object primaryParam) {
if (primaryParam == null) {
LOG.warn(原因...);
return;
}
}
- 非预期执行:
为程序在“有可能”执行到的地方打印日志。如果我想删除一个文件,结果返回成功。但事实上,那个文件在你想删除之前就不存在了。最终结果是一致的,但程序得让我们知道这种情况,要查清为什么文件在删除之前就已经不存在呢
int myValue = xxxx;
int absResult = Math.abs(myValue);
if (absResult < 0) {
LOG.info("Original int " + myValue + "has nagetive abs " + absResult);
}
- 很少出现的else情况:
else可能吞掉你的请求,或是赋予难以理解的最终结果
Object result = null;
if (running) {
result = xxx;
} else {
result = yyy;
LOG.debug("System does not running, we change the final result");
}
程序运行状态的日志
程序在运行时就像一个机器人,我们可以从它的日志看出它正在做什么,是不是按预期的设计在做,所以这些正常的运行状态是要有的。
- 程序运行时间:
long startTime = System.currentTime();
// business logical
LOG.info("execution cost : " + (System.currentTime() - startTime) + "ms");
大批量数据的执行进度:
LOG.debug("current progress: " + (currentPos * 100 / totalAmount) + "%");
关键变量及正在做哪些重要的事情:
执行关键的逻辑,做IO操作等等
String getJVMPid() {
String pid = "";
// Obtains JVM process ID
LOG.info("JVM pid is " + pid);
return pid;
}
void invokeRemoteMethod(Object params) {
LOG.info("Calling remote method : " + params);
//Calling remote server
}
四、需要规避的问题
频繁打印大数据量日志:
当日志产生的速度大于日志文件写磁盘的速度,会导致日志内容积压在内存中,导致内存泄漏。无意义的Log:
日志不包含有意义的信息: 你肯定想知道的是哪个文件不存在吧
File file = new File("xxx");
if (!file.isExist()) {
LOG.warn("File does not exist"); //Useless message
}
- 混淆信息的Log:
日志应该是清晰准确的: 当看到日志的时候,你知道是因为连接池取不到连接导致的问题么?
Connection connection = ConnectionFactory.getConnection();
if (connection == null) {
LOG.warn("System initialized unsuccessfully");
}
参考:
《阿里巴巴开发手册》
Logger日志级别说明及设置方法、说明
闲谈程序中如何打印log