Skip to content

Commit 4643048

Browse files
authored
Add files via upload
1 parent 1b8d131 commit 4643048

File tree

1 file changed

+269
-0
lines changed

1 file changed

+269
-0
lines changed

工作总结/日志打印规范.md

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
## 前言
2+
3+
大家好,我是**捡田螺的小男孩**。日志是快速定位问题的好帮手,是**撕逼和甩锅**的利器!打印好日志非常重要。今天我们来聊聊**日志打印**的15个好建议~
4+
5+
- 公众号:**捡田螺的小男孩**
6+
7+
8+
## 1. 选择恰当的日志级别
9+
10+
常见的日志级别有5种,分别是error、warn、info、debug、trace。日常开发中,我们需要选择恰当的日志级别,不要反手就是打印info哈~
11+
12+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c4502943568747e79ac4091b4d2868ea~tplv-k3u1fbpfcp-zoom-1.image)
13+
14+
- error:错误日志,指比较严重的错误,对正常业务有影响,需要**运维配置监控的**
15+
- warn:警告日志,一般的错误,对业务影响不大,但是需要**开发关注**
16+
- info:信息日志,记录排查问题的关键信息,如调用时间、出参入参等等;
17+
- debug:用于开发DEBUG的,关键逻辑里面的运行时数据;
18+
- trace:最详细的信息,一般这些信息只记录到日志文件中。
19+
20+
21+
## 2. 日志要打印出方法的入参、出参
22+
23+
我们并不需要打印很多很多日志,只需要打印可以**快速定位问题的有效日志**。有效的日志,是甩锅的利器!
24+
25+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c59f66bfc09d42ffa8528c16145952b8~tplv-k3u1fbpfcp-zoom-1.image)
26+
27+
哪些算得的上**有效关键**的日志呢?比如说,方法进来的时候,打印**入参**。再然后呢,在方法返回的时候,就是**打印出参,返回值**。入参的话,一般就是**userId或者bizSeq这些关键**信息。正例如下:
28+
29+
```
30+
public String testLogMethod(Document doc, Mode mode){
31+
log.debug(“method enter param:{}”,userId);
32+
String id = "666";
33+
log.debug(“method exit param:{}”,id);
34+
return id;
35+
}
36+
```
37+
38+
39+
## 3. 选择合适的日志格式
40+
41+
理想的日志格式,应当包括这些最基本的信息:如当**前时间戳**(一般毫秒精确度)、**日志级别****线程名字**等等。在logback日志里可以这么配置:
42+
43+
```
44+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
45+
<encoder>
46+
<pattern>%d{HH:mm:ss.SSS} %-5level [%thread][%logger{0}] %m%n</pattern>
47+
</encoder>
48+
</appender>
49+
```
50+
51+
如果我们的日志格式,连当前时间都沒有记录,那**连请求的时间点都不知道了**
52+
53+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6ecfebbe1a0b411e8186d46edaddbd38~tplv-k3u1fbpfcp-zoom-1.image)
54+
55+
56+
## 4. 遇到if...else...等条件时,每个分支首行都尽量打印日志
57+
58+
当你碰到**if...else...或者switch**这样的条件时,可以在分支的首行就打印日志,这样排查问题时,就可以通过日志,确定进入了哪个分支,代码逻辑更清晰,也更方便排查问题了。
59+
60+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/60bc12dfb6324c089b86a9dc05acc2f3~tplv-k3u1fbpfcp-zoom-1.image)
61+
62+
63+
正例:
64+
```
65+
if(user.isVip()){
66+
log.info("该用户是会员,Id:{},开始处理会员逻辑",user,getUserId());
67+
//会员逻辑
68+
}else{
69+
log.info("该用户是非会员,Id:{},开始处理非会员逻辑",user,getUserId())
70+
//非会员逻辑
71+
}
72+
```
73+
74+
## 5.日志级别比较低时,进行日志开关判断
75+
76+
对于trace/debug这些比较低的日志级别,必须进行日志级别的开关判断。
77+
78+
正例:
79+
```
80+
User user = new User(666L, "公众号", "捡田螺的小男孩");
81+
if (log.isDebugEnabled()) {
82+
log.debug("userId is: {}", user.getId());
83+
}
84+
```
85+
86+
因为当前有如下的日志代码:
87+
```
88+
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
89+
```
90+
91+
如果**配置的日志级别是warn**的话,上述日志不会打印,但是会执行字符串拼接操作,如果```symbol```是对象,
92+
还会执行```toString()```方法,浪费了系统资源,执行了上述操作,最终日志却没有打印,因此建议**加日志开关判断。**
93+
94+
## 6. 不能直接使用日志系统(Log4j、Logback)中的 API,而是使用日志框架SLF4J中的API。
95+
96+
SLF4J 是门面模式的日志框架,有利于维护和各个类的日志处理方式统一,并且可以在保证不修改代码的情况下,很方便的实现底层日志框架的更换。
97+
98+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9846b8d9ddd2485483e41b7134954f91~tplv-k3u1fbpfcp-zoom-1.image)
99+
100+
正例:
101+
```
102+
import org.slf4j.Logger;
103+
import org.slf4j.LoggerFactory;
104+
105+
private static final Logger logger = LoggerFactory.getLogger(TianLuoBoy.class);
106+
```
107+
108+
## 7. 建议使用参数占位{},而不是用+拼接。
109+
110+
反例:
111+
```
112+
logger.info("Processing trade with id: " + id + " and symbol: " + symbol);
113+
```
114+
115+
上面的例子中,使用```+```操作符进行字符串的拼接,有一定的**性能损耗**
116+
117+
正例如下:
118+
```
119+
logger.info("Processing trade with id: {} and symbol : {} ", id, symbol);
120+
```
121+
我们使用了大括号```{}```来作为日志中的占位符,比于使用```+```操作符,更加优雅简洁。并且,**相对于反例**,使用占位符仅是替换动作,可以有效提升性能。
122+
123+
## 8. 建议使用异步的方式来输出日志。
124+
125+
- 日志最终会输出到文件或者其它输出流中的,IO性能会有要求的。如果异步,就可以显著提升IO性能。
126+
- 除非有特殊要求,要不然建议使用异步的方式来输出日志。以logback为例吧,要配置异步很简单,使用AsyncAppender就行
127+
```
128+
<appender name="FILE_ASYNC" class="ch.qos.logback.classic.AsyncAppender">
129+
<appender-ref ref="ASYNC"/>
130+
</appender>
131+
```
132+
133+
## 9. 不要使用e.printStackTrace()
134+
135+
136+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e2aeaca5e0f44c08ad25fb92e75222dc~tplv-k3u1fbpfcp-zoom-1.image)
137+
138+
139+
反例:
140+
```
141+
try{
142+
// 业务代码处理
143+
}catch(Exception e){
144+
e.printStackTrace();
145+
}
146+
```
147+
正例:
148+
```
149+
try{
150+
// 业务代码处理
151+
}catch(Exception e){
152+
log.error("你的程序有异常啦",e);
153+
}
154+
```
155+
156+
**理由:**
157+
158+
- e.printStackTrace()打印出的堆栈日志跟业务代码日志是交错混合在一起的,通常排查异常日志不太方便。
159+
- e.printStackTrace()语句产生的字符串记录的是堆栈信息,如果信息太长太多,字符串常量池所在的内存块没有空间了,即内存满了,那么,用户的请求就卡住啦~
160+
161+
## 10. 异常日志不要只打一半,要输出全部错误信息
162+
163+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/420f0da65bac44b5b340a96facfd5c11~tplv-k3u1fbpfcp-zoom-1.image)
164+
165+
反例1:
166+
167+
```
168+
try {
169+
//业务代码处理
170+
} catch (Exception e) {
171+
// 错误
172+
LOG.error('你的程序有异常啦');
173+
}
174+
175+
```
176+
- 异常e都没有打印出来,所以压根不知道出了什么类型的异常。
177+
178+
反例2:
179+
```
180+
try {
181+
//业务代码处理
182+
} catch (Exception e) {
183+
// 错误
184+
LOG.error('你的程序有异常啦', e.getMessage());
185+
}
186+
```
187+
188+
- ```e.getMessage()```不会记录详细的堆栈异常信息,只会记录错误基本描述信息,不利于排查问题。
189+
190+
正例:
191+
192+
```
193+
try {
194+
//业务代码处理
195+
} catch (Exception e) {
196+
// 错误
197+
LOG.error('你的程序有异常啦', e);
198+
}
199+
```
200+
201+
## 11. 禁止在线上环境开启 debug
202+
203+
禁止在线上环境开启debug,这一点非常重要。
204+
205+
206+
因为一般系统的debug日志会很多,并且各种框架中也大量使用 debug的日志,线上开启debug不久可能会打满磁盘,影响业务系统的正常运行。
207+
208+
## 12.不要记录了异常,又抛出异常
209+
210+
211+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/671289ecaa4b4eb39dca1139b657f8cd~tplv-k3u1fbpfcp-zoom-1.image)
212+
213+
214+
反例如下:
215+
```
216+
log.error("IO exception", e);
217+
throw new MyException(e);
218+
```
219+
220+
- 这样实现的话,通常会把栈信息打印两次。这是因为捕获了MyException异常的地方,还会再打印一次。
221+
- 这样的日志记录,或者包装后再抛出去,不要同时使用!否则你的日志看起来会让人很迷惑。
222+
223+
224+
## 13.避免重复打印日志
225+
226+
避免重复打印日志,酱紫会浪费磁盘空间。如果你已经有一行日志清楚表达了意思,**避免再冗余打印**,反例如下:
227+
228+
```
229+
if(user.isVip()){
230+
log.info("该用户是会员,Id:{}",user,getUserId());
231+
//冗余,可以跟前面的日志合并一起
232+
log.info("开始处理会员逻辑,id:{}",user,getUserId());
233+
//会员逻辑
234+
}else{
235+
//非会员逻辑
236+
}
237+
```
238+
239+
如果你是使用log4j日志框架,务必在```log4j.xml```中设置 additivity=false,因为可以避免重复打印日志
240+
241+
正例:
242+
```
243+
<logger name="com.taobao.dubbo.config" additivity="false">
244+
```
245+
246+
## 14.日志文件分离
247+
248+
249+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/12a2cbe6cf7f4ba981ba7103f9d81858~tplv-k3u1fbpfcp-zoom-1.image)
250+
251+
252+
- 我们可以把不同类型的日志分离出去,比如access.log,或者error级别error.log,都可以单独打印到一个文件里面。
253+
- 当然,也可以根据不同的业务模块,打印到不同的日志文件里,这样我们排查问题和做数据统计的时候,都会比较方便啦。
254+
255+
256+
## 15. 核心功能模块,建议打印较完整的日志
257+
258+
259+
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6f26259cda042edb98400145d208d12~tplv-k3u1fbpfcp-zoom-1.image)
260+
261+
262+
- 我们日常开发中,如果核心或者逻辑复杂的代码,建议添加详细的注释,以及较详细的日志。
263+
- 日志要多详细呢?脑洞一下,如果你的核心程序哪一步出错了,通过日志可以定位到,那就可以啦。
264+
265+
266+
267+
268+
269+

0 commit comments

Comments
 (0)