1. 背景
上周五11点,上游业务急匆匆的拉我入群,说有一个大客户反馈,他们账户无缘无故不能下单了。
多大的客户呢?真的非常大,看了下他的账户授信额度上千万的级别,这个客户大家绝对都听说过(做物流的一哥)。
吓得我开会的心情的都没了,直接去日志平台排查问题。不看不知道,一看我的血压直接飙升,心里开始骂人了。
这个系统是离职的同学暂时交给我的,现在处于只维护,不新增需求的状态。
2. 代码
简化代码如下:
java
@RequestMapping("/pay")
public void pay(PayReq req) {
// switch 读取阿波罗配置
if (switch) {
aPay(req);
} else {
bPay(req);
}
}
public void aPay(PayReq req) {
// 里面去查了3-4张数据表,涉及5-6个阿波罗字段,才确定是哪个实现类
// payService 下面有5个继承 serviceA, serviceB, serviceC, serviceD, serviceE...
payServiceA = xxxSelect.getPayService(req);
payServiceA.pay(req); // 里面至少5-6个rpc调用
}
看代码,我头都懵了,这下单的逻辑这么复杂,涉及到5-6个系统,10几个数据库表,好几个不同的实现类,几十个阿波罗的配置,分支绕来绕去。
但是日志平台的 <math xmlns="http://www.w3.org/1998/Math/MathML"> 日志只有 3 行!!! \color{red}{日志只有3行!!!} </math>日志只有3行!!! 没错,只有3行,我当时都懵了,这shabi写代码的人,怎么一行日志都不打印,这特么怎么看。
我甚至都不知道你走了aPay,还是bPay。你走的哪个payService我都不知道,报错是哪一行我都看不出来。当时真的无从下手。
不过最后客户账号又好了,也不知道原因。当然这不是今天的主题,今天的主题是:如何优雅的打日志。
3. 如何优雅的打日志
在Java体系中,日志的实现有很多,logback、log4j等等,网上讲怎么配置的也非常多,推荐不知道怎么配置的可以看看这篇文章 log4j2 异步日志正确配置
今天我想聊一聊在真正的生产环境中,如何打日志,哪些地方需要打日志,哪些地方不需要打日志,如何优雅的打日志,这可是血与泪的教训中形成的。
3.1 如何打日志
一个合格的日志必须具备以下要素。具体的时间、日志级别、全局traceId,类名+方法名 + 具体行数 + 具体的内容,尤其是全局traceId,必备。
shell
###|||2025-06-01 17:53:36.000|||INFO|||[全局traceId]()|||http-nio-8080-exec-7|||ClassName|||methodName|||182---> 具体日志内容
3.2 哪些地方需要打日志
- 方法的入口必须打日志。比如controller开头,controller结尾。当然你也可以使用aop全局打印日志。
- 有分支逻辑之前必须打日志。比如你从阿波罗里面读取的配置 if (switch) {xxx} ,又比如你整合了所有数据,最后判断走a逻辑还是b逻辑
- 如果涉及到策略模式、模板模式等设计模式,必须明确打印出,走的是哪个具体的service方法,否则给其他人或者排查问题,会带来很大的困扰
- 调用RPC前后必须打日志,比如 log.info("req = {}", req), doXXX,log.info("res = {}", res),如果如果调用失败,抛异常,必须把完整的栈链路打印出来
- 从数据库获取的数据必须打日志。因为数据库的状态可能会变,比如客户的账号status信息,必须是当时的那个时点的状态信息,而不是在从数据库捞数据,是不准确的。
- 重要数据、上下文数据、一些状态等重要信息必须打日志
- 建议打日志req bo等使用json,比如 log.info("req = {}", JSON.toJsonString(req)),这样排查问题格式化比较方便
- 有些公司的日志平台限制长度,比如1000个字符等等,注意长度
- 宁可多打,也不要少打,因为少打日志,对排查问题难度太大了。
4. 最后
刚毕业在第一家公司的时候,由于项目比较大,本地是启动不了的、不能debug。只能在stg、pre等环境启动,也就养成了我看日志的习惯,也慢慢锻炼了我排查问题的水平。
因此也希望大家养成打日志的好习惯,不要自己的代码被其他人接手的时候,还要被骂这不行那不行的,这样口碑会非常不好的。
慢慢去补这个项目的日志了。