规范内容取自“阿里巴巴异常日志强制规范”(Java版),发布时间为2022.2.3,开源。
内容较多,3000字,建议收藏。
规范的原理,比规范本身,更加重要。
第一部分,异常处理强制规范【9条】
1. 可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理。
正例:
if (obj != null) {...}
反例:
try { obj.method(); }
catch (NullPointerException e) {…}
2. 不要用异常捕获模块来做流程控制,条件控制。
画外音:异常捕获的初衷是解决程序运行中的各种意外情况,而不是处理业务异常分支。
3. 只 catch 非稳定代码。
稳定代码指的是无论如何不会出错的代码。
正例:
用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单... 这些是稳定代码,应该在程序上作出判断,并提示给用户,而不是放到 try catch 中。
4. 捕获异常的目的是为了处理它,不能捕获了却不处理而抛弃之。
如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,并将其转化为用户可以理解的内容。
5. 事务场景中,抛出异常被 catch 后,如果需要回滚,可能要考虑业务层面的补偿事务。
6. finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch 。
画外音:如果JDK7,可以使用 try-with-resources 方式。
7. 不要在 finally 块中使用 return 。
画外音:try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句,则会在此直接返回,无情丢弃掉 try 块中的返回点。
反例:
8. 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
9. 在调用RPC、二方包、或动态生成类的相关方法时,捕捉异常使用 Throwable 类进行拦截。
画外音:二方包,是指公司内部的依赖库。
通过反射机制来调用方法,如果找不到方法,抛出 NoSuchMethodException 。
反例:
非核心服务引入了高版本的 spring ,导致运行到某段核心逻辑时,抛出 NoSuchMethodError 错误, catch 用的类却是 Exception ,堆栈向上抛,影响到上层业务。这是一个非核心功能点影响到核心应用的典型反例。
第二部分,错误码强制规范【7条】
1. 错误码的制定原则是:快速溯源、沟通标准化。
画外音:错误码必须能够方便的比对与查找(方便grep,方便代码查找),让人能够快速知晓错误来源,快速判断是谁的问题,并对错误原因快速达成一致认知。
2. 错误码不体现版本号和错误等级信息。
画外音:
错误码只追加,不修改,不使用版本号区分;
错误等级,由错误码本身的业务含义决定,由其他字段体现。
3. 没有错误,但不得不填充错误码时,错误码填写五个零:00000。
4. 错误码为字符串类型,共5位,分成两个部分:错误产生来源+四位数字编号。
其中,错误产生来源分为A/B/C。
A表示错误来源于用户,比如参数错误,用户安装版本过低等:
B表示错误来源于当前系统,往往是业务逻辑出错,例如:
C表示错误来源于非自身系统,例如:
其次,四位数字编号从0001到9999,大类之间的步长间距预留100。
5. 错误码不与公司业务架构挂钩,不与组织架构挂钩,需要在统一平台审批,审批后生效,先到先得。
6. 禁止随意定义新的错误码。
画外音:尽可能在原有错误码表中找语义相同或者相近的错误码使用。
7. 错误码不能直接透传给用户。
画外音,作为工程师,应该区分好:
堆栈(stack_trace)
错误信息(error_message)
错误码(error_code)
用户提示(user_tip)
它们有关联,但不要混用。
第三部分,日志强制规范【10条】
1. 应用中不可直接使用三方日志组件(Log4j、Logback)中的API,而应依赖使用统一日志框架(SLF4J、JCL—Jakarta Commons Logging)中的 API。
画外音:使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
2. 普通日志保存天数与命名需要遵循规范。
(1)日志文件至少保存15天;
画外音:有些异常具备以“周”为频次发生的特点。
(2)当天日志,以“应用名.log”来保存;
(3)保存目录为:/{统一目录}/{应用名}/logs/;
(4)过往日志格式为:{logname}.log.{保存日期}
(5)日期格式为:yyyy-MM-dd
以 mppserver 应用为例:
日志保存为: /home/admin/mppserver/logs/mppserver.log
历史日志为: mppserver.log.2024-08-18
3. 根据国家法律,网络运行状态、网络安全事件、个人敏感信息操作等相关记录,留存的日志不少于六个月,并且要多机备份。
4. 应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。
logType:日志类型,如 stats / monitor / access 等;
logName:日志描述。
画外音:这种命名的好处是,通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
例如,mppserver应用中单独监控时区转换异常:
mppserver_monitor_timeZoneConvert.log
5. 在日志输出时,字符串变量之间的拼接使用占位符。
字符串拼接使用 StringBuilder.append() ,有性能损耗。使用占位符仅是替换动作,可以有效提升性能。
正例:
logger.debug("Processing trade with id : {} and symbol : {}", id, symbol);
6. 对于 trace/debug/info 级别的日志输出,必须进行日志级别的开关判断。
虽然在debug(参数) 的方法体内第一行代码 isDisabled(Level.DEBUG_INT) 为真时就直接 return, 但是参数可能会进行字符串拼接运算。
正例:
7. 避免重复打印日志,浪费磁盘空间,必须在日志配置中设置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方法被覆写,存在抛出异常的情况,则可能会因为打印日志而影响正常业务流程的执行。
规范背后的原理,比规范本身,更有价值。
讨论:
你觉得日志与异常需要被规范吗?
你们公司有相关规范吗?执行情况如何?
相关文章:
希望大家有收获,谢转。