利用Log4j創建日誌服務器

最近因爲平臺升級改造做了雙機模式,日誌的記錄就成了一個大問題。以前都是一個應用起一個實例只打印一個日誌,現在則是因爲一個應用起了兩個實例,而這兩個實例又分別打印日誌,這種情況造成我們查看日誌,診斷問題的不便,因爲必須把兩個實例打的日誌都拿到,纔是這個應用的全部日誌。
另外平臺有多個子系統組成,這些子系統都有自己的日誌,並且運行在不同的操作系統和主機上,收集這些日誌對運營人員來說也比較困難。
針對以上兩個問題,我們決定在平臺中採用日誌服務器來做到集中日誌管理,平臺中所有子系統通過socket方式將日誌信息傳到日誌服務器,再由日誌服務器統一記錄。這樣既避免了一個應用日誌不同實例分別打印,也可以將所有子系統日誌集中管理,並能夠自定義輸出路徑。
我們的平臺基於J2EE架構實現,故在各應用和各子系統均使用了Log4j,考慮到Log4j提供了SocketAppender可以直接調用,我們決定日誌服務器仍然基於Log4j實現。
Log4j提供了一個簡單的基於socket的日誌服務器,但直接使用這個服務器不能完全滿足我們的需求,首先它自身代碼存在問題,需要修改;其次即使修改正確,該服務器也只能按客戶端IP配置打印appender,而我們有些子系統是運行在同一主機,直接使用該服務器只能將運行在同一主機上的子系統日誌打在一起,不便於分析處理。我們要求按照不同應用輸出日誌。
稍經改造,Log4j提供的這個服務器就能較好地提供服務,滿足我們的使用要求了。
Log4j提供的日誌服務器由SocketServer.java和SocketNode.java實現,我們需要改造這兩個類,以達到我們的目的。
Log4j提供的SocketServer利用一個Hashtable的變量hierarchyMap保存各個客戶端的log4j配置信息,一旦偵聽到某個客戶端發送請求過來,則立刻New一個SocketNode來處理該請求,該SocketNode的構造參數中有一個是從hierarchyMap中獲取的log4j配置信息,SocketNode將根據該配置信息直接輸出客戶端發送的日誌請求。
改造後的日誌服務器, SocketServer仍然利用hierarchyMap保存各個客戶端的log4j配置信息,但這次不是基於客戶端IP,而是基於應用的,當偵聽到某個客戶端發送請求過來,則同樣New一個SocketNode來處理該請求,hierarchyMap將作爲改造後的SocketNode一個構造參數,這樣SocketNode自己就能夠根據客戶端請求內容來決定使用哪個log4j配置信息輸出客戶端日誌請求,這裏有個關鍵就是客戶端需要上傳信息表明自己是哪個應用。
分析Log4j源碼,我們發現可以爲SocketAppender配置一個屬性application,而這個屬性在服務端是可以獲取的,SocketNode讀取這個屬性並自動選擇相應的log4j配置信息來處理。
修改後的相關代碼和配置文件如下:

//#SocketServer.java

import java.io.File;

import java.net.InetAddress;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Hashtable;


import org.apache.log4j.Hierarchy;

import org.apache.log4j.Level;

import org.apache.log4j.Logger;

import org.apache.log4j.PropertyConfigurator;

import org.apache.log4j.spi.RootLogger;


public class SocketServer {


static String CLIENT_DIR = "client";

static String CONFIG_FILE_EXT = ".properties";


static Logger cat = Logger.getLogger(SocketServer.class);

static SocketServer server;

static int port;// key=application, value=hierarchy

Hashtable<String, Hierarchy> hierarchyMap;

String dir;

public static void main(String argv[]) {

if (argv.length == 2)

init(argv[0], argv[1]);

else

usage("Wrong number of arguments.");


//init("30020", "config");

try {

cat.info("Listening on port " + port);

ServerSocket serverSocket = new ServerSocket(port);

while (true) {

cat.info("Waiting to accept a new client.");

Socket socket = serverSocket.accept();

InetAddress inetAddress = socket.getInetAddress();

cat.info("Connected to client at " + inetAddress);

cat.info("Starting new socket node.");

new Thread(new SocketNode(socket, server.hierarchyMap)).start();

}

} catch (Exception e) {

e.printStackTrace();

}

}


static void usage(String msg) {

System.err.println(msg);

System.err.println("Usage: java " + SocketServer.class.getName() + " port configFile directory");

System.exit(1);

}


static void init(String srvPort, String configDir) {

try {

port = Integer.parseInt(srvPort);

} catch (java.lang.NumberFormatException e) {

e.printStackTrace();

usage("Could not interpret port number [" + srvPort + "].");

}


PropertyConfigurator.configure(configDir + File.separator + "socketserver.properties");


server = new SocketServer(configDir);

}


public SocketServer(String configDir) {

this.dir = configDir;

hierarchyMap = new Hashtable<String, Hierarchy>(11);

configureHierarchy();


}


// This method assumes that there is no hiearchy for inetAddress

// yet. It will configure one and return it.

void configureHierarchy() {

File configFile = new File(dir + File.separator + CLIENT_DIR);

if (configFile.exists() && configFile.isDirectory()) {

String[] clients = configFile.list();

for (int i = 0; i < clients.length; i++) {

File client = new File(dir + File.separator + CLIENT_DIR + File.separator + clients[i]);

if (client.isFile()) {

Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));

String application = clients[i].substring(0, clients[i].indexOf("."));

cat.info("Locating configuration file for " + application);

hierarchyMap.put(application, h);

new PropertyConfigurator().doConfigure(client.getAbsolutePath(), h);

}
}
}
}
}
//#SocketNode.java

