前言
线上出现问题,你的第一反应是什么?
如果是我的话,第一时间想的应该是查日志:
- if…else 到底进入了哪个分支?
- 关键参数是不是有缺失?
- 入参是不是有问题,没做好校验放进去了?
良好的日志能帮我们快速定位到问题所在,坑你的东西往往最为无形,良好的日志就是要让这些玩意无所遁形!
使用正确的格式
如果你是这样打印日志的:
log.info("根据条件id:{}" + id + "查询用户信息");
复制代码
不要这样做,会产生大量的字符串对象,占用空间的同时也会影响性能。
正确的做法是使用参数化信息的方式:
log.info("根据条件id:[{}],查询用户信息", id);
复制代码
这样做除了能避免大量创建字符串之外,还能明确的把参数隔离出去,当你需要把参数复制出来的时候,只需要双击鼠标即可,而不是用鼠标慢慢对准再划拉一下。
这样打出来的日志,可读性强,对排查问题的帮助也很大!
小技巧
1)多线程
遇到多个线程一起执行的日志怎么打?
有些系统,涉及到并发执行,定时调度等等,就会出现多次执行的日志混在一起,出问题不好排查,我们可以把线程名打印进去,或者加一个标识用来表明这条日志属于哪一次执行:
if (log.isDebugEnabled()) {
log.debug("执行ID=[{}],处理了ID=[{}]的消息,处理结果:[{}]", execId, id, result);
}
复制代码
2)使用 SpringBoot Admin 灵活开关日志级别
日志级别
Java应用中,日志一般分为以下5个级别:
- ERROR 错误信息
- WARN 警告信息
- INFO 一般信息
- DEBUG 调试信息
- TRACE 跟踪信息
1)ERROR
ERROR 级别的日志一般在 catch 块里面出现,用于记录影响当前线程正常运行的错误,出现 Exception 的地方就可以考虑打印 ERROR 日志,但不包括业务异常。
需要注意的是,如果你抛出了异常,就不要记录 ERROR 日志了,应该在最终的地方处理,下面这样做就是不对的:
try {
int i = 1 / 0;
} catch (Exception e) {
log.error("出错了,什么错我不知道,啊哈哈哈!", e);
throw new CloudBaseException();
}
复制代码
2)WARN
不应该出现,但是不会影响当前线程执行的情况可以考虑打印 WARN 级别的日志,这种情况有很多,比如:
- 各种池(线程池、连接池、缓存池)的使用超过阈值,达到告警线
- 记录业务异常
- 出现了错误,但是设计了容错机制,因此程序能正常运行,但需要记录一下
3)INFO
使用最多的日志级别,使用范围很广,用来记录系统的运行信息,比如:
- 重要模块中的逻辑步骤呈现
- 客户端请求参数记录
- 调用第三方时的参数和返回结构
4)DEBUG
Debug 日志用来记录自己想知道的所有信息,常常是某个功能模块运行的详细信息,已经中间的数据变化,以及性能信息。
Debug 信息在生产环境一般是关闭状态的,需要使用开关管理(比如 SpringBoot Admin 可以做到),一直开启会产生大量的 Debug,而 Debug 日志在程序正常运行时大部分时间都没什么用。
if (log.isDebugEnabled()) {
log.debug("开始执行,开始时间:[{}],参数:[{}]", startTime, params);
log.debug("通过计算,得到参数1:[{}],参数2:[{}]", param1, param2);
log.debug("最后处理结果:[{}]", result);
}
复制代码
5)TRACE
特别详细的系统运行完成信息,业务代码中一般不使用,除非有特殊的意义,不然一般用 DEBUG 代替,事实上,我编码到现在,也没有用过这个级别的日志。
写在最后
一开始写代码的时候,没有规范日志的意识,不管哪里,都打个 INFO,打印出来的东西也没有思考过,有没有意义,其实让自己踩了不少坑,加了不少班,回过头,我想对学习时期的我说一句:”能让你加班的东西,都藏在各种细节里!写代码之前,先好好学习如何打日志!“