日志打印规范及技巧学习总结

一、日志打印级别

  • 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);
  1. 【强制】日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。

可以结合实际业务需求,基于按天,和按照容量配置appender。例如,按天保存接口对接基本关键数值记录日志,按照容量保存接口对接详细日志。

  1. 【强制】应用中的扩展日志 ( 如打点、临时监控、访问日志等 )
  • 命名方式:appName _ logType _ logName . log 。
  • 日志类型( logType),推荐分类有stats / desc / monitor / visit 等
  • 日志描述(logName)
    这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。
    正例: mppserver 应用中单独监控时区转换异常,如:mppserver _ monitor _ timeZoneConvert . log
  1. 【强制】对 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);
  1. 【强制】避免重复打印日志,浪费磁盘空间,务必在 log 4 j . xml 中设置 additivity = false 。
    正例: <logger name="com.taobao.dubbo.config" additivity="false">
  1. 【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
    正例: logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);
  1. 【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志 ; 有选择地输出 info 日志 ; 如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
    说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
  1. 【参考】可以使用 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

推荐阅读更多精彩内容

  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 2,673评论 1 13
  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 2,867评论 0 5
  • idea 添加注释/** 然后回车 选中代码块 Ctrl+Shift+/ 重点推荐阅读:https://www....
    Helen_Cat阅读 10,939评论 0 34
  • From:Python之日志处理(logging模块) - 云游道士 - 博客园 https://www.cnbl...
    vigny的先生阅读 1,738评论 3 5
  • 程序员的日常离不开日志,日志就好比私人秘书,负责运行周期一切trace工作。优秀的日志实践能极大帮助地程序员快速定...
    Java架构阅读 250评论 0 3