服务端日志规范
0.1 写在前面
日志的作用 | 什么是好的日志 | 日志规范方向 |
---|---|---|
问题追踪 | 日志信息要素全,简单易懂,快速查找 | 定义输出哪些日志和具体输出的信息要素 |
状态监控 | 格式统一,便于自动化采集 | 统一输出的格式,合理的落盘策略 |
业务埋点 | 日志信息要素全,位置要对 | 规范输出日志的场景 |
0.2 日志的要素
- when
- 日志时间
- where
- 产生的行数 // 酌情
- 记录器名称
- 业务标识
- 线程名称
- who
- 会话标识
- what
- 日志级别
- 日志内容
- 异常信息
- 异常堆栈
- how
- 关键参数
一、日志开发规范
-
【强制】 应用中不可直接使用日志系统中的 API,而应依赖使用日志框架中的API。
目的: 使用门面模式的日志框架有利于维护和各个类的日志处理方式统一。 正例:使用slf4j api
@Slf4j public class Test
反例:
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; private static final Logger logger = LogManager.getLogger("HelloWorld");
-
【强制】 在日志输出时,字符串变量之间的拼接使用占位符的方式
目的: 因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符仅是确定需要打印时才有的替换动作,可以有效提升性能 正例:使用占位符的方式
log.info("后台-根据电池类型编号获取电池类型信息:batteryTypeId:{}",batteryTypeId);
反例:
log.info("根据城市编号获取城市信息,cityId:"+cityId);
-
【强制】禁止直接使用 System.out 或 System.err 输出日志或使用e.printStackTrace()打印异常堆栈
目的: 日志输出统一使用日志框架配置,统一格式
-
【强制】 异常信息应该包括两类信息:案发现场信息和异常堆栈信息,如果不处理,那么通过关键字 throws 往上抛出
正例:
logger.error("inputParams:{} and errorMessage:{}", 各类参数或者对象 toString(), e.getMessage(), e);
-
【强制】 避免打印日志时出现空指针,造成日志打印影响了主业务
-
【强制】 日志信息中避免出现换行 避免自动采集程序出现问题
-
【推荐】 打印日志时仅打印出业务相关属性值或者调用其对象的 toString()方法
日志打印时不建议直接用 JSON 工具将对象转换成 String,不过具体问题具体分析,并发访问量不高的情况下可以尝试使用
-
【推荐】 一个项目尽量使用统一的语言(英文/中文)来描述日志信息,考虑英文水平差异性,建议选中文
-
【推荐】 记录日志时请先进行思考:这些日志真的有人看吗,看到这条日志你能做什么,能不能给问题排查带来好处,如果没有,建议不记录。
二、 日志场景规范
日志不是记录的越多越详细越好,太过多的日志,会减少日志的存储天数!
必须记录的一些场景
- 编程语言提示异常
- 业务流程预期不符
- 系统核心角色,组件关键动作
- 系统启动初始化时加载的自定义配置信息
- 调用第三方服务的出参以及入参 // 跨信任边界的调用
酌情考虑的
- 后台定时任务的执行情况
- 特殊的条件分支
- 接口请求数据的入参以及出参
- 执行关键方法的入参以及出参
- 执行关键方法的中间状态记录
三、日志级别规范
debug
此级别不允许在生产环境输出
- 调试性质的内容,该级别日志主要用于在开发、测试阶段输出
info
- 系统启动初始化时加载的自定义配置信息
- 调用第三方服务的出参以及入参
- 后台定时任务的执行情况
这条日志会被频繁打印或者大部分时间对于纠错起不到作用,就应当考虑下调为DEBUG级别。
warn
- 一些不影响主流程的异常,有补偿机制的模块中的异常,可以使用此级别来记录 如: 调用发短信服务,如果失败了有重试机制或者有其他通知方式,出现异常可以使用此级别
- 特殊的条件分支 如: 一些参数本不应该为空但是判断条件为空而执行的分支等,建议使用WARN级别日志输出
error
一般来说,WARN级别不会短信报警,ERROR级别则会短信报警甚至电话报警,ERROR级别的日志意味着系统中发生了非常严重的问题,必须有人马上处理,比如数据库不可用,系统的关键业务流程走不下去等等。 ERROR日志目标:给处理者直接准确的信息,error信息能够明确告知发生了什么问题,哪些功能受影响,如何解决或者找什么人解决等。如果只是if逻辑之类的,不要走error级别
模版:
log.error(“[接口名或操作名] [Some Error Msg] happens. [Probably Because]. [Probably need to do] [params] .”);
log.error(“[接口名或操作名] [Some Error Msg] happens. [Probably Because]. [please contact xx@xxx] [params] .”);
四、日志落盘规范
- 对日志输出进行分类,便于开发人员查看,也便于通过日志对系统进行及时监控
- 目前日志会在ES中存储1周。
- 统一日志输出格式,如果有特殊配置则需要评审
11:44:44.827 WARN [93ef3E0120160803114444] [main] [ClassPathXmlApplicationContext] Exception encountered during context initialization - cancelling refresh attempt
- 强制输出:
- 时间
- 执行线程
- 日志级别
- 执行类名
- 执行类行号
- 输出内容
- 推荐输出
- traceId,轨迹Id,用于串联一次完整的请求执行路径
- userId,用户Id,用于作业系统标识某位业务操作人员执行的请求
其他
-
打印日志前三问
- 是否真的需要打印这行日志? // 避免真正需要的日志被淹没
- 输出的日志信息要素真的能帮助定位问题吗?// 信息要素是否全
- 日志的级别设置的合理吗?// 避免小题大做