標籤:
前面這幾個Appender就是Log4J提供的基於檔案系統的Appender。當然,在一些特殊的需要記錄到檔案的Appender來說,我們只需要選擇一個合適的Appender來繼承並完成自己的邏輯即可。關於自訂Appender,待會再看。下面來看幾個比較特殊的Appender。
前面所有介紹的Appender都有一個共同的特點,就是他們都需要配置一個Layout對象,下面要介紹的這個Appender非常特殊,他本身不需要任何Layout對象,這個Appender就是SocketAppender。注意,要能理解SocketAppender,必須對java網路編程和java對象序列化有相關瞭解。
顧名思義SocketAppender是基於Socket的Appender,他不會對日誌訊息做任何的處理,而僅僅是通過Socket將日誌請求發送到特定的伺服器上。既然是把日誌請求發送到特定的伺服器上,首先要考慮的就是日誌訊息使用什麼樣的方式發送,在SocketAppender中,只是簡單的把LoggingEvent對象直接通過序列化的方式發送到Socket的輸出資料流上。所以,要能正常使用SocketAppender,就不單單是使用日誌的應用了,還需要一個能統一和接受日誌訊息的獨立的Log Service器應用了。我們下面就簡單的把使用日誌功能的應用稱為Logging Client,把接收和處理日誌訊息的伺服器應用稱為Logging Server。
要配置Logging Client端的appender這個已經不是痛點了,下面的問題是我們需要自己來寫一個logging server來接收我們的日誌訊息。要完成這樣一個伺服器端的應用,首先我們要確定幾個需求。第一,我們從Socket中讀到的是序列化了的LoggingEvent對象,所以,我們得首先還原序列化這個對象,得到LoggingEvent對象;第二,得到這個對象後,我們要確定處理這個對象的方式,這裡我們就簡單處理,直接使用System.out列印出日誌訊息即可。那麼,我們可以先把這個Server的架構代碼寫出來:
public static void main(String[] args) throws Exception {
ServerSocket socket = new ServerSocket(4560);
while (true) {
Socket client = socket.accept();
Thread t = new Thread(new LogRunner(client));
t.start();
}
}
我們使用一個ServerSocket迴圈的監聽4560連接埠,得到請求後,就把這個客戶請求交給一個LogRunner線程去處理。
下面就是LogRunner的代碼:
static class LogRunner implements Runnable {
private ObjectInputStream ois;
public LogRunner(Socket client) {
try {
this.ois = new ObjectInputStream(client.getInputStream());
} catch (Exception e) {
}
}
@Override
public void run() {
try {
while (true) {
LoggingEvent event = (LoggingEvent) ois.readObject();
System.out.println(event.getLoggerName() + ": " + event.getMessage());
}
} catch (Exception e) {
} finally {
}
}
}
這裡面有幾個點需要特別注意:
1,我們使用socket的InputStream建立了一個ObjectInputStream用來還原序列化Logging Client發送的LoggingEvent對象。
2,特別注意,我們在run方法中,使用了一個while(true)無線迴圈來不斷的讀入並將讀入的二進位內容轉成Object對象,並將Object對象再強轉成LoggingEvent。並且,當我們讀入一個LoggingEvent對象之後,並沒有去關閉ois。為什麼要這樣做呢?其實需要考慮SocketAppender的工作方式。當一個Logging Client啟動並執行時候,他對應的SocketAppender就會啟動,並建立一個到指定Logging Server的Socket串連。每當一個日誌訊息到達SocketAppender之後,就會直接向這個Socket的OutputStream寫入序列化的對象資料,並通過flush發送到伺服器端。換句話說,就是當一個Logging Client應用建立好之後,就會一直有一個Socket串連到伺服器,這個串連是不會斷的,並且一旦有日誌訊息到達,就直接發送序列化資料。所以我們的伺服器端,就必須在得到一個Socket串連之後,一直維護這個串連,不斷的從串連中讀取資料,並且不能關閉這個串連。
然後我們將這個應用先運行起來。再來看用戶端。
首先來看看SocketAppender,他有幾個常用的配置:
RemoteHost:指定遠端的Logging Server的地址。比如對於我們的應用來說,直接配成LocalHost就可以了。
Port:指定遠端的Logging Server的連接埠。這裡,我們的伺服器端監聽的是4560連接埠,所以,這裡就填4560。其實,預設情況下,SocketAppender就是請求的4560連接埠。
Application:可以設定應用的名稱。因為使用SocketAppender,就可以使用同一個專門的Logging Server來統一完成日誌記錄。那麼就有可能會有不同的應用提交日誌到Server上,怎麼區分日誌資訊是哪個用戶端發過來的呢?就可以通過配置Application資訊即可。具體這個資訊從LoggingEvent中怎麼擷取,待會再介紹。
reconnectionDelay:如果用戶端串連不上伺服器端,那麼會在reconnectionDelay指定的時間之後重新串連。這裡需要注意的一個問題就是,用戶端發送日誌請求和伺服器之間的通訊問題。在網路通訊正常的情況下,每一條用戶端的日誌請求都會正常的發送給伺服器端處理,這是沒有問題的。但是考慮網路的不穩定性,就會出現以下的情況:
1,當網路發送包的頻率大於日誌頻率,會正常的按照日誌頻率發送資訊;
2,當網路發送包的頻率低於日誌頻率,則只會按照網路發送包的頻率來發送用戶端的日誌資訊。這裡就有可能出現日誌訊息丟失的問題;
3,當伺服器串連斷開,SocketAppender會嘗試重新串連伺服器端,但是在這個斷開的過程當中,所有的日誌資訊將會丟失。
所以,在使用SocketAppender的時候,也需要考慮到網路的因素。當然,我們也能自己實現一個Appender,當網路斷開的時候,先將LoggingEvent對象暫時序列化到本地,當網路再次串連上之後,再把緩衝到本地的LoggingEvent對象發送。要完成這個功能,只要瞭解了Appender的工作原理就能比較輕鬆的完成。這個我們後面再看。
下面,就完成用戶端的配置,並運行測試:
log4j.rootLogger=DEBUG,socket
log4j.appender.socket=org.apache.log4j.net.SocketAppender
log4j.appender.socket.RemoteHost=localhost
log4j.appender.socket.port=4560
log4j.appender.socket.application=localclient
我們定義了一個socketAppender,並配置了伺服器位址和連接埠號碼,最後為我們的client取名為localclient。
下面來看看測試:
@Test
public void testSocketAppender() throws Exception{
Logger logger = Logger.getLogger("cd.itcast");
Logger barLogger = Logger.getLogger("cd.itcast.log");
for (int i = 0; i < 100; i++) {
logger.warn("logger warn");
logger.debug("logger debug");
barLogger.info("bar logger info");
barLogger.debug("bar logger debug long long ");
}
}
同樣的測試,運行100次,這次來運行,可以看到,速度非常的快,原因就是,使用SocketAppender,在用戶端根本不會對LoggingEvent做任何過多的處理,而直接就把序列化的對象發送到遠端server上,並且,序列化和發送的邏輯,還是獨立運行在一個單獨的線程中,對我們應用所在的線程效能沒有任何影響。
查看伺服器端的控制台輸出:
localclient:cd.itcast: logger warn
localclient:cd.itcast: logger debug
localclient:cd.itcast.log: bar logger info
localclient:cd.itcast.log: bar logger debug long long
可以看到內容完整的輸出了。
其實,我們也沒有必要自己來寫這個Server端的應用,因為Log4J為我們實現了一個Server端:SimpleSocketServer。
這個SimpleSocketServer的使用非常簡單,他相當於把接受到的LoggingEvent作為本地的日誌記錄事件,再使用在伺服器端配置的Log4J環境來記錄日誌。畫個簡單的圖:
在啟動SimpleSocketServer的時候,有兩個啟動選項,一個是監聽的連接埠,一個是設定檔地址,我們可以這樣測試,首先額外建立一個log4j.properties檔案來控制SimpleSocketServer上面的Log4J環境。
log4j.rootLogger=DEBUG,file
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.conversionPattern=%r [%t] %p %c %x - %m%n
log4j.appender.file.file=serverlog.log
我們把這個設定檔命名為log4jserver.properties,
然後使用參數4560 log4jserver.properties來啟動SimpleSocketServer。
再次運行我們的測試,可以發現,在應用目錄下多了log4jserver.log記錄檔,並且裡面的內容:
0 [main] INFO org.apache.log4j.net.SimpleSocketServer - Listening on port 4560
0 [main] INFO org.apache.log4j.net.SimpleSocketServer - Waiting to accept a new client.
29968 [main] INFO org.apache.log4j.net.SimpleSocketServer - Connected to client at /192.168.1.101
29968 [main] INFO org.apache.log4j.net.SimpleSocketServer - Starting new socket node.
29968 [main] INFO org.apache.log4j.net.SimpleSocketServer - Waiting to accept a new client.
87406 [main] INFO org.apache.log4j.net.SimpleSocketServer - Connected to client at /192.168.1.101
87406 [main] INFO org.apache.log4j.net.SimpleSocketServer - Starting new socket node.
87422 [main] INFO org.apache.log4j.net.SimpleSocketServer - Waiting to accept a new client.
87422 [main] WARN cd.itcast - logger warn
87437 [main] DEBUG cd.itcast - logger debug
87437 [main] INFO cd.itcast.log - bar logger info
87437 [main] DEBUG cd.itcast.log - bar logger debug long long
確實為我們用戶端的日誌訊息。
關於Appender,我們就先暫時看到這裡,至於後面的SMTPAppender和JdbcAppender,大家按照我們學習這些Appender的思路,去運用就可以了。
Log4J學習【二十三】常用的Appender之SocketAppender