← 返回目录

(三) 日志规约

第 14 / 27 卷作者:guanbao yang日期:

说明:本规约明确防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败,运行时异常等场景返回 null 的情况。

11.【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:

1)返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE 反例:public int method() { return Integer 对象; },如果为 null,自动解箱抛 NPE。

2)数据库的查询结果可能为 null。

3)集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。

4)远程调用返回对象时,一律要求进行空指针判断,防止 NPE。

5)对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。

6)级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。

正例:使用 JDK8 的 Optional 类来防止 NPE 问题。

12.【推荐】定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(),更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异

常,如:DAOException / ServiceException 等。

13.【参考】对于公司外的 http / api 开放接口必须使用错误码,而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess() 方法、错误码、错误简短信息;应用内部推荐

异常抛出。

说明:关于 RPC 方法返回方式使用 Result 方式的理由:

1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。

2)如果不加栈信息,只是 new自定义异常,加入自己的理解的 error message,对于调用端解决问题的帮助不会太多。

如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。

(三) 日志规约

1.【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架(SLF4J、 JCL—Jakarta Commons Logging)中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理

方式统一。

说明:日志框架(SLF4J、JCL--Jakarta Commons Logging)的使用方式(推荐使用 SLF4J)

使用 SLF4J:

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(Test.class);

使用 JCL:

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

private static final Log log = LogFactory.getLog(Test.class);

2.【强制】日志文件至少保存 15天,因为有些异常具备以“周”为频次发生的特点。对于当天日志,以 “应用名.log”来保存,保存在/{统一目录}/{应用名}/logs/目录下,过往日志格式为:

{logname}.log.{保存日期},日期格式:yyyy-MM-dd

正例:以 mppserver 应用为例,日志保存/home/admin/mppserver/logs/mppserver.log,历史日志名称

4.【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:

appName_logType_logName.log。logType:日志类型,如 stats / monitor / access 等;

logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。

说明:推荐对日志进行分类,将错误日志和业务日志分开放,便于开发人员查看,也便于通过日志对系统进行及时监控。

正例:mppserver 应用中单独监控时区转换异常,如:mppserver_monitor_timeZoneConvert.log

5.【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。

说明:因为 String 字符串的拼接会使用 StringBuilder 的 append() 方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。

正例:logger.debug("Processing trade with id : {} and symbol : {}", id, symbol);

6.【强制】对于trace / debug / info 级别的日志输出,必须进行日志级别的开关判断:

说明:虽然在 debug(参数) 的方法体内第一行代码 isDisabled(Level.DEBUG_INT) 为真时(Slf4j 的常见实现 Log4j 和 Logback),就直接return,但是参数可能会进行字符串拼接运算。此外,如果 debug(getName()) 这种参数内有

getName() 方法调用,无谓浪费方法调用的开销。

正例:

// 如果判断为真,那么可以输出 trace 和 debug 级别的日志 if (logger.isDebugEnabled()) {

logger.debug("Current ID is: {} and name is: {}", id, getName());

}

7.【强制】避免重复打印日志,浪费磁盘空间,务必在日志配置文件中设置 additivity=false

正例:<logger name="com.taobao.dubbo.config" additivity="false">

8.【强制】生产环境禁止使用 System.out 或 System.err 输出或使用 e.printStackTrace() 打印异常堆栈。

说明:标准日志输出与标准错误输出文件每次 Jboss 重启时才滚动,如果大量输出送往这两个文件,容易造成文件大小超过操作系统大小限制。

9.【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。

正例:logger.error("inputParams: {} and errorMessage: {}", 各类参数或者对象 toString(), e.getMessage(), e);

10.【强制】日志打印时禁止直接用JSON工具将对象转换成 String。

说明:如果对象里某些 get方法被覆写,存在抛出异常的情况,则可能会因为打印日志而影响正常业务流程的执行。

正例:打印日志时仅打印出业务相关属性值或者调用其对象的 toString() 方法。

11.【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时

删除这些观察日志。

说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?

12.【推荐】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。

说明:注意日志输出的级别,error 级别只记录系统逻辑出错、异常或者重要的错误信息。

说明:日志排查问题时,推荐使用订单号、UUID 之类的唯一编号进行查询。

全站分卷索引 · 27 卷
  1. 1一、编程规约
  2. 2(二) 常量定义
  3. 3(三) 代码格式
  4. 4(四) OOP规约
  5. 5(五) 日期时间
  6. 6(六) 集合处理
  7. 7(七) 并发处理
  8. 8(八) 控制语句
  9. 9(九) 注释规约
  10. 10(十) 前后端规约
  11. 11(十一) 其他
  12. 12二、异常日志
  13. 13(二) 异常处理
  14. 14(三) 日志规约
  15. 15三、单元测试
  16. 16四、安全规约
  17. 17五、MySQL数据库
  18. 18(二) 索引规约
  19. 19(三) SQL语句
  20. 20(四) ORM映射
  21. 21六、工程结构
  22. 22(二) 二方库依赖
  23. 23(三) 服务器
  24. 24七、设计规约
  25. 25附1:版本历史
  26. 26附2:专有名词解释
  27. 27附3:错误码列表