前面兩章節咱們介紹了一些日誌框架的常見配置及使用實踐。通常上,在開發過程當中,像
log4j2
、logback
日誌框架都提供了不少Appender
,基本上能夠知足大部分的業務需求了。但在一些特殊需求或者須要將日誌進行集中管理(集羣部署時,日誌是分拆到不一樣服務器上的,不可能去每一臺服務器上去下載文件的,也不便於日誌檢索)時,就須要自定義Appender
,將日誌集中輸出或者其餘一些特殊需求。因此本章節就來簡單介紹下關於log4j2
和logback
的自定義Appender
知識。html
編寫自定義
Appender
時,咱們先來看看log4j2
和logback
自帶了哪些Appender
,瞭解下是否能夠知足咱們的個性化需求,避免重複製造輪子。java
先看一張官網提供的Appender
說明:git
名稱 | 描述 |
---|---|
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.htmlgithub
和log4j2
同樣,自帶的都差很少了。redis
名稱 | 描述 |
---|---|
ConsoleAppender | 將日誌輸出到控制檯 |
FileAppender | 將日誌輸出到文件 |
RollingFileAppender | 滾動文件生成,按條件生成不一樣文件,配合TriggeringPolicy使用 |
SocketAppender | 輸出日誌到遠程實例中,明文傳輸 |
SSLSocketAppender | 輸出日誌到遠程實例中,密文傳輸 |
SMTPAppender | 將日誌輸出到郵件 |
DBAppender | 日誌事件插入數據庫中,須要提早建立表 |
SyslogAppender | 是一個SocketAppender,將日誌輸出到遠程系統日誌 |
SiftingAppender | 可基於任何給定的實時屬性分開(或者篩選)日誌,如基於用戶會話分開日誌事件 |
AmqpAppender | 將日誌輸出到MQ服務中 |
具體可查看:https://blog.csdn.net/tianyaleixiaowu/article/details/73327752 很詳細!spring
或者查看官網:https://logback.qos.ch/manual/appenders.html數據庫
自定義
Appender
時,能夠按實現的功能,適當的繼承(log4j2
的appender
類基本上被設置成了final
沒法繼承)或者參考一些已有的功能,固然了也能夠直接繼承其基類接口的。如下就簡單的示例下,沒有實現特定的功能,⊙﹏⊙‖∣apache
按官網的擴展說明,咱們來簡單實現一個appender。緩存
官網地址:http://logging.apache.org/log4j/2.x/manual/extending.html#Appenderstomcat
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
提供了AppenderBase
和UnsynchronizedAppenderBase
兩個抽象類(同步和非同步),因此咱們自定義時,只須要看實際業務繼承其中的一個便可。先看下其類繼承結構:
0.編寫自定義appender
類。
MyLogbackAppender.java
@Getter @Setter public 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啓動!
當你運行了以上的自定義
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
而言,配置失效了。谷歌了一圈也沒有發現解決方法,網上的方案試了一遍都是不行。。很尷尬。要是使用log4j2
的話,能夠取巧下,在start()
方法裏面,註冊鉤子以後調用stop
方法。但願有知道的大神分享下如何解決!
本文主要是簡單介紹了
log4j2
和logback
自定義appender
相關知識。實現起來是相對簡單的,**須要注意當涉及須要關閉釋放相關資源時,須要確認下關閉前是否有被調用,否則可能形成鏈接未關閉等行爲,避免沒必要要的問題。**關於最後使用log4j2
關閉鉤子未生效問題,因爲如今都默認使用logback
了,這個問題就不深究了,還望有知道的同窗分享下解決方案!謝謝!同時因爲沒有對兩個框架有過多的深刻了解,只能點到爲止了,若文中有誤,還望指出!
目前互聯網上不少大佬都有
SpringBoot
系列教程,若有雷同,請多多包涵了。原創不易,碼字不易,還但願你們多多支持。若文中有所錯誤之處,還望提出,謝謝。
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/