2023年11月29日发(作者:)
Java编码常见的Log⽇志打印问题
前⾔
本⽂总结了作者在Java代码检视中遇到的⼀些关于⽇志打印的问题,并给出修改建议。因能⼒有限,难免存在错漏,欢迎指正。
⼀. 不规范的异常打印
使⽤slf4j⽇志组件时,(与)接受Throwable参数,以打印异常名和详细的堆栈信息(可能内部调⽤tackTrace())。
但书写打印语句时,需要注意格式。例如:
1 ("Best print: ", e);
2 ("Good print: {}", e); //a.
3 ("Bad print: " + e); //b. 或 + ng()
4 ("Bad print: " + sage()); //c. 或: {}", sage())
a句仍可打印异常名和堆栈信息,但多输出⼀对花括号"{}";b句仅打印异常名;c句打印异常消息字符串。以空指针异常(Runtime异常)为
例,b句打印"interException",c句打印"null"(过于简陋)。
可使⽤如下正则表达式排查Java代码⾥不规范的异常打印:
^s*[Ll][Oo][Gg](ger|GER)*.(error|warn)("(.+?)"s*+s*(e|ex|sage())s*);.*
该正则表达式可排查出形如上⽂b、c句的打印缺陷。考虑到实际代码书写风格的差异,会存在少量的漏判和误判。此外应注意,某些异常
(如SQL或IO异常)可能泄露敏感信息,打印异常堆栈之前应根据⽹络安全要求做必要的”加⼯”。
⼆. 不规范的变量打印
使⽤slf4j⽇志组件打印变量时,建议使⽤”{}”占位符风格(C风格),⽽不是”+”拼接符(C++风格)。例如:
1 ("Country: {}, Province: {}, City: {}", ctry, prov, city); //占位符
2 ("Country: "+ctry+", Province:" +prov +", City: "+city); //拼接符
显然,占位符风格更直观,⽽且不容易出现笔误——上⾯的拼接符语句有问题,谁能发现?
但最常见的变量打印问题,是打印时未使⽤占位符或拼接符:
1 ("print cannotBePrinted: ", cannotBePrinted); //Wrong
2 ("print canBePrinted: " + canBePrinted); //Good
3 ("print canBePrinted: {}", canBePrinted); //Better
注意,cannotBePrinted或canBePrinted为⾮Throwable的变量。亦即,异常变量e不适⽤上述规则,但sage()适⽤。
可使⽤如下正则表达式排查Java代码⾥不规范的变量打印:
^s*[Ll][Oo][Gg](ger|GER)*.[a-zA-Z]{4,5}("([^({})]+?)"s*,s*([^es]|w{2,})s*);.*
考虑到实际代码书写风格的差异,该正则表达式会存在漏判和误判。例如,"("Best print: ", ex);"会被误判。
三. 多
余的DEBUG级别判断
使⽤slf4j⽇志组件以Debug级别打印时,debug()⽅法内部会调⽤isDebugEnabled()判断调试级别。因此,下述代码⾥的判断没有必要:
if(gEnabled())
{
("handleChanges end:{}:{}(ms) ", new Object[] {changes, useTime});
}
四. ⽇志对象logger的声明
⼀般建议⽇志对象logger声明为private static final。声明为private可防⽌logger对象被其他类⾮法使⽤。声明为static可防⽌重复new出
logger对象,造成资源浪费;还可以防⽌logger被序列化,造成安全风险。声明为final是因为在类的⽣命周期内⽆需变更logger。
然⽽,实际编码中可根据使⽤场景稍作变通。例如,static final成员变量通常要求⼤写(如LOGGER),如果觉得⼤写别扭,可以去除final修
饰。⼜如,期望logger对象可复⽤时,可如下定义:
protected final Logger logger = ger(this.getClass());
这样,⼦类可以直接使⽤继承来的logger对象打印输出,⽽⽆需再次getLogger()获取⽇志对象。
五. ⽇志级别应合理
如果⽇志不分级别或级别不合理,则定位问题时就⽆法快速有效地屏蔽⼤量低级别信息,给快速定位带来难度。建议与具体实现有关的⽇
志使⽤debug级,⼀般的业务处理⽇志⽤info级,不影响业务进⾏的错误⽤warn级,⽽记录异常或重要错误的⽇志应为error级。


发布评论