1.什麼是日誌?
日誌是記錄程序運行的軌跡,方便查找信息,快速定位問題。日誌應用主要有下面三個原因:記錄操作軌跡、監控系統運行狀況、回溯系統故障。在開發時可以使用debug來跟蹤代碼,真正代碼發佈到了DAT 生產環境是沒辦法隨便使用遠程調試的。因此如果日誌打的好,線上的問題很快便能定位,反之用不好則影響系統性能。
2.如何引入日誌?
常用日誌框架及區別
在使用日誌前先來認識下常用的日誌框架:
log4j、log4j2、Logging、commons-logging、slf4j、logback等,那麼它們之間有什麼關係,又該如何選擇呢?
日誌框架分爲三大部分:日誌門面、日誌適配器、日誌庫。
- 門面設計模式是面向對象設計模式中的一種,日誌框架採用的就是這種模式,類似jdbc的概念,既它只是一套設計規範,並不提供具體的實現。目的使使用者無論使用什麼日誌框架都無需關心其具體具體是哪個庫負責打印。目前主流使用的框架有兩種**:slf4j、commons-logging。**
- 日誌庫具體實現了日誌的相關功能,主流日誌庫有:log4j、log4j2、log-jdk、logback。
- 日誌適配器是針對沒有實現門面模式接口的日誌框架想使用類似於self4j這種門面模式框架時,需要單獨引入一個適配器來解決接口不兼容的問題。或者在老的工程中,使用了多種日誌框架,隨着時間的推移,想將原來直接調用日誌庫的模式改成門面模式,此時可使用適配器來完成舊API到slf4j的路由。
日誌級別詳解
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Nzh8PY2g-1572703274878)(https://i.loli.net/2019/10/28/ZwWKcU7hvtxRGg2.png)]
所以,日誌優先級別標準順序爲:
ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF
使用方式
以springboot中的slf4j+logback爲例
Spring boot默認支持的就是slf4j+logback的日誌框架,想要定製日誌策略只需要添加配置文件即可。spring推薦配置文件以-spring命名。此處命名爲logback-spring.xml。
基本配置:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日誌存放路徑 -->
<property name="log.path" value="./logs" />
<!-- 日誌輸出格式 -->
<property name="log.pattern" value=" %contextName %d{HH:mm:ss.SSS} [logid=%logid] [ip=%ip] [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<conversionRule conversionWord="ip" converterClass="io.renren.IPLogConfig" />
<conversionRule conversionWord="logid" converterClass="io.renren.LogIdConfig" />
<contextName>myLogBack</contextName>
<!-- 控制檯輸出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系統日誌輸出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循環政策:基於時間創建日誌文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日誌文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日誌最大的歷史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 過濾的級別 -->
<level>INFO</level>
<!-- 匹配時的操作:接收(記錄) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配時的操作:拒絕(不記錄) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循環政策:基於時間創建日誌文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日誌文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日誌最大的歷史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 過濾的級別 -->
<level>ERROR</level>
<!-- 匹配時的操作:接收(記錄) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配時的操作:拒絕(不記錄) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 用戶訪問日誌輸出 -->
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-user.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滾 daily -->
<fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日誌最大的歷史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- sql 輸出配置 -->
<appender name="log.dao" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/log-dao.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滾 daily -->
<fileNamePattern>${log.path}/log-dao.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日誌最大的歷史 2天 -->
<maxHistory>2</maxHistory>
<!-- 日誌最大文件大小10m -->
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系統模塊日誌級別控制 -->
<logger name="io.renren.config" level="info" />
<!-- Spring日誌級別控制 -->
<logger name="org.springframework" level="warn" />
<!--系統操作日誌-->
<root level="info">
<appender-ref ref="console" />
</root>
<!--系統用戶操作日誌-->
<logger name="sys-user" level="info">
<appender-ref ref="sys-user"/>
</logger>
<!--sql日誌-->
<logger name="io.renren.dao" level="DEBUG">
<appender-ref ref="log.dao"/>
</logger>
</configuration>
- contextName:設置上下文名稱,用來區分不同進程
- property 兩個屬性name 和value,name是變量名稱,定義後可使用${}來使用。
- appender格式化日誌輸出節點,有倆個屬性name和class,class用來指定哪種輸出策略,常用就是控制檯輸出策略和文件輸出策略。(控制檯輸出ConsoleAppender、輸出到文件RollingFileAppender)設置文件大小限制和保留天數時衝突解決方案:https://blog.csdn.net/wujianmin577/article/details/68922545
- encoder 表示日誌編碼:%d{HH: mm:ss.SSS}輸出時間、%thread——輸出日誌的進程名字、%-5level——日誌級別,並且使用5個字符靠左對齊、%msg——日誌消息、%n——平臺的換行符
- logger 設置某一個包或者具體的某一個類的日誌打印級別、以及指定。僅有一個name屬性,一個可選的level和一個可選的addtivity屬性。addtivity :是否向上級logger傳遞打印信息。默認是true
- conversionRule 可配置自定義class來輸出到日誌中.
3.使用時需要注意什麼?
1.日誌命名的規範
推薦日誌的命名方式爲:appName_logType_logName.log,logType是日誌類型,推薦分類有stats、monitor、visit等;logName爲日誌描述。這種命名方式的好處是通過文件名就可以知道日誌文件屬於什麼應用,什麼類型。
2.日誌的存儲週期
在EasyCoding中推薦將日誌文件至少保存15天,防止以周爲頻率發生的異常無法被發現。然後根據日誌文件的重要程度,文件大小,磁盤空間來自行延長保存時間。
3.使用時注意
在使用時爲了防止產生無用的字符串對象,建議使用條件判斷 + 佔位符形式來輸出日誌。例:
//使用條件判斷形式
if(myLogger.isInfoEnabled()){
//使用佔位符形式
myLogger.info("This logInfo id:{},and value:{}","木大木大木大","阿嘟嘟嘟嘟");
}
4.避免無效的日誌輸出
因爲日誌打印的順序是由低到高先子級再父級,包名級別越高則logger的級別越高。在執行完子級日誌後執行父級日誌會重複輸出,此時應在子級配置中設置additivity=“false”,例:
<!-- 系統模塊日誌級別控制 -->
<logger name="io.renren.config" level="info" additivity="false"/>
5.使用異步寫入日誌AsyncAppender
一般程序使用串行狀態,要記錄日誌時必然會阻塞到程序的運行,這是一種損耗效率的方式,所以實際使用日誌時使用異步的方式來記錄,這樣日誌同主程序就會形成並行狀態,不會影響程序運行。
AsyncAppender並不處理日誌,只是將日誌緩衝到一個BlockingQueue裏面去,並在內部創建一個工作線程從隊列頭部獲取日誌,之後將獲取的日誌循環記錄到附加的其他appender上去,從而達到不阻塞主線程的效果。因此AsynAppender僅僅充當事件轉發器,必須引用另一個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 ="FILE_LOG"/>
</appender>
6.根據環境不同使用不同的日誌策略
<!-- 開發環境輸出到控制檯 -->
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</springProfile>
<!-- 生產環境輸出到文件 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="FILE_LOG" />
</root>
</springProfile>
在root節點外面套了一層springProfile ,指定了name屬性的值,此時啓用時有兩種方式.
方式1:執行jar包時添加參數:
java -jar xxx.jar --spring.profiles.active=prod
方式2:在項目的application.properties配置文件中添加:
spring.profiles.active=prod
4.整合
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--定義一個將日誌輸出到控制檯的appender,名稱爲STDOUT -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%contextName]%date [%thread %line] %level >> %msg >> %logger{10}%n</pattern>
</encoder>
</appender>
<!--定義一個將日誌輸出到文件的appender,名稱爲FILE_LOG -->
<appender name="FILE_LOG" class="ch.qos.logback.core.FileAppender">
<file>D:/test.log</file>
<append>true</append>
<encoder>
<pattern>[Eran]%date [%thread %line] %level >> %msg >> %logger{10}%n</pattern>
</encoder>
</appender>
<!-- 按時間滾動產生日誌文件 -->
<appender name="ROL-FILE-LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--滾動策略,按照時間滾動 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>D:/logs/test.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 只保留近七天的日誌 -->
<maxHistory>7</maxHistory>
<!-- 用來指定日誌文件的上限大小,那麼到了這個值,就會刪除舊的日誌 -->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>[Eran]%date [%thread %line] %level >> %msg >> %logger{10}%n</pattern>
</encoder>
</appender>
<!-- 按時間和文件大小滾動產生日誌文件 -->
<appender name="ROL-SIZE-FILE-LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>D:/logs/test.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 單個文件的最大內存 -->
<maxFileSize>100MB</maxFileSize>
<!-- 只保留近七天的日誌 -->
<maxHistory>7</maxHistory>
<!-- 用來指定日誌文件的上限大小,那麼到了這個值,就會刪除舊的日誌 -->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>[Eran]%date [%thread %line] %level >> %msg >> %logger{10}%n</pattern>
</encoder>
<!-- 只處理INFO級別以及之上的日誌 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<!-- 只處理INFO級別的日誌 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</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 ="FILE_LOG"/>
</appender>
<!-- 指定com.demo包下的日誌打印級別爲DEBUG,但是由於沒有引用appender,所以該logger不會打印日誌信息,日誌信息向上傳遞 -->
<logger name="com.example" level="DEBUG"></logger>
<!-- 指定開發環境基礎的日誌輸出級別爲DEBUG,並且綁定了名爲STDOUT的appender,表示將日誌信息輸出到控制檯 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</springProfile>
<!-- 指定生產環境基礎的日誌輸出級別爲INFO,並且綁定了名爲ASYNC的appender,表示將日誌信息異步輸出到文件 -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="ASYNC" />
</root>
</springProfile>
</configuration>
參考資源:
https://juejin.im/post/5c7e2445f265da2de71370f2
http://tengj.top/2017/04/05/springboot7/#%E5%A4%9A%E7%8E%AF%E5%A2%83%E6%97%A5%E5%BF%97%E8%BE%93%E5%87%BA
https://blog.csdn.net/wujianmin577/article/details/68922545
http://tengj.top/2017/02/28/springboot2/