基本要素
一行日志的基本要素必须包含时间戳, 日志等级,traceId,线程名称,日志所在类(logger名称),日志内容 等要素。
特别的,对于接口层日志,还应该包含 请求URL。
对于 日志内容, 必须包含:
- “中文问题或操作描述”,(强烈建议中文,一是一眼看明白,二是中文很容易在一堆框架日志中凸显出来)。
- 中文描述前使用 ’[]’用一两个词表示这一组日志是在做什么功能的(或本次调用是做什么功能的)。
- 所有需要登录的请求,必须打印用户id(RequestInfoUtils)。
- 文档id(课程id),空间id,以及其他所有正在操作的实体id。 若有,则必须打印(不用特别只为日志调接口获取)。
- 远程调用时的日志,必须打印参数。
对于对象打印:
建议直接使用log格式, 直接把对象传给logger即可,不需要手动toJSON。
普通日志格式和json格式,都能被常见的日志框架直接解析。
常见日志场景
- controller层参数和返回值(强制): “请求进入”和“请求返回”必须打印日志(common实现)。
- service层参数和返回值(建议):如无必要,不需要打印“请求进入”和“请求返回”。
rpc调用前后的日志(优先):rpc调用前后打印调用参数和返回值,但对于下方有异常校验,返回值校验的请求可以不打印。
well case:1
2
3
4
5
6
7
8
9InnerRequest<DocParam> request = new InnerRequest<>();
log.info("[点赞]请求doc-server获取文档内容-request:{}", request.getData());
DocResult result = InnerResponseUtils.get(docClient.queryDoc(request));
// 因为下方校验了返回值,且在出现错误时会打印错误日志,所以此处不需要打印返回结果。
if (result == null || StringUtils.isBlank(result.getContentJson()) {
log.error("[点赞]请求doc-server获取文档内容失败,返回值为空或者内容为空-result:{}", result)
throw new GlobalException("xxx");
}参数校验和结果校验(强制):所有校验不通过提前结束流程的分支,必须打印日志,且需要包含问题描述和具体参数或返回值。
badcase:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23private class DocParam {
// 验证注解不写明错误消息
private Long docId;
}
......
if (param.getCreateTime() < someDate) {
// 提前中断没有日志
return null;
}
Doc doc = doSomething(param.getCreateTime());
if (doc == null) {
// 提前中断没有日志
return new Doc();
}
if (StringUtils.isBlank(doc.getDocContentJson()) {
// 没有打印方法入参
log.error("[发布评论]查询文档内容为空");
// 没有说清楚具体错误
log.error("[发布评论]查询文档报错了-param:{}", param);
throw new GlobalException("xxx");
}mq消息的发送与接收(强制):mq的发送必须打印完整的消息体。 mq的consume必须打印完整的消息体。
- 异常捕获(强制):异常捕获必须打印日志,必须有错误描述,必须打印当前方法的入参。除非是checked的异常,否则都应该打印异常栈。
badcase:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24try {
doSomething();
} catchException e) {
} // 啥也不干
try {
doSomething();
} catch (Exception e) {
doSomethingOthers();
} // 不打日志
try {
doSomething();
} catch (Exception e) {
doSomethingOthers();
// 完全没用
log.error("[some]出错了");
// 没有详细信息
log.error("[some]doSomething出错了");
// 没有打印异常, 排错困难
log.error("[some]doSomething出错了-param:{}", param);
// 异常被toString, 没有堆栈
log.error("[some]doSomething出错了-param:{}, exception:{}", param, e);
}
- 循环体(强制):循环体不要打印info级别的日志,其他规则遵循1-6。
badcase:1
2
3
4
5
6for (Doc doc : list) {
log.info("[文档列表]开始处理-id:{}", doc.getId()); // debug或者脚本进度条有用,其他情况就别了。
boolean result = doSomething();
log.info("[文档列表]处理结束-id:{}, result:{}", doc.getId(), result);
......
}
well case:
1 | log.info("[文档列表]开始处理文档列表"); // 批量参数可以没有, 通过trace能看到前面的调用。 |