背景
這兩天遇到一個比較有意思的日志問題.
近期對我之前的python代碼進行java的重構, 一方面是因為java使用的醫療庫非常健全穩定, 可以商用. 另一方面是因為java速度快, 這個庫的實現的效率也高, 性能是python版本的好幾倍.
但是作為這個項目的唯一作者, 我的癖好也成為這個項目的風格. 這個項目會給很多部署工程師使用. 當然項目的可用性和性能作為第一考慮的因素, 但是作為一個懶人, 對使用軟件時候的復雜部署過程和混亂調試信息深惡痛絕. 所以我在項目中使用了高度可配置/易用和多文件日志.
說道多文件日志, 它的優點是每個日志只容納自身的邏輯, 所以對于一般的入門開發者或者是初級運維工程師查看起來非常方便.
初步嘗試
因為spring boot的配置一般來講是application.properties, 但是同時開發者可以使用yml格式的配置, 二者相比, yml文件更為簡潔. 熟讀python之禪的我當然是簡潔勝于冗余選擇了yml.
發現spring-boot可以通過application.yml配置日志. 高興的配置一番之后發現沒法配置多個logger, 棄用! 改用logback-spring.xml(為什么不用logback.xml? 因為-spring這種文件可以獲取到spring配置中的變量.下面再說)
第一次實現
我有好幾個服務需要打日志. 一般來講我的日志風格是 *.log 保存 info以上級別日志. *.err.log保存error以上級別日志. 我如果每個文件日志都使用一個appender的話, 配置文件太長了. 而且很難看, 不是我的風格.
google了一下, 發現了這種方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
<?xml version= "1.0" encoding= "utf-8" ?> <configuration> # 下面這一行的意思是使用application.yml中的global.log-dir變量 <springproperty scope= "context" name= "log_dir" source= "global.log-dir" defaultvalue= "./log/" /> <!-- 追加器開始 --> # 這個是一個可以定義變量的appender <appender name= "sift" class = "ch.qos.logback.classic.sift.siftingappender" > # 使用 loggernamebaseddiscriminator 這個類根據當前logger獲取變量 <discriminator class = "com.utils.loggers.loggernamebaseddiscriminator" > <defaultvalue>general</defaultvalue> </discriminator> <sift> # 根據變量loggername名字生成根據日期滾動的appender <appender name= "file-${loggername}" class = "ch.qos.logback.core.rolling.rollingfileappender" > <file> ${log_dir}/${loggername}.log </file> <rollingpolicy class = "ch.qos.logback.core.rolling.timebasedrollingpolicy" > <filenamepattern> ${log_dir}/${loggername}.%d{yyyy-mm-dd}.log.gz </filenamepattern> <maxhistory> 15 </maxhistory> </rollingpolicy> <filter class = "ch.qos.logback.classic.filter.thresholdfilter" > <level>info</level> </filter> <encoder> <pattern>%d{hh:mm:ss.sss} %-5level - %msg%n </pattern> </encoder> </appender> <appender name= "file-error-${loggername}" class = "ch.qos.logback.core.rolling.rollingfileappender" > <file>${log_dir}/${loggername}.err.log</file> <rollingpolicy class = "ch.qos.logback.core.rolling.timebasedrollingpolicy" > <filenamepattern> ${log_dir}/${loggername}.%d{yyyy-mm-dd}.err.log.gz </filenamepattern> <maxhistory> 15 </maxhistory> </rollingpolicy> <filter class = "ch.qos.logback.classic.filter.thresholdfilter" > <level>error</level> </filter> <encoder> <pattern>%d{hh:mm:ss.sss} %-5level - %msg%n </pattern> </encoder> </appender> </sift> </appender> <!-- 追加器結束 --> <!-- 日志開始 --> <logger name= "com.some.service" level= "info" additivity= "false" > <appender-ref ref= "sift" /> </logger> <!-- 日志結束 --> </configuration> |
下的是對應的 loggernamebaseddiscriminator 類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
package com.utils.loggers; import ch.qos.logback.classic.spi.iloggingevent; import ch.qos.logback.core.sift.abstractdiscriminator; public class loggernamebaseddiscriminator extends abstractdiscriminator<iloggingevent> { private static final string key = "loggername" ; private string defaultvalue; public string getdefaultvalue() { return defaultvalue; } public void setdefaultvalue(string defaultvalue) { this .defaultvalue = defaultvalue; } # 這就是之所以xml里面可以引用loggername變量的原因 public string getkey() { return key; } public void setkey() { throw new unsupportedoperationexception( "key not settable. using " + key); } public string getdiscriminatingvalue(iloggingevent e) { string loggername = e.getloggername(); if (loggername == null ) return defaultvalue; else { string[] split = loggername.split( "\\." ); return split[split.length - 1 ]; } } } |
最開始我的日志里面沒有報錯信息, 正常的生成info日志. 但是后來發現事情好像不是想象的那樣
問題出現
后來我把程序改成多線程. 發現所有涉及到多線程的服務日志里面都沒信息了. google半天, 發現幾個令我震驚的真相:
- 真相1: 所有滾動appender都不支持異步追加 (其實也不是, 但是那種方式需要寫死日志文件名, 不推薦, 不講)
- 真相2: siftingappender 內部最多嵌套一個appender. 所以理論上我的error的日志里面應該永遠不會有內容.
問題解決
對于之前的兩個問題, 分而治之.
不支持異步
再次谷歌(到這里讀者基本上發現了我搬磚的本質), 發現有個appender名字叫asyncappender, 這玩意是一個其他appender的wrapper. 說白了, 就是你打日志的命令是異步的, 放到隊列里面, 而它真正的打日志的動作是一個單獨的同步線程. 這就牛逼了, 使用這玩意收集我所有日志, 然后再轉發給siftingappender 進行分發即可.
siftingappender 內部最多嵌套一個appender
這個好辦, 把info的appender和error的appender拆開放到兩個siftingappender里面就行了, 不過這樣的話, 前面提到的的asyncappender 也要寫兩個.
最后, logback-spring.xml文件如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
<?xml version= "1.0" encoding= "utf-8" ?> <configuration> <springproperty scope= "context" name= "log_dir" source= "global.log-dir" defaultvalue= "./log/" /> <!-- 追加器開始 --> <appender name= "sift" class = "ch.qos.logback.classic.sift.siftingappender" > <discriminator class = "com.utils.loggers.loggernamebaseddiscriminator" > </discriminator> <sift> <appender name= "file-${loggername}" class = "ch.qos.logback.core.rolling.rollingfileappender" > <file>${log_dir}/${loggername}.log</file> <rollingpolicy class = "ch.qos.logback.core.rolling.timebasedrollingpolicy" > <filenamepattern> ${log_dir}/${loggername}.%d{yyyy-mm-dd}.log </filenamepattern> <maxhistory> 15 </maxhistory> </rollingpolicy> <filter class = "ch.qos.logback.classic.filter.thresholdfilter" > <level>info</level> </filter> <encoder> <pattern>%d{hh:mm:ss.sss} %-5level - %msg%n </pattern> </encoder> </appender> </sift> </appender> <appender name= "sift-err" class = "ch.qos.logback.classic.sift.siftingappender" > <discriminator class = "com.infervision.utils.loggers.loggernamebaseddiscriminator" > </discriminator> <sift> <appender name= "file-error-${loggername}" class = "ch.qos.logback.core.rolling.rollingfileappender" > <file>${log_dir}/${loggername}.err.log</file> <rollingpolicy class = "ch.qos.logback.core.rolling.timebasedrollingpolicy" > <filenamepattern> ${log_dir}/${loggername}.%d{yyyy-mm-dd}.err.log </filenamepattern> <maxhistory> 15 </maxhistory> <totalsizecap>50mb</totalsizecap> </rollingpolicy> <filter class = "ch.qos.logback.classic.filter.thresholdfilter" > <level>error</level> </filter> <encoder> <pattern>%d{hh:mm:ss.sss} %-5level - %msg%n </pattern> </encoder> </appender> </sift> </appender> <!-- 異步輸出 --> <appender name = "async" class = "ch.qos.logback.classic.asyncappender" > <!-- 不丟失日志.默認的,如果隊列的 80 %已滿,則會丟棄tract、debug、info級別的日志 --> <discardingthreshold > 0 </discardingthreshold> <!-- 更改默認的隊列的深度,該值會影響性能.默認值為 256 --> <queuesize> 512 </queuesize> <!-- 添加附加的appender,最多只能添加一個 --> <appender-ref ref = "sift" /> </appender> <!-- 異步輸出 --> <appender name = "async-err" class = "ch.qos.logback.classic.asyncappender" > <!-- 不丟失日志.默認的,如果隊列的 80 %已滿,則會丟棄tract、debug、info級別的日志 --> <discardingthreshold > 0 </discardingthreshold> <!-- 更改默認的隊列的深度,該值會影響性能.默認值為 256 --> <queuesize> 512 </queuesize> <!-- 添加附加的appender,最多只能添加一個 --> <appender-ref ref = "sift-err" /> </appender> <!-- 追加器結束 --> <!-- 日志開始 --> <logger name= "com.some.service" level= "info" additivity= "false" > <appender-ref ref= "async" /> </logger> <!-- 日志結束 --> </configuration> |
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://www.jianshu.com/p/9493855ce4cc