先描述一下問題,多個服務(wù)器實現(xiàn)的負(fù)載均衡,每個服務(wù)器存儲在自己的硬盤里。但是現(xiàn)在需要對日志做統(tǒng)一的分析,在多個服務(wù)器上統(tǒng)計就麻煩了。思路是把日志統(tǒng)一到一臺日志服務(wù)器上,再統(tǒng)一做統(tǒng)計分析。怎么統(tǒng)一到一臺服務(wù)器上,說實話沒有特別好的思路,最后嘗試了log4j的SocketAppender。查了不少網(wǎng)絡(luò)資源,都說的有些不明了,還是得親自嘗試之后才見分曉。

1、客戶端的配置

客戶端的配置比較簡單,只需要告訴log4j需要監(jiān)聽哪個遠(yuǎn)程服務(wù)器的哪個端口即可。直接在log4j.properties里直接配置就好。

  1. <span style="font-size:12px;">log4j.appender.logs=org.apache.log4j.DailyRollingFileAppender  
  2. log4j.appender.logs.File = /data/logs/request/logs.log  
  3. log4j.appender.logs.layout = org.apache.log4j.PatternLayout  
  4. log4j.appender.logs.layout.ConversionPattern=%d [%t] - %m%n  
  5. log4j.appender.logs.DatePattern='.'yyyy-MM-dd'.log'  
  6.   
  7. log4j.appender.socket=org.apache.log4j.net.SocketAppender  
  8. log4j.appender.socket.RemoteHost=172.16.2.152  
  9. log4j.appender.socket.Port=4560  
  10. log4j.appender.socket.LocationInfo=true  
  11. #下面這兩句感覺沒用  
  12. log4j.appender.socket.layout=org.apache.log4j.PatternLayout  
  13. log4j.appender.socket.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%t%m%n  
  14.   
  15. #將日志寫入本地和遠(yuǎn)程日志服務(wù)器  
  16. log4j.logger.com.test.core.filter =DEBUG,socket,logs</span>  
 

2、日志服務(wù)器的配置

日志服務(wù)器需要單獨(dú)啟動一個java進(jìn)程,接收客戶端給自己發(fā)送的socket請求。Log4j提供了org.apache.log4j.net.SocketServer類,直接運(yùn)行其main函數(shù)就行了(當(dāng)然也可以自己寫啦)。

java -cp /log4jsocket/serverConfig/log4j-1.2.16.jarorg.apache.log4j.net.SocketServer 4560 /log4jsocket/log4jserver.properties /log4jsocket/clientConfig

/log4jsocket/serverConfig/log4j-1.2.16.jar是log4j jar包存放的位置,org.apache.log4j.net.SocketServer需要三個參數(shù):

1)4560 是監(jiān)聽的端口號

2)/log4jsocket/log4jserver.properties 是記錄日志服務(wù)器的日志的配置文件

3)/log4jsocket/clientConfig 是客戶端配置文件所在的目錄(注意是目錄)。

著重說一下org.apache.log4j.net.SocketServer的第三個參數(shù),這個文件夾下配置的是各個客戶端的日志的配置。配置文件以.lcf結(jié)尾,文件名可以用客戶端的IP命名,log4j會自己找發(fā)送請求的客戶端IP對應(yīng)的那個配置文件,如172.16.2.46服務(wù)器發(fā)送的socket請求會尋找172.16.2.46.lcf配置文件,并根據(jù)配置將日志寫入對應(yīng)的文件。

  1. <span style="font-size:12px;">#注意logger后面的值要與client的值相同  
  2. log4j.logger.com.test.core.filter=DEBUG,localLogs  
  3.    
  4. log4j.appender.localLogs=org.apache.log4j.DailyRollingFileAppender  
  5. log4j.appender.localLogs.File=/data/logs/request/172.16.2.46/logs.log  
  6. log4j.appender.localLogs.layout=org.apache.log4j.PatternLayout  
  7. log4j.appender.localLogs.layout.ConversionPattern=%d [%t] - %m%n  
  8. log4j.appender.localLogs.DatePattern='.'yyyy-MM-dd'.log'  
  9. </span>  


這樣做的好處是可以根據(jù)不同客戶端,將日志寫入不同的文件夾下的。

