Java日志通关(五) - 最佳实践
阿里妹导读
一、总是使用接口层
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-core</artifactId><version>${logback.version}</version><scope>runtime</scope><optional>true</optional></dependency>
简单解释一下:
<scope>runtime</scope>:runtime 的包编译时会被忽略(认为运行环境已经有对应包了); <optional>true</optional>:依赖不会传递,Maven 不会自动安装此包;
二、不要打印分隔线
三、避免因写日志而抛错
Object result = rpcResource.call();// 如果 result 为 null 会抛 NPElog.info("result.userId={}", result.getUserId());
这个问题老生常谈,这里不展开说了。
四、两个 Fastjson 参数
4.1 IgnoreErrorGetter
public class Foo {private Long userId;@Deprecatedprivate Long accountId;// getter 有异常抛出public Long getAccountId() {throw new RuntimeException("请使用 userId");}}// 这样打印日志,就不会被 getter 抛出的异常阻断了log.info("foo={}", JSON.toJSONString(foo, SerializerFeature.IgnoreErrorGetter));
4.2 IgnoreNonFieldGetter
@Datapublic class Result<T> {private boolean success;private T data;public boolean isError() {return !success;}}// 这样打印日志,就不会有 "error":false 了log.info("result={}", JSON.toJSONString(result, SerializerFeature.IgnoreNonFieldGetter));
这个参数对于打印 Result 包装类非常有帮助。如果打印出 "error":false,那当你希望使用 error 关键字查询错误时,就会匹配到很多包含 error 却并非错误的无效数据。
五、不要遗漏异常堆栈
Exception e = new RuntimeException("blahblahblah");log.error("exception={}", e); // 此时 IDEA 会给出警告:参数比占位符少
此时因为 e 与对应的{}位置匹配,Slf4j 会尝试将异常转为字符串拼到日志模板中,最终这句相当于:
log.error("exception={}", e.toString());
// 用 e.getMessage() 拼到日志信息后,同时有独立的 e 用于打印堆栈log.error("exception={}", e.getMessage(), e);
最终会输出:
exception=blahblahblah换行后会有堆栈信息
六、限制日志输出长度
6.1 限制日志文本最大长度
有时候一个 POJO 非常大,当我们通过 :
log.info("result={}", JSON.toJSONString(result))
%.-2000message
6.2 限制堆栈的层级
七、将堆栈合并为一行
%replace(%exception){'[\r\n\t]+', ' '}%nopex
简单说明一下:
%replace(p){r, t}:将给到的 p,使用正则 r 进行匹配,命中的替换为 t,所以上边就是,将 %exception 中的 [\r\n\t](即换行、回车、Tab)替换为(四个空格);
%nopex:如果不加,Logback 会自动在日志最后追加 %exception,导致异常堆栈打两遍(一遍我们自己转为一行的,一遍带原始换行的);
%.-10000replace(%exception{50}){'[\r\n\t]+', ' '}%nopex
即:
只打印前 50 层堆栈;
转为一行后,再限制最大长度为 10000,超过的部分丢弃尾部字符;
八、不建议使用 %method 和 %line
log.info("queryUserInfo, request={}, result={}", request, result);
九、不要将日志输出至 Console
机器上线后,没有人会盯着控制台看,所以输出至控制台毫无意义,还浪费机器资源;
本地 Debug 时,要么直接加断点,要么会翻日志文件,也基本不会检查控制台输出;
通过 main 函数跑测试代码时,一般直接用 System.out.println,不涉及日志系统;
十、无用的 LogUtil
实现日志内容拼接,请见第三篇【3.1 info 方法】一节;
实现日志参数默认转 JSON;
日志超过最大长度截断,请见【6.1 限制日志文本最大长度】一节;
将异常堆栈合并在同一行输出,请见【七、将堆栈合并为一行】一节;
通过动态开关控制是否打印某些日志;
日志中追加 traceId,请见第三篇【五、MDC】、第四篇【五、MDC 中的 traceId】两节;
十一、熟读《日志规约》
十二、一个小细节
@Datapublic class Foo {private String bar;}Foo foo = new Foo();foo.setBar("baz");// 方案一(注意第一个参数里的冒号)log.info("foo:{}", foo);// 输出 foo:Foo{bar=baz}// 方案二(注意第一个参数里的等号)log.info("foo={}", JSON.toJSONString(foo));// 输出 foo={"bar":"baz"}
看出两者的区别了吗?
[1]https://logback.qos.ch/manual/layouts.html#Evaluators
[2]https://github.com/alibaba/p3c