包阅导读总结
1.
关键词:Java 日志、最佳实践、Fastjson 序列化、Logback 配置、异常堆栈
2.
总结:本文是 Java 日志系列文章的第五篇,介绍了日志的最佳实践,包括只使用接口层记录日志、正确的日志标记方式、Fastjson 序列化的参数设置、Logback 的各种配置及相关注意事项,还强调抛弃重复实现日志功能的工具类,熟读相关开发手册。
3.
主要内容:
– 日志配置
– 只使用接口层记录日志,向外提供三方工具时对依赖做相应标记
– 避免打印只含分隔线的内容,用关键字标记
– 可用 Marker 使日志语义更清晰
– Fastjson 序列化
– 某些 getter 异常可使用 `SerializerFeature.IgnoreErrorGetter` 参数忽略
– 对于 `Result` 包装类,可追加 `SerializerFeature.IgnoreNonFieldGetter` 参数避免无效数据
– 异常处理
– 异常值参数不占用字符模板,参数数量不匹配会影响打印结果
– 正确处理异常的打印方式
– 日志性能与长度
– 限制消息最大长度,Logback 天然支持,对异常堆栈有更多控制
– 可配置将异常堆栈在一行输出
– 方法名与输出位置
– 一般不建议在日志中输出获取方法名和行号,可硬编码
– 不建议将日志输出至控制台
– 其他
– 抛弃重复实现日志功能的工具类
– 建议熟读《阿里巴巴 Java 开发手册》中日志规约章节
思维导图:
文章地址:https://mp.weixin.qq.com/s/KrJ1s1bCjOQLlnWeUaHJvg
文章来源:mp.weixin.qq.com
作者:尚左
发布时间:2024/6/13 10:20
语言:中文
总字数:2772字
预计阅读时间:12分钟
评分:90分
标签:Java,日志,最佳实践,Fastjson,Logback
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
<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();
log.info("result.userId={}", result.getUserId());
这个问题老生常谈,这里不展开说了。
Fastjson 的序列化其实是依赖于类中的各个 getter,如果某个 getter 抛异常则会阻断整个序列化。但其实有些 getter 异常并非严重问题,此时就可以使用 SerializerFeature.IgnoreErrorGetter 参数忽略 getter 中抛出的异常: public class Foo {
private Long userId;
@Deprecated
private Long accountId;
public Long getAccountId() {
throw new RuntimeException("请使用 userId");
}
}
log.info("foo={}", JSON.toJSONString(foo, SerializerFeature.IgnoreErrorGetter));
比如有个Result包装类如下(注意isError方法),当被 Fastjson 序列化时,会输出“error”:false。如果希望忽略掉类似这种没有实体字段对应的 getter 方法,就可以追加SerializerFeature.IgnoreNonFieldGetter参数: @Data
public class Result<T> {
private boolean success;
private T data;
public boolean isError() {
return !success;
}
}
log.info("result={}", JSON.toJSONString(result, SerializerFeature.IgnoreNonFieldGetter));
这个参数对于打印 Result 包装类非常有帮助。如果打印出“error”:false,那当你希望使用error关键字查询错误时,就会匹配到很多包含error却并非错误的无效数据。
我们在第三篇【3.1 info方法】节中提到,异常值参数是不占用字符模板的,如果你的参数数量不匹配,很可能打印结果与预期不符。如果你这样写: Exception e = new RuntimeException("blahblahblah");
log.error("exception={}", e);
此时因为e与对应的{}位置匹配,Slf4j 会尝试将异常转为字符串拼到日志模板中,最终这句相当于:
log.error("exception={}", e.toString());
最终你只能得到exception=blahblahblah,而堆栈就丢掉了。 log.error("exception={}", e.getMessage(), e);
最终会输出:
exception=blahblahblah
换行后会有堆栈信息
有时候一个 POJO 非常大,当我们通过 :
log.info(“result={}”, JSON.toJSONString(result))
打印日志时,整条日志就会变得很长。不但对性能会有影响,主要这么大的结果对实际问题排查也不见得有帮助。 可以参考第四篇【3.2 Format modifiers】来限制消息最大长度,并将超出的部分丢弃:
其实 Logback 天然支持,比如%exception{50}就可以只打印 50 层。同时 Logback 针对异常堆栈有更多的控制能力,可以参考官方文档Evaluators[1]。
有些同学希望将堆栈在一行输出,保证通过管道(|)进行多层grep时捞到期望的记录。 其实通过 Logback 配置就可以支持这个能力,主要用到我们在 【4.3.1 Conversion Word】中提过的%replace: %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
即:
在 Logback 的配置中,可以通过%method和%line输出方法名和行号。但这两项依赖于当前的堆栈轨迹 (StackTrace) ,而获取堆栈轨迹的代价比较高,日志一多就会占用大量的 CPU,所以一般情况不建议在日志中输出这些字段。 如果对方法名有输出要求,可以直接硬编码到输出字符串中,比如: log.info("queryUserInfo, request={}, result={}", request, result);
我们平时调用System.out.println时,默认输出位置就是控制台。Logback 也提供了ch.qos.logback.core.ConsoleAppender用于将日志输出至控制台。但:
-
机器上线后,没有人会盯着控制台看,所以输出至控制台毫无意义,还浪费机器资源;
-
本地 Debug 时,要么直接加断点,要么会翻日志文件,也基本不会检查控制台输出;
-
通过main函数跑测试代码时,一般直接用System.out.println,不涉及日志系统;
-
实现日志内容拼接,请见第三篇【3.1 info 方法】一节;
-
实现日志参数默认转 JSON;
-
日志超过最大长度截断,请见【6.1 限制日志文本最大长度】一节;
-
将异常堆栈合并在同一行输出,请见【七、将堆栈合并为一行】一节;
-
通过动态开关控制是否打印某些日志;
-
日志中追加 traceId,请见第三篇【五、MDC】、第四篇【五、MDC 中的 traceId】两节;
@Data
public class Foo {
private String bar;
}
Foo foo = new Foo();
foo.setBar("baz");
log.info("foo:{}", foo);
log.info("foo={}", JSON.toJSONString(foo));
看出两者的区别了吗?
方案一使用了 Lombok 的@ToString转字符串,其中的 Key-Value 之间使用的等号=分隔,所以在前边建议使用冒号,从而在查看日志时可以更快分辨记录的信息。 同样的,方案二因为使用的 JSON 格式中 Key-Value 之间使用的冒号:分隔,所以前边建议使用等号。 [1]https://logback.qos.ch/manual/layouts.html#Evaluators
[2]https://github.com/alibaba/p3c
[3]https://github.com/alibaba/p3c/blob/master/p3c-gitbook/异常日志/日志规约.md