其實,配置過程就這么簡單,但是當(dāng)你這么做之后,你會發(fā)現(xiàn)運(yùn)行org.apache.log4j.net.SocketServer后,客戶端向日志服務(wù)器發(fā)送請求時,會報找不到.lcf文件的錯誤,得不到想要的結(jié)果。原因出在org.apache.log4j.net.SocketServer代碼中的一個小bug。 

  1. <span style="font-size:12px;">LoggerRepository configureHierarchy(InetAddress inetAddress)  
  2.   {  
  3.     cat.info("Locating configuration file for " + inetAddress);  
  4.   
  5.     String s = inetAddress.toString();  
  6.     int i = s.indexOf("/");  
  7.     if (i == -1) {  
  8.       cat.warn("Could not parse the inetAddress [" + inetAddress + "]. Using default hierarchy.");  
  9.   
  10.       return genericHierarchy();  
  11.     }  
  12.     String key = s.substring(0,i);  
  13.   
  14.     File configFile = new File(this.dir, key + CONFIG_FILE_EXT);  
  15.     if (configFile.exists()) {  
  16.       Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));  
  17.       this.hierarchyMap.put(inetAddress, h);  
  18.   
  19.       new PropertyConfigurator().doConfigure(configFile.getAbsolutePath(), h);  
  20.   
  21.       return h;  
  22.     }  
  23.     cat.warn("Could not find config file [" + configFile + "].");  
  24.     return genericHierarchy();  
  25.   }</span>  

String key = s.substring(0, i);換成String key = s.substring(i+1);就好了。這段代碼是解析IP地址,然后尋找對應(yīng)IP命名的.lcf配置文件;如果找不到,則解析默認(rèn)的generic.lcf。由于截取的錯誤,導(dǎo)致找不到172.16.2.46.lcf,文件夾下又沒有g(shù)eneric.lcf,所以會拋異常。

org.apache.log4j.net.SocketServer代碼中的另外一個bug是,只能接收來自一臺客戶端的日志請求,一旦客戶端停止運(yùn)行,SocketServer也將關(guān)閉。查看代碼:

  1. public static void main(String[] argv)  
  2.   {     
  3.       if (argv.length == 3)  
  4.       init(argv[0], argv[1], argv[2]);  
  5.     else  
  6.       usage("Wrong number of arguments.");  
  7.     try  
  8.     {  
  9.         cat.info("Listening on port " + port);  
  10.         ServerSocket serverSocket = new ServerSocket(port);  
  11.         cat.info("Waiting to accept a new client.");  
  12.     Socket socket = serverSocket.accept();  
  13.     InetAddress inetAddress = socket.getInetAddress();  
  14.     cat.info("Connected to client at " + inetAddress);  
  15.       
  16.     LoggerRepository h = (LoggerRepository)server.hierarchyMap.get(inetAddress);  
  17.     if (h == null) {  
  18.             h = server.configureHierarchy(inetAddress);  
  19.     }  
  20.       
  21.     cat.info("Starting new socket node.");  
  22.     new Thread(new SocketNode(socket, h)).start();  
  23.       }  
  24.     catch (Exception e)  
  25.     {  
  26.       e.printStackTrace();  
  27.     }  
  28.   }  

問題出在只建立了一個socket連接就不在accept了,加上while循環(huán)問題就解決了。

  1. ServerSocket serverSocket = new ServerSocket(port);  
  2. while(true){  
  3.  cat.info("Waiting to accept a new client.");  
  4.  Socket socket = serverSocket.accept();  
  5.  InetAddress inetAddress = socket.getInetAddress();  
  6.  cat.info("Connected to client at " + inetAddress);  
  7.   
  8.  LoggerRepository h = (LoggerRepository)server.hierarchyMap.get(inetAddress);  
  9.  if (h == null) {  
  10.    h = server.configureHierarchy(inetAddress);  
  11.  }  
  12.   
  13.  cat.info("Starting new socket node.");  
  14.  new Thread(new SocketNode(socket, h)).start();  
  15. }  



 

 

好了。Log4j的配置到此結(jié)束。

最后一個問題,日志服務(wù)器是linux,需要有一個統(tǒng)一的start、shutdown命令來啟動和關(guān)閉org.apache.log4j.net.SocketServer。那就需要些shell命令了,下面這段代碼參考了http://www.cnblogs.com/baibaluo/archive/2011/08/31/2160934.html

