B端系统,记好日志超级重要!!!
做好日志记录,通过查看日志,能帮我们解决很多问题,以下是开发过程中经常碰到的一些问题:
程序是不是按预期执行?
可以通过日志记录程序的执行流程、运行状态、关键指标⋯⋯有了这些日志,才能更好地进行调试跟踪。
程序有BUG怎么办?
有BUG得排查问题,而定位问题最高效的方式,就是日志。总不能一行行代码排查,或者掐指一算随便蒙,不然问题没找到,更不用谈修复上线。
用户在系统上干了什么?
这就需要结合业务,记录用户操作行为。用户登录到退出系统中间,所有的重要操作,都应该形成日志。
这个问题是谁造成的?
在应用中,不同系统之间的协作相当紧密,有时系统出问题了,可能是第三方系统造成的,通过在程序交互的关键位置记好日志,就能减少不必要的扯皮。
日志记得好,甩锅没烦恼!
关于什么时候记
程序流程
记录程序的流转分支,在关键代码逻辑的执行前后进行相应的日志输出,有助于代码调试。但要避免不必要的日志输出,比如一般只在循环体前后记录日志,而不在循环体内重复记录,过多的日志反而会影响阅读。
远程调用
远程调用因为涉及到外部系统的交互,在出入口处记录请求响应的信息,相当重要。
系统初始化
系统初始化需要依赖一些关键配置参数,这些参数决定系统的启动状态,应该把这些系统初始化信息记录起来。
核心业务操作
系统用户进行核心业务操作的行为,也应该进行记录,便于进行操作审计。
可预期的异常
这类异常应该有效记录起来,通过警告方式反馈给相关人员加以关注,避免频繁发生,最终演化为不可控的错误。
预期外的错误
这类异常发生时,要有详尽记录,并通知相关人员介入处理,第一时间作出响应,因为这种错误已经影响系统的正常使用。
日志中记什么
理想的日志中应该记录不多不少的信息。
不多,是指不要在日志中记录无用的信息。实践中常见到的无用的日志有:
1)能够放在一条日志里的东西,放在多条日志中输出;
2)预期会发生且能够被正常处理的异常,打印出一堆无用的堆栈;
3)开发人员在开发过程中为了调试方便而加入的“临时”日志
不少,是指对于日志的使用者,能够从日志中得到所有需要的信息。在实践中经常发生日志不够的情况,例如:
1)请求出错时不能通过日志直接来定位问题,而需要开发人员再临时增加日志并要求请求的发送者重新发送同样的请求才能定位问题;
2)无法确定服务中的后台任务是否按照期望执行;
3)无法确定服务的内存数据结构的状态;
4)无法确定服务的异常处理逻辑(如重试)是否正确执行;
5)无法确定服务启动时配置是否正确加载;
日志不规范,排查泪两行!
应该怎么做
1、遵守约定,尽量保证除了开发人员自己以外,包括测试人员和运维人员都可以方便地通过日志定位问题;
2、在实际的解决问题过程中,结合线上问题的定位,不断地进行优化。最关键的一点是,团队要重视日志优化这件事情,不要让日志的质量持续降低(当项目变大时,项目的代码也存在一样的问题,越写越乱)。实践:
-
在定位问题的过程中完善日志,如果定位问题花费了很长时间,那就说明系统日志还存在问题,需要进一步完善和优化;
-
思考是否可以通过优化日志,来提前预判该问题是否可能发生(如某种资源耗尽而导致的错误,可以对资源的使用情况进行记录)
-
通过在查问题的过程来优化日志记录的方式;
-
运维或测试在日志中发现的问题,都需要及时向开发人员反映;
注意事项
1、上线后日志观察
每一次上线完成后,除了对系统进行完整的回归测试外,还需要对日志进行观察,特别是当上线新功能以后,可以通过日志确认新功能是否工作正常。
2、日志文件的大小
日志规约摘录
【推荐】对不确定会否输出的日志,采用占位符或条件判断
//wrong
logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
//right
logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);
如果日志级别是info,上述日志不会打印,但是会执行
1)字符串拼接操作
2)如果symbol是对象,还会执行toString()方法
浪费了系统资源,最终日志却没有打印。
【推荐】对确定输出,而且频繁输出的日志,采用直接拼装字符串的方式
如果这是一条WARN,ERROR级别的日志,或者确定输出的INFO级别的业务日志,直接字符串拼接,比使用占位符替换,更加高效。
Slf4j的占位符并没有魔术,每次输出日志都要进行占位符的查找,字符串的切割与重新拼接。
参考:org.slf4j.helpers.MessageFormatter#arrayFormat(java.lang.String, java.lang.Object[], java.lang.Throwable)
【强制】禁止使用性能很低的System.out()打印日志信息
同理也禁止e.printStackTrace();
例外: 应用启动和关闭时,担心日志框架还未初始化或已关闭。
- Sonar-106: Standard outputs should not be used directly to log anything
- Sonar-1148: Throwable.printStackTrace(…) should not be called
【推荐】谨慎地记录日志,避免大量输出无效日志,信息不全的日志
大量地输出无效日志,不利于系统性能,也不利于快速定位错误点。
记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws往上抛出。
正例:logger.error("inputParams:{} and errorMessage:{}", 各类参数或者对象 toString(), e.getMessage(), e);
参考: