1. 程式人生 >實用技巧 >其它 >Log4J2官方文件學習實踐

Log4J2官方文件學習實踐


摘要:Log4j是Apache的一個開源專案,通過使用Log4j,我們可以控制日誌資訊輸送的目的地是控制檯、檔案、GUI元件,甚至是套介面伺服器、NT的事件記錄器、UNIXSyslog守護程序等;我們也可以控制每一條日誌的輸出格式;通過定義每一條日誌資訊的級別,我們能夠更加細緻地控制日誌的生成過程。最令人感興趣的就是,這些可以通過一個配置檔案來靈活地進行配置,而不需要修改應用的程式碼。

Log4J2有各種Appender,通過不同的方式去輸出Log。log4j2.xml

例如一些XML標籤的應用 :

  • <console> 輸出到控制檯
  • <file>寫道臨時檔案中
  • <RollingFile> 滾動寫入,自動歸檔 (檔案到了100MB ,就會歸檔

支援Cassandra寫到Cassandra資料庫
支援Console控制檯
支援Failover往一個地方寫失敗了就寫入到另一個地方去
支援Flume大資料日誌採集系統
支援JDBC寫入關係型資料庫
支援JMS寫入外部訊息系統
支援JPAJPA標準寫入資料庫
支援HTTP訪問某一個HTTP介面
支援Kafka寫入訊息中介軟體
支援Mapped File記憶體對映檔案

Async

Appender支援非同步的Append,也就是Async模式。

他的工作原理就是接收一些引用定位到其他的appender,使用一個獨立的執行緒去寫日誌,也就是完全非同步化。

但是這裡需要注意:寫到其他的appender丟擲的異常是直接不可見的,是隱藏起來的。

當然一般來說是配好了所有的Appender最後去做的一個配置。預設情況下Async會使用一個ArrayBlockingQueue。當我們的應用使用多執行緒去跑程式時必須對Async進行關注,他可能會導致鎖的競爭,會導致效能的下降。

當我們需要在非同步環境中提高打日誌的效能,一般來說建議用lock-free Async Loggers,也就是無鎖化非同步Logger。倒也無需太關注AsyncAppender。

官方文件見:Log4j – Log4j 2 Appenders

Log4J2官方文件學習實踐

https://logging.apache.org/log4j/2.x/manual/appenders.html#AsyncAppender

首先我們關注一下配置:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <File name="MyFile" fileName="logs/app.log">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
    </File>
    <Async name="Async">
      <AppenderRef ref="MyFile"/>
    </Async>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="Async"/>
    </Root>
  </Loggers>
</Configuration>

如程式碼塊所見:Root Logger裡面只引用了一個Async,Async裡面再去引用別人,這裡是MyFile。

通過這種方式可以讓我們把日誌先放入ArrayBlockingQueue,再去往其他的Append裡面寫入。

當然裡面也有一些配置:

AppenderRefAsyncAppend引用的一些其他的Append
blocking如果設定為true,佇列滿了就會阻塞住,這個其實就不太好了
shutdownTimeout關閉時的超時時間
BlockingQueueFactory

你可以自己定義一個Queue的工廠

不過這些也不用細究

文件裡也說了正常來說也不建議使用,這個BlockingQueueFactory提供了幾種queue的實現,比如

  • ArrayBlockingQueue:預設的實現
  • DisruptorBlockingQueue
  • JCToolsBlockingQueue
  • LinkedTransferQueue

但其實比較推薦的並不是用Async,而是非同步的Logger


FailoverAppender

官方文件見:​​​​​​Log4j – Log4j 2 Appenders

Log4J2官方文件學習實踐

https://logging.apache.org/log4j/2.x/manual/appenders.html#FailoverAppender

當我們使用FailoverAppender時,就可以先定義一個primary Appender,再定義一個secondary appenders,這個東西說實在還是挺有用的。

因為他會優先往primary裡面列印,如果失敗了再往secondary裡面去寫。那麼如果我們後續有需求要把日誌輸入到ES中去,是不是就得用這個了?

到了那時我們primary就是列印進ES中,但是因為可能有一些不可預知的異常,比如網路通訊導致寫入失敗了,那麼肯定就得落盤到磁碟檔案中去。

Parameter NameTypeDescription
filterFilter

一個過濾器,用於確定事件是否應由此 Appender 處理。

使用 CompositeFilter 可以使用多個過濾器。

primaryString要使用的主要 Appender 的名稱。
failoversString[]要使用的輔助 Appender 的名稱。
nameStringThe name of the Appender.

retryIntervalSeconds

[重試間隔]

integer預設60,如果primary失敗了那麼不會進行重試.
ignoreExceptionsboolean

預設值為 true,會導致遇到的異常時被記錄到內部,然後被忽略。

當設定為 false 時,異常將被傳播給呼叫者。

targetStringEither "SYSTEM_OUT" or "SYSTEM_ERR". The default is "SYSTEM_ERR".

官方配置示例:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
  <Appenders>
    <RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{MM-dd-yyyy}.log.gz"
                 ignoreExceptions="false">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
      <TimeBasedTriggeringPolicy />
    </RollingFile>
    <Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
      <PatternLayout pattern="%m%n"/>
    </Console>
    <Failover name="Failover" primary="RollingFile">
      <Failovers>
        <AppenderRef ref="Console"/>
      </Failovers>
    </Failover>
  </Appenders>
  <Loggers>
    <Root level="error">
      <AppenderRef ref="Failover"/>
    </Root>
  </Loggers>
</Configuration>

我們可以看到在示例中直接引用了Failover,Failover定義了滾動檔案。

如果失敗了就可以配置控制檯輸出。


KafkaAppender

這個其實我們可以關注一下,把日誌往Kafka裡面去打。對應著我們可以開一個後臺的服務,比如統一日誌中心。日誌中心裡面就包含了Kafka以及對應的消費元件。消費元件負責把Kafka裡的日誌寫入到ES中去,其實這也是一個不錯的思路。然後我們在基於ES做一個對應的日誌查詢系統就可以了。

這個配置相對簡單,就不一一贅述了。你可以配置一個Kafka的Topic,指定一個Key進行partition分割槽,確保同一個服務有序的分發到同一個分割槽中去。

配置syncSend預設為true,這個是為了同步的去傳送以保證寫Kafka出錯就能反饋回來。

還有就是properties,我們需要設定的就是bootstrap.servers這樣的一個地址

官方示例:

<?xml version="1.0" encoding="UTF-8"?>
  ...
  <Appenders>
    <Kafka name="Kafka" topic="log-test">
      <PatternLayout pattern="%date %message"/>
        <Property name="bootstrap.servers">localhost:9092</Property>
    </Kafka>
  </Appenders>

NoSQLAppender

當然我們也可以把日誌直接寫到ES這種NoSQL資料庫裡,沒必要走什麼Kafka了。他會用一個比較輕量級的介面,目前介面對MongoDB和CouchDB都有實現。如果說我們需要自定義provider其實也非常簡單。這個其實還是比較值的學習的。

這裡的配置最重要的就是定義一個自己的provider(NoSqlProvider),我們對ES的定義直接寫在這個配置就可以了。

文件上直接說了推薦我們閱讀一下MongoDB和CounchDB的原始碼作為一個指導【To create your own custom provider, read the JavaDoc for theNoSQLProvider,NoSQLConnection, andNoSQLObjectclasses】,學習一下日誌是如何直接寫入NoSQL資料庫中去的。

當然,如果這樣做相對來說要麻煩一些,如果用Kafka來中轉就會簡單很多

{
    "level": "WARN",
    "loggerName": "com.example.application.MyClass",
    "message": "Something happened that you might want to know about.",
    "source": {
        "className": "com.example.application.MyClass",
        "methodName": "exampleMethod",
        "fileName": "MyClass.java",
        "lineNumber": 81
    },
    "marker": {
        "name": "SomeMarker",
        "parent" {
            "name": "SomeParentMarker"
        }
    },
    "threadName": "Thread-1",
    "millis": 1368844166761,
    "date": "2013-05-18T02:29:26.761Z",
    "thrown": {
        "type": "java.sql.SQLException",
        "message": "Could not insert record. Connection lost.",
        "stackTrace": [
                { "className": "org.example.sql.driver.PreparedStatement$1", "methodName": "responder", "fileName": "PreparedStatement.java", "lineNumber": 1049 },
                { "className": "org.example.sql.driver.PreparedStatement", "methodName": "executeUpdate", "fileName": "PreparedStatement.java", "lineNumber": 738 },
                { "className": "com.example.application.MyClass", "methodName": "exampleMethod", "fileName": "MyClass.java", "lineNumber": 81 },
                { "className": "com.example.application.MainClass", "methodName": "main", "fileName": "MainClass.java", "lineNumber": 52 }
        ],
        "cause": {
            "type": "java.io.IOException",
            "message": "Connection lost.",
            "stackTrace": [
                { "className": "java.nio.channels.SocketChannel", "methodName": "write", "fileName": null, "lineNumber": -1 },
                { "className": "org.example.sql.driver.PreparedStatement$1", "methodName": "responder", "fileName": "PreparedStatement.java", "lineNumber": 1032 },
                { "className": "org.example.sql.driver.PreparedStatement", "methodName": "executeUpdate", "fileName": "PreparedStatement.java", "lineNumber": 738 },
                { "className": "com.example.application.MyClass", "methodName": "exampleMethod", "fileName": "MyClass.java", "lineNumber": 81 },
                { "className": "com.example.application.MainClass", "methodName": "main", "fileName": "MainClass.java", "lineNumber": 52 }
            ]
        }
    },
    "contextMap": {
        "ID": "86c3a497-4e67-4eed-9d6a-2e5797324d7b",
        "username": "JohnDoe"
    },
    "contextStack": [
        "topItem",
        "anotherItem",
        "bottomItem"
    ]
}

RollingFileAppender

這個還是挺常用的,RollingFile其實也算是輸出流的一種,往磁碟檔案裡面輸出嘛。

如果觸發了TriggeringPolicy就會將檔案進行歸檔。如果觸發了RolloverPolicy就會把檔案進行清理。所以說RollingFileAppender會使用RollingFileManager去執行磁碟檔案的IO以及執行rollover開闢新檔案刪除舊檔案。當RolloverFileAppender不能被共享的時候,RollingFileManager是可以被共享的。這個其實是說兩個應用部署在一個tomcat的時候是可以共享的,當然這個你可以不用管他。