Log4J2官方文件學習實踐
摘要:Log4j是Apache的一個開源專案,通過使用Log4j,我們可以控制日誌資訊輸送的目的地是控制檯、檔案、GUI元件,甚至是套介面伺服器、NT的事件記錄器、UNIXSyslog守護程序等;我們也可以控制每一條日誌的輸出格式;通過定義每一條日誌資訊的級別,我們能夠更加細緻地控制日誌的生成過程。最令人感興趣的就是,這些可以通過一個配置檔案來靈活地進行配置,而不需要修改應用的程式碼。
Log4J2有各種Appender,通過不同的方式去輸出Log。log4j2.xml
例如一些XML標籤的應用 :
- <console> 輸出到控制檯
- <file>寫道臨時檔案中
- <RollingFile> 滾動寫入,自動歸檔 (檔案到了100MB ,就會歸檔)
| 支援Cassandra | 寫到Cassandra資料庫 |
| 支援Console | 控制檯 |
| 支援Failover | 往一個地方寫失敗了就寫入到另一個地方去 |
| 支援Flume | 大資料日誌採集系統 |
| 支援JDBC | 寫入關係型資料庫 |
| 支援JMS | 寫入外部訊息系統 |
| 支援JPA | JPA標準寫入資料庫 |
| 支援HTTP | 訪問某一個HTTP介面 |
| 支援Kafka | 寫入訊息中介軟體 |
| 支援Mapped File | 記憶體對映檔案 |
Async
Appender支援非同步的Append,也就是Async模式。
他的工作原理就是接收一些引用定位到其他的appender,使用一個獨立的執行緒去寫日誌,也就是完全非同步化。
但是這裡需要注意:寫到其他的appender丟擲的異常是直接不可見的,是隱藏起來的。
當然一般來說是配好了所有的Appender最後去做的一個配置。預設情況下Async會使用一個ArrayBlockingQueue。當我們的應用使用多執行緒去跑程式時必須對Async進行關注,他可能會導致鎖的競爭,會導致效能的下降。
當我們需要在非同步環境中提高打日誌的效能,一般來說建議用lock-free Async Loggers,也就是無鎖化非同步Logger。倒也無需太關注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裡面寫入。
當然裡面也有一些配置:
| AppenderRef | AsyncAppend引用的一些其他的Append |
| blocking | 如果設定為true,佇列滿了就會阻塞住,這個其實就不太好了 |
| shutdownTimeout | 關閉時的超時時間 |
| BlockingQueueFactory | 你可以自己定義一個Queue的工廠 不過這些也不用細究 |
文件裡也說了正常來說也不建議使用,這個BlockingQueueFactory提供了幾種queue的實現,比如
- ArrayBlockingQueue:預設的實現
- DisruptorBlockingQueue
- JCToolsBlockingQueue
- LinkedTransferQueue
但其實比較推薦的並不是用Async,而是非同步的Logger
FailoverAppender
當我們使用FailoverAppender時,就可以先定義一個primary Appender,再定義一個secondary appenders,這個東西說實在還是挺有用的。
因為他會優先往primary裡面列印,如果失敗了再往secondary裡面去寫。那麼如果我們後續有需求要把日誌輸入到ES中去,是不是就得用這個了?
到了那時我們primary就是列印進ES中,但是因為可能有一些不可預知的異常,比如網路通訊導致寫入失敗了,那麼肯定就得落盤到磁碟檔案中去。
| Parameter Name | Type | Description |
|---|---|---|
| filter | Filter | 一個過濾器,用於確定事件是否應由此 Appender 處理。 使用 CompositeFilter 可以使用多個過濾器。 |
| primary | String | 要使用的主要 Appender 的名稱。 |
| failovers | String[] | 要使用的輔助 Appender 的名稱。 |
| name | String | The name of the Appender. |
| retryIntervalSeconds [重試間隔] | integer | 預設60,如果primary失敗了那麼不會進行重試. |
| ignoreExceptions | boolean | 預設值為 true,會導致遇到的異常時被記錄到內部,然後被忽略。 當設定為 false 時,異常將被傳播給呼叫者。 |
| target | String | Either "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 the
NoSQLProvider,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的時候是可以共享的,當然這個你可以不用管他。