import java.io.BufferedInputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.net.Socket;

import java.util.Hashtable;


import org.apache.log4j.Hierarchy;

import org.apache.log4j.Logger;

import org.apache.log4j.spi.LoggingEvent;

// Contributors: Moses Hohman <[email protected]>


/**

* Read {@link LoggingEvent} objects sent from a remote client using Sockets

* (TCP). These logging events are logged according to local policy, as if they

* were generated locally.

*

* <p>

* For example, the socket node might decide to log events to a local file and

* also resent them to a second socket node.

*

* @author Ceki Gülcü

*

* @since 0.8.4

*/

public class SocketNode implements Runnable {


Socket socket;

ObjectInputStream ois;

Hashtable<String, Hierarchy> hashtable;


static Logger logger = Logger.getLogger(SocketNode.class);


public SocketNode(Socket socket, Hashtable<String, Hierarchy> hashtable) {

this.socket = socket;

this.hashtable = hashtable;

try {

ois = new ObjectInputStream(new BufferedInputStream(socket.getInputStream()));

} catch (Exception e) {

logger.error("Could not open ObjectInputStream to " + socket, e);

}

}


// public

// void finalize() {

// System.err.println("-------------------------Finalize called");

// System.err.flush();

// }


public void run() {

LoggingEvent event;

Logger remoteLogger;


try {

if (ois != null) {

while (true) {

// read an event from the wire

event = (LoggingEvent) ois.readObject();

Object application = event.getMDC("application");

if (application != null) {

// get a logger from the hierarchy. The name of the

// logger

// is taken to be the name contained in the event.

remoteLogger = hashtable.get(application).getLogger(event.getLoggerName());


// logger.info(remoteLogger.getAppender(application.toString()));

// event.logger = remoteLogger;

// apply the logger-level filter

if (remoteLogger != null && event.getLevel().isGreaterOrEqual(remoteLogger.getEffectiveLevel())) {

// finally log the event as if was generated locally

remoteLogger.callAppenders(event);

}


}

}

}

} catch (java.io.EOFException e) {

logger.info("Caught java.io.EOFException closing conneciton.");

} catch (java.net.SocketException e) {

logger.info("Caught java.net.SocketException closing conneciton.");

} catch (IOException e) {

logger.info("Caught java.io.IOException: " + e);

logger.info("Closing connection.");

} catch (Exception e) {

logger.error("Unexpected exception. Closing conneciton.", e);

} finally {

if (ois != null) {

try {

ois.close();

} catch (Exception e) {

logger.info("Could not close connection.", e);

}

}

if (socket != null) {

try {

socket.close();

} catch (IOException ex) {

}
}
}
}
}

日誌服務器參數的配置,文件名必須爲socketserver.properties,該配置文件必須置於日誌服務器的啓動腳本LogServer.bat上層目錄下的配置文件夾下,該配置文件夾在LogServer.bat中指定,本文中提到的配置文件夾爲config。
#文件名socketserver.properties
log4j.rootCategory=INFO, STDOUT

log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
log4j.appender.STDOUT.layout.ConversionPattern=[%d{yyyy-MM-dd HH\:mm\:ss}][%5p][%5t][%l] %m%n

日誌服務器端各應用的log4j配置文件,必須放在config/client目錄下,這裏可以部署多個配置文件,日誌服務器啓動的時候會一次讀入所有的配置信息。
#文件名test.properties
log4j.rootLogger=info,test
log4j.category.org.springframework.jdbc=debug,test
log4j.category.test=debug,test
log4j.additivity.test=false
log4j.additivity.org.springframework.jdbc=false

log4j.appender.test=org.apache.log4j.DailyRollingFileAppender
log4j.appender.test.DatePattern='.'yyyy-MM-dd
log4j.appender.test.File=${logPath}/test/bmr.log
log4j.appender.test.Append=true
log4j.appender.test.Threshold=INFO
log4j.appender.test.layout=org.apache.log4j.PatternLayout
log4j.appender.test.layout.ConversionPattern=[%d{yyyy-MM-dd HH\:mm\:ss}][%5p][%5t][%l] %m%n


日誌服務器的啓動腳本LogServer.bat
@echo off
java -cp .\log4j-1.2.8.jar -DlogPath=D:\LogServer\log *****.SocketServer 30020 config


某個客戶端Log4j.properties的配置,注意SocketAppender的application屬性,要求和服務器端某個應用的log4j配置文件對應。
log4j.rootCategory=, test
log4j.appender.test=org.apache.log4j.net.SocketAppender
log4j.appender.test.RemoteHost=“日誌計算機”
log4j.appender.test.Port=30020 
log4j.appender.test.application=test

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章