SpringBoot | 第二十五章:日誌管理之自定義Appender

前言

前面兩章節咱們介紹了一些日誌框架的常見配置及使用實踐。通常上,在開發過程當中,像log4j2logback日誌框架都提供了不少Appender,基本上能夠知足大部分的業務需求了。但在一些特殊需求或者須要將日誌進行集中管理(集羣部署時,日誌是分拆到不一樣服務器上的,不可能去每一臺服務器上去下載文件的,也不便於日誌檢索)時,就須要自定義Appender,將日誌集中輸出或者其餘一些特殊需求。因此本章節就來簡單介紹下關於log4j2logback的自定義Appender知識。html

一點知識

編寫自定義Appender時,咱們先來看看log4j2logback自帶了哪些Appender,瞭解下是否能夠知足咱們的個性化需求,避免重複製造輪子。java

log4j2自帶Appender

先看一張官網提供的Appender說明:git

官方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.htmlgithub

logback自帶Appender

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

自定義Appender時,能夠按實現的功能,適當的繼承(log4j2appender類基本上被設置成了final沒法繼承)或者參考一些已有的功能,固然了也能夠直接繼承其基類接口的。如下就簡單的示例下,沒有實現特定的功能,⊙﹏⊙‖∣apache

log4j2自定義Appender

按官網的擴展說明,咱們來簡單實現一個appender。緩存

extend 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的自定義,也是相似的,都是基於一個基類appender來實現。自己logback提供了AppenderBaseUnsynchronizedAppenderBase兩個抽象類(同步和非同步),因此咱們自定義時,只須要看實際業務繼承其中的一個便可。先看下其類繼承結構:

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啓動!

關於ShutdownHook

當你運行了以上的自定義appender後,中止應用時,你會發現定義的stop方法並無被執行。還須要配置一個ShutdownHook系統鉤子,使得在jvm在退出時以前會調用。

一點知識

咱們知道,在java中,註冊一個關閉鉤子是很簡單的,使用Runtime類便可,具體用法以下:

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

            @Override
            public void run() {
                // 執行資源釋放操做

            }
        }));

而在SpringBoot中,只須要配置logging.register-shutdown-hooktrue便可。

logging.register-shutdown-hook=true

對於logback而言,也能夠在logback-spring.xml中配置:

<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>

也是能夠的。再或者在啓動類手動註冊這個DelayingShutdownHook也是能夠的

這裏有個坑,log4j2而言,配置失效了。谷歌了一圈也沒有發現解決方法,網上的方案試了一遍都是不行。。很尷尬。要是使用log4j2的話,能夠取巧下,在start()方法裏面,註冊鉤子以後調用stop方法。但願有知道的大神分享下如何解決!

參考資料

  1. https://blog.csdn.net/zhoucheng05_13/article/details/78494458
  2. http://logging.apache.org/log4j/2.x/manual/appenders.html
  3. http://logging.apache.org/log4j/2.x/manual/extending.html#Appenders
  4. https://logback.qos.ch/manual/appenders.html
  5. https://blog.csdn.net/hupoling/article/details/75353854

總結

本文主要是簡單介紹了log4j2logback自定義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/

相關文章
相關標籤/搜索