catalina.sh

  1. <span style="font-size:12px;">#!/bin/bash  
  2. #端口  
  3. LISTEN_PORT=4560  
  4. #服務(wù)端log4j配置文件  
  5. SERVER_CONFIG=/log4jsocket/server.properties  
  6. #客戶端的配置  
  7. CLIENT_CONFIG_DIR=/log4jsocket/clientConfig  
  8.   
  9. #Java程序所在的目錄(classes的上一級目錄)  
  10. APP_HOME=/opt/log4jsocket/serverConfig   
  11. #需要啟動的Java主程序(main方法類)  
  12. APP_MAINCLASS=org.apache.log4j.net.SocketServer  
  13.    
  14. #拼湊完整的classpath參數(shù),包括指定lib目錄下所有的jar  
  15. CLASSPATH=$APP_HOME  
  16. for i in "$APP_HOME"/*.jar; do     
  17.     CLASSPATH="$CLASSPATH":"$i"  
  18. done  
  19.   
  20. #JDK所在路徑  
  21. JAVA_HOME="/opt/jdk1.6.0_30"   
  22. #執(zhí)行程序啟動所使用的系統(tǒng)用戶,考慮到安全,推薦不使用root帳號  
  23. RUNNING_USER=root  
  24.    
  25. #java虛擬機(jī)啟動參數(shù)  
  26. JAVA_OPTS="-ms512m -mx512m -Xmn256m -Djava.awt.headless=true -XX:MaxPermSize=128m"   
  27.   
  28. #初始化psid變量(全局)  
  29. psid=0  
  30.    
  31. checkpid() {  
  32.    javaps=`$JAVA_HOME/bin/jps -l | grep $APP_MAINCLASS`  
  33.    
  34.    if [ -n "$javaps" ]; then  
  35.       psid=`echo $javaps | awk '{print $1}'`  
  36.    else  
  37.       psid=0  
  38.    fi  
  39. }  
  40.   
  41. start() {  
  42.    checkpid  
  43.    
  44.    if [ $psid -ne 0 ]; then  
  45.       echo "================================"  
  46.       echo "warn: $APP_MAINCLASS already started! (pid=$psid)"  
  47.       echo "================================"  
  48.    else  
  49.       echo -n "Starting $APP_MAINCLASS ..."  
  50.       JAVA_CMD="nohup $JAVA_HOME/bin/java -classpath $CLASSPATH $APP_MAINCLASS $LISTEN_PORT $SERVER_CONFIG $CLIENT_CONFIG_DIR >/dev/null 2>&1 &"  
  51.       su - $RUNNING_USER -c "$JAVA_CMD"  
  52.       checkpid  
  53.       if [ $psid -ne 0 ]; then  
  54.          echo "(pid=$psid) [OK]"  
  55.       else  
  56.          echo "[Failed]"  
  57.       fi  
  58.    fi  
  59. }  
  60.   
  61. stop() {  
  62.    checkpid  
  63.    
  64.    if [ $psid -ne 0 ]; then  
  65.       echo -n "Stopping $APP_MAINCLASS ...(pid=$psid) "  
  66.       su - $RUNNING_USER -c "kill -9 $psid"  
  67.       if [ $? -eq 0 ]; then  
  68.          echo "[OK]"  
  69.       else  
  70.          echo "[Failed]"  
  71.       fi  
  72.    
  73.       checkpid  
  74.       if [ $psid -ne 0 ]; then  
  75.          stop  
  76.       fi  
  77.    else  
  78.       echo "================================"  
  79.       echo "warn: $APP_MAINCLASS is not running"  
  80.       echo "================================"  
  81.    fi  
  82. }  
  83.   
  84. status() {  
  85.    checkpid  
  86.    
  87.    if [ $psid -ne 0 ];  then  
  88.       echo "$APP_MAINCLASS is running! (pid=$psid)"  
  89.    else  
  90.       echo "$APP_MAINCLASS is not running"  
  91.    fi  
  92. }  
  93. info() {  
  94.    echo "System Information:"  
  95.    echo "****************************"  
  96.    echo `head -n 1 /etc/issue`  
  97.    echo `uname -a`  
  98.    echo  
  99.    echo "JAVA_HOME=$JAVA_HOME"  
  100.    echo `$JAVA_HOME/bin/java -version`  
  101.    echo  
  102.    echo "APP_HOME=$APP_HOME"  
  103.    echo "APP_MAINCLASS=$APP_MAINCLASS"  
  104.    echo "****************************"  
  105. }  
  106. case "$1" in  
  107.   
  108.    'start')  
  109.       start  
  110.       ;;  
  111.    'stop')  
  112.      stop  
  113.      ;;  
  114.    'restart')  
  115.      stop  
  116.      start  
  117.      ;;  
  118.    'status')  
  119.      status  
  120.      ;;  
  121.    'info')  
  122.      info  
  123.      ;;  
  124.   *)  
  125.      echo "Usage: $0 {start|stop|restart|status|info}"   
  126.      exit 0   
  127. esac  
  128. </span>  
startup.sh

  1. <span style="font-size:12px;">#!/bin/sh  
  2. EXECUTABLE=/log4jsocket/catalina.sh  
  3. exec "$EXECUTABLE" start "$@"</span>  
shutdown.sh

  1. <span style="font-size:12px;">EXECUTABLE=/log4jsocket/catalina.sh  
  2. exec "$EXECUTABLE" stop "$@"</span>  


PS: csdn博客啥時可以上傳附件啊


版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。