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

一、日志打印级别

  • 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 。
    正例:
  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();  
    ... 
    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


 上一篇
如何高效学习开源项目 如何高效学习开源项目
随着蓬勃发展的开源时代的到来,为了减少开发成本,提高开发效率,越来越多的公司使用各种开源项目,作为开发者,如果能充分利用好开源项目中的资源,不仅能提高实践能力,专业知识水平,还能从中其中学到的优秀的架构思想。 本文将提供一些学习开源项目的
2019-10-13
下一篇 
基于SonarQube代码质量检查工具总结 基于SonarQube代码质量检查工具总结
# 1 概述SonarQube(sonar)是一个开源平台,用于管理源代码的质量。 SonarQube不只是一个质量数据报告工具,更是代码质量管理平台。 支持java, C#, C/C++, PL/SQL, Cobol, JavaScri
2019-10-13
  目录