標籤:val 關聯 官網 access learn 部落格 manual 關係 連網
前言
前面兩章節我們介紹了一些日誌架構的常見配置及使用實踐。一般上,在開發過程中,像log4j2
、logback
日誌架構都提供了很多Appender
,基本上可以滿足大部分的業務需求了。但在一些特殊需求或者需要將日誌進行集中管理(叢集部署時,日誌是分拆到不同伺服器上的,不可能去每一台伺服器上去下載檔案的,也不便於日誌檢索)時,就需要自訂Appender
,將日誌集中輸出或者其他一些特殊需求。所以本章節就來簡單介紹下關於log4j2
和logback
的自訂Appender
知識。
- 一點知識
- log4j2內建Appender
- logback內建Appender
- 自訂Appender
- log4j2自訂Appender
- logback自訂Appender
- 關於ShutdownHook
- 參考資料
- 總結
- 最後
- 老生常談
一點知識
編寫自訂Appender
時,我們先來看看log4j2
和logback
內建了哪些Appender
,瞭解下是否可以滿足我們的個人化需求,避免重複製造輪子。
log4j2內建Appender
先看一張官網提供的Appender
說明:
名稱 |
描述 |
AsyncAppender |
使用一個單獨線程記錄日誌,實現非同步處理日誌事件。 |
CassandraAppender |
將日誌資訊輸出到一個Apache的Cassandra資料庫 |
ConsoleAppender |
將日誌資訊輸出到控制台 |
FailoverAppender |
包含其他appenders,按順序嘗試,直至成功或結尾 |
FileAppender |
一個OutputStreamAppender,將日誌輸出到檔案 |
FlumeAppender |
將日誌輸出到Apache Flume系統 |
JDBCAppender |
將日誌通過JDBC輸出到關係型資料庫 |
JMS Appender |
將日誌輸出到JMS(Java Message Service) |
JPAAppender |
將日誌輸出到JPA架構 |
HttpAppender |
通過HTTP輸出日誌 |
KafkaAppender |
將日誌輸出到Apache Kafka |
MemoryMappedFileAppender |
將日誌輸出到一塊檔案關聯的記憶體 |
OutputStreamAppender |
將日誌輸出到一個OutputStream |
RandomAccessFileAppender |
效能比FileAppender高20%~200%的檔案輸出Appender |
RewriteAppender |
允許對日誌資訊進行加工 |
RollingFileAppender |
按log檔案最大長度限度產生新檔案 |
RollingRandomAccessFA |
添加了緩衝的RollingFileAppender |
RoutingAppender |
將日誌事件分類,按條件分配給子appender |
SMTPAppender |
將日誌輸出到郵件 |
SocketAppender |
將日誌輸出到一個Socket |
SyslogAppender |
是一個SocketAppender,將日誌輸出到遠程系統日誌 |
ZeroMQ/JeroMQ Appender |
使用JeroMQ庫將日誌輸出到ZeroMQ終端 |
基本上已經覆蓋了百分之九十的業務情境了。相關的詳細說明或者配置大家自行搜尋或者查看官網說明。
官網地址:http://logging.apache.org/log4j/2.x/manual/appenders.html
logback內建Appender
和log4j2
一樣,內建的都差不多了。
名稱 |
描述 |
ConsoleAppender |
將日誌輸出到控制台 |
FileAppender |
將日誌輸出到檔案 |
RollingFileAppender |
滾動檔案產生,按條件產生不同檔案,配合TriggeringPolicy使用 |
SocketAppender |
輸出日誌到遠程執行個體中,明文傳輸 |
SSLSocketAppender |
輸出日誌到遠程執行個體中,密文傳輸 |
SMTPAppender |
將日誌輸出到郵件 |
DBAppender |
日誌事件插入資料庫中,需要提前建立表 |
SyslogAppender |
是一個SocketAppender,將日誌輸出到遠程系統日誌 |
SiftingAppender |
可基於任何給定的即時屬性分開(或者篩選)日誌,如基於使用者會話分開日誌事件 |
AmqpAppender |
將日誌輸出到MQ服務中 |
具體可查看:73327752 很詳細!
或者查看官網:https://logback.qos.ch/manual/appenders.html
自訂Appender
自訂Appender
時,可以按實現的功能,適當的繼承(log4j2
的appender
類基本上被設定成了final
無法繼承)或者參考一些已有的功能,當然了也可以直接繼承其基類介面的。以下就簡單的樣本下,沒有實現特定的功能,⊙﹏⊙‖∣
log4j2自訂Appender
按官網的擴充說明,我們來簡單實現一個appender。
官網地址:http://logging.apache.org/log4j/2.x/manual/extending.html#Appenders
0.編寫自訂appender類,繼承AbstractAppender
抽象實作類別:
MyLog4j2Appender.java
/** * 自訂log4j2輸出源,簡單的輸出到控制台 * @author oKong * *///這裡的 MyLog4j2 對應就是 xml中,/** * * <appenders> * <MyLog4j2 name="customAppender" printString="一枚趔趄的猿"> * </MyLog4j2> * </appenders> * */@Plugin(name = "MyLog4j2", category = "Core", elementType = "appender", printObject = true)public class MyLog4j2Appender extends AbstractAppender { String printString; /** *建構函式 可自訂參數 這裡直接傳入一個常量並輸出 * */ protected MyLog4j2Appender(String name, Filter filter, Layout<? extends Serializable> layout,String printString) { super(name, filter, layout); this.printString = printString; } @Override public void append(LogEvent event) { if (event != null && event.getMessage() != null) { // 此處自訂實現輸出 // 擷取輸出值:event.getMessage().toString() // System.out.print(event.getMessage().toString()); // 格式化輸出 System.out.print(printString + ":" + getLayout().toSerializable(event)); } } /** 接收設定檔中的參數 * * @PluginAttribute 字面意思都知道,是xml節點的attribute值,如<oKong name="oKong"></oKong> 這裡的name 就是 attribute * @PluginElement:表示xml子節點的元素, * 如 * <oKong name="oKong"> * <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> * </oKong> * 其中,PatternLayout就是 的 Layout,其實就是{@link Layout}的實作類別。 */ @PluginFactory public static MyLog4j2Appender createAppender( @PluginAttribute("name") String name, @PluginElement("Filter") final Filter filter, @PluginElement("Layout") Layout<? extends Serializable> layout, @PluginAttribute("printString") String printString) { if (name == null) { LOGGER.error("no name defined in conf."); return null; } //預設使用 PatternLayout if (layout == null) { layout = PatternLayout.createDefaultLayout(); } return new MyLog4j2Appender(name, filter, layout, printString); } @Override public void start() { System.out.println("log4j2-start方法被調用"); super.start(); } @Override public void stop() { System.out.println("log4j2-stop方法被調用"); super.stop(); }}
簡單說明下,相關注意點:
@Plugin
註解:這個註解,是為了在之後配置log4j2-spring.xml
時,指定的Appender Tag。
- 建構函式:除了使用父類的以外,也可以增加一些自己的配置。
- 重寫
append()
方法:這裡面需要實現具體的邏輯,日誌的去向。
createAppender()
方法:主要是接收log4j2-spring.xml
中的配置項。
1.使用自訂的appender。
log4j2-spring.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration status="WARN" monitorInterval="30" packages="cn.lqdev.learning"> <!--定義appenders--> <appenders> <MyLog4j2 name="oKong" printString="一枚趔趄的猿(log4j2)"> <!--輸出日誌的格式--> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> </MyLog4j2> </appenders> <!--然後定義logger,只有定義了logger並引入的appender,appender才會生效--> <loggers> <!--過濾掉spring和mybatis的一些無用的DEBUG資訊--> <logger name="org.springframework" level="INFO"></logger> <logger name="org.mybatis" level="INFO"></logger> <!-- 自訂包下設定為INFO,則可以看見輸出的日誌不包含debug輸出了 --> <logger name="cn.lqdev.learning" level="INFO"/> <root level="all"> <appender-ref ref="oKong"/> </root> </loggers> </configuration>
這裡需要注意,需要在configuration
中,加入屬性packages
為自定類所在包名cn.lqdev.learning
才會被掃描生效,不知道是否還有其他方法。
2.啟動後,就可以看見相關輸出了。
...部分省略...一枚趔趄的猿(log4j2):[14:47:43:751] [INFO] - org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:180) - Using a shared selector for servlet write/read一枚趔趄的猿(log4j2):[14:47:43:761] [INFO] - org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer.start(TomcatEmbeddedServletContainer.java:216) - Tomcat started on port(s): 8080 (http)一枚趔趄的猿(log4j2):[14:47:43:764] [INFO] - org.springframework.boot.StartupInfoLogger.logStarted(StartupInfoLogger.java:57) - Started Chapter25Application in 2.03 seconds (JVM running for 3.164)一枚趔趄的猿(log4j2):[14:47:43:764] [INFO] - cn.lqdev.learning.springboot.chapter25.Chapter25Application.main(Chapter25Application.java:14) - Chapter25啟動!
不知道如何整合log4j2
的,可以查看:《第二十三章:日誌管理之整合篇》
logback自訂Appender
logback
的自訂,也是類似的,都是基於一個基類appender
來實現。本身logback
提供了AppenderBase
和UnsynchronizedAppenderBase
兩個抽象類別(同步和非同步),所以我們自訂時,只需要看實際業務繼承其中的一個即可。先看下其類繼承結構:
0.編寫自訂appender
類。
MyLogbackAppender.java
@Getter@Setterpublic class MyLogbackAppender extends UnsynchronizedAppenderBase<ILoggingEvent>{ Layout<ILoggingEvent> layout; //自訂配置 String printString; @Override public void start(){ //這裡可以做些初始化判斷 比如layout不能為null , if(layout == null) { addWarn("Layout was not defined"); } //或者寫入資料庫 或者redis時 初始化串連等等 super.start(); } @Override public void stop() { //釋放相關資源,如資料庫連接,redis線程池等等 System.out.println("logback-stop方法被調用"); if(!isStarted()) { return; } super.stop(); } @Override public void append(ILoggingEvent event) { if (event == null || !isStarted()){ return; } // 此處自訂實現輸出 // 擷取輸出值:event.getFormattedMessage() // System.out.print(event.getFormattedMessage()); // 格式化輸出 System.out.print(printString + ":" + layout.doLayout(event)); }}
也簡單說明下,相關注意點:
start
方法:初始時調用。故在編寫如資料庫入庫,串連緩衝或者mq時,可以在這個方法裡面進行初始化操作。
stop
:當停止時,調用。可做些資源釋放操作。
1.使用自訂appender:
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?><configuration debug="false"> <!--定義記錄檔的儲存地址 勿在 LogBack 的配置中使用相對路徑 --> <property name="LOG_HOME" value="/home" /> <!-- 控制台輸出 --> <appender name="MyLogback" class="cn.lqdev.learning.springboot.chapter25.config.MyLogbackAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <!-- 日誌收集最低記錄層級 --> <level>INFO</level> </filter> <layout class="ch.qos.logback.classic.PatternLayout"> <!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:層級從左顯示5個字元寬度%msg:日誌訊息,%n是分行符號 --> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </layout> <!-- 自訂參數 --> <printString>一枚趔趄的猿(logback)</printString> </appender> <!-- 自訂包下設定為INFO,則可以看見輸出的日誌不包含debug輸出了 --> <logger name="cn.lqdev.learning" level="INFO" /> <!-- 日誌輸出層級 --> <root level="INFO"> <appender-ref ref="MyLogback" /> </root> </configuration>
2.應用啟動,查看控制台輸出,效果是一樣的:
...部分省略...一枚趔趄的猿(logback):2018-08-25 15:01:57.486 [main] INFO org.apache.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]一枚趔趄的猿(logback):2018-08-25 15:01:57.497 [main] INFO org.apache.tomcat.util.net.NioSelectorPool - Using a shared selector for servlet write/read一枚趔趄的猿(logback):2018-08-25 15:01:57.520 [main] INFO o.s.b.c.e.tomcat.TomcatEmbeddedServletContainer - Tomcat started on port(s): 8080 (http)一枚趔趄的猿(logback):2018-08-25 15:01:57.523 [main] INFO c.l.l.springboot.chapter25.Chapter25Application - Started Chapter25Application in 54.349 seconds (JVM running for 55.377)一枚趔趄的猿(logback):2018-08-25 15:01:57.524 [main] INFO c.l.l.springboot.chapter25.Chapter25Application - Chapter25啟動!
關於ShutdownHook
當你運行了以上的自訂appender
後,停止應用時,你會發現定義的stop
方法並沒有被執行。還需要配置一個ShutdownHook
系統鉤子,使得在jvm
在退出時之前會調用。
一點知識
我們知道,在java
中,註冊一個關閉鉤子是很簡單的,使用Runtime
類即可,具體用法如下:
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { // 執行資源釋放操作 } }));
而在SpringBoot
中,只需要配置logging.register-shutdown-hook
為true
即可。
logging.register-shutdown-hook=true
對於logback
而言,也可以在logback-spring.xml
中配置:
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
也是可以的。再或者在啟動類手動註冊這個DelayingShutdownHook
也是可以的
這裡有個坑,log4j2
而言,配置失效了。Google了一圈也沒有發現解決方案,網上的方案試了一遍都是不行。。很尷尬。要是使用log4j2
的話,可以取巧下,在start()
方法裡面,註冊鉤子之後調用stop
方法。希望有知道的大神分享下如何解決!
參考資料
- 78494458
- http://logging.apache.org/log4j/2.x/manual/appenders.html
- http://logging.apache.org/log4j/2.x/manual/extending.html#Appenders
- https://logback.qos.ch/manual/appenders.html
- 75353854
總結
本文主要是簡單介紹了log4j2
和logback
自訂appender
相關知識。實現起來是相對簡單的,需要注意當涉及需要關閉釋放相關資源時,需要確認下關閉前是否有被調用,不然可能造成串連未關閉等行為,避免不必要的問題。關於最後使用log4j2
關閉鉤子未生效問題,由於現在都預設使用logback
了,這個問題就不深究了,還望有知道的同學分享下解決方案!謝謝!同時由於沒有對兩個架構有過多的深入瞭解,只能點到為止了,若文中有誤,還望指出!
最後
目前互連網上很多大佬都有SpringBoot
系列教程,如有雷同,請多多包涵了。原創不易,碼字不易,還希望大家多多支援。若文中有所錯誤之處,還望提出,謝謝。
老生常談
- 個人QQ:
499452441
- 公眾號:
lqdevOps
個人部落格:http://blog.lqdev.cn
完整樣本:https://github.com/xie19900123/spring-boot-learning/tree/master/chapter-25
原文地址:http://blog.lqdev.cn/2018/08/25/springboot/chapter-twenty-five/
SpringBoot | 第二十五章:日誌管理之自訂Appender