log4j日誌服務器支持分佈式和集羣

1. 將 log4j-1.2.16.jar 的源碼解壓,導入java工程
2. 設置 org.apache.log4j.net.SocketServer 類的啓動參數:

        4712 C:\Users\logger\log4j-reciever.properties C:\Users\logger\clientConfig

    2.1 第一個參數爲服務端口號,第二個參數爲服務端加載的默認log4j配置文件,第三個參數爲設置客戶端服務ip及應用日誌配置目錄
    2.2 在 C:\Users\logger\clientConfig 目錄中的文件夾  127.0.0.1  爲客戶端ip,每個客戶端ip單獨作爲目錄存在,用於存放該客戶端中的應用配置
    2.3 在 C:\Users\logger\clientConfig\127.0.0.1\config\AccountBusiness.lcf 文件爲 127.0.0.1 客戶端,AccountBusiness 應用的日誌配置信息
3. 以上配置需要對 org.apache.log4j.net.SocketServer 和 org.apache.log4j.net.SocketNode 類進行自定義修改,源碼在附件中
4. AccountBusiness.lcf 配置文件內容,需要與客戶端配置的rootLogger一致,服務端會將客戶端的 LoggingEvent 進行處理,在服務端作爲日誌輸出,客戶端的stdout 會在服務端按照服務端的 stdout 配置進行輸出

 客戶端日誌配置( log4j.appender.localLogs.application=AccountBusiness  設置每個客戶端的應用名稱 ):

 log4j.rootLogger=info,stdout,ErrorLogFile,localLogs
#log4j.rootLogger=info,stdout

#log level
log4j.logger.com.opensymphony=error
log4j.logger.com.dafy=info
log4j.logger.com.cdoframework=info
log4j.logger.net.spy.memcached=error
log4j.logger.org.apache.velocity=error

# socket log
log4j.appender.localLogs=org.apache.log4j.net.SocketAppender
log4j.appender.localLogs.Port=4712
log4j.appender.localLogs.RemoteHost=localhost
log4j.appender.localLogs.ReconnectionDelay=10000
log4j.appender.localLogs.application=AccountBusiness
log4j.appender.localLogs.LocationInfo=true

##info
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\:SSS}%5p [AccountBusiness,%C,%L]\: %m%n

##error
log4j.appender.ErrorLogFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.ErrorLogFile.File=${catalina.base}/logs/AccountBusinessError.log
log4j.appender.ErrorLogFile.DatePattern='.'yyyy-MM-dd
log4j.appender.ErrorLogFile.Append=true
log4j.appender.ErrorLogFile.Threshold=info
log4j.appender.ErrorLogFile.layout=org.apache.log4j.PatternLayout
log4j.appender.ErrorLogFile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss:SSS}%5p [AccountBusiness,%C,%L]: %m%n

服務器端配置:

log4j.rootLogger=info,stdout
   
log4j.logger.com.opensymphony=error
log4j.logger.com.dafy=info
log4j.logger.com.cdoframework=info
log4j.logger.net.spy.memcached=error
log4j.logger.org.apache.velocity=error   
   
 
log4j.appender.stdout=org.apache.log4j.DailyRollingFileAppender
log4j.appender.stdout.File=C:\\Users\\logger\\clientConfig\\127.0.0.1\\logs\\AccountBusiness.log  
log4j.appender.stdout.DatePattern='.'yyyy-MM-dd
log4j.appender.stdout.Append=true
log4j.appender.stdout.Threshold=info
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss:SSS}%5p [AccountBusiness,%C,%L]: %m%n  


SocketNode.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.log4j.net;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.ObjectInputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Hashtable;

import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggerRepository;
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 hierarchyMap;

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

  public SocketNode(Socket socket, Hashtable hierarchyMap) {
    this.socket = socket;
    this.hierarchyMap = hierarchyMap;
    try {
      ois = new ObjectInputStream(
                         new BufferedInputStream(socket.getInputStream()));
    } catch(InterruptedIOException e) {
      Thread.currentThread().interrupt();
      logger.error("Could not open ObjectInputStream to "+socket, e);
    } catch(IOException e) {
      logger.error("Could not open ObjectInputStream to "+socket, e);
    } catch(RuntimeException 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();
	        // IP
	        InetAddress inetAddress =  socket.getInetAddress();
	        String ip = inetAddress.getHostAddress();
	        // Application
	        String application = event.getMDC("application").toString();
	        
	        LoggerRepository hierarchy = (LoggerRepository) hierarchyMap.get(ip+application);
	        
	        // get a logger from the hierarchy. The name of the logger is taken to be the name contained in the event.
	        remoteLogger = hierarchy.getLogger(event.getLoggerName());
	        
	        //event.logger = remoteLogger;
	        // apply the logger-level filter
	        if(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(InterruptedIOException e) {
      Thread.currentThread().interrupt();
      logger.info("Caught java.io.InterruptedIOException: "+e);
      logger.info("Closing connection.");
    } 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(InterruptedIOException e) {
            Thread.currentThread().interrupt();
        } catch(IOException ex) {
        }
      }
    }
  }
}


SocketServer.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.log4j.net;

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.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.RootLogger;


/**
   A {@link SocketNode} based server that uses a different hierarchy
   for each client.

   <pre>
     <b>Usage:</b> java org.apache.log4j.net.SocketServer port configFile configDir

     where <b>port</b> is a part number where the server listens,
           <b>configFile</b> is a configuration file fed to the {@link PropertyConfigurator} and
           <b>configDir</b> is a path to a directory containing configuration files, possibly one for each client host.
     </pre>

     <p>The <code>configFile</code> is used to configure the log4j
     default hierarchy that the <code>SocketServer</code> will use to
     report on its actions.

     <p>When a new connection is opened from a previously unknown
     host, say <code>foo.bar.net</code>, then the
     <code>SocketServer</code> will search for a configuration file
     called <code>foo.bar.net.lcf</code> under the directory
     <code>configDir</code> that was passed as the third argument. If
     the file can be found, then a new hierarchy is instantiated and
     configured using the configuration file
     <code>foo.bar.net.lcf</code>. If and when the host
     <code>foo.bar.net</code> opens another connection to the server,
     then the previously configured hierarchy is used.

     <p>In case there is no file called <code>foo.bar.net.lcf</code>
     under the directory <code>configDir</code>, then the
     <em>generic</em> hierarchy is used. The generic hierarchy is
     configured using a configuration file called
     <code>generic.lcf</code> under the <code>configDir</code>
     directory. If no such file exists, then the generic hierarchy will be
     identical to the log4j default hierarchy.

     <p>Having different client hosts log using different hierarchies
     ensures the total independence of the clients with respect to
     their logging settings.

     <p>Currently, the hierarchy that will be used for a given request
     depends on the IP address of the client host. For example, two
     separate applicatons running on the same host and logging to the
     same server will share the same hierarchy. This is perfectly safe
     except that it might not provide the right amount of independence
     between applications. The <code>SocketServer</code> is intended
     as an example to be enhanced in order to implement more elaborate
     policies.


    @author  Ceki Gülcü

    @since 1.0 */

public class SocketServer  {

  static String GENERIC = "generic";
  static String CONFIG_FILE_EXT = ".lcf";
  static String CONFIG_DIR = "config";

  static Logger cat = Logger.getLogger(SocketServer.class);
  static SocketServer server;
  static int port;

  // key=inetAddress, value=hierarchy
  Hashtable hierarchyMap;
  LoggerRepository genericHierarchy;
  File dir;

  public
  static
  void main(String argv[]) {
    if(argv.length == 3)
      init(argv[0], argv[1], argv[2]);
    else
      usage("Wrong number of arguments.");

    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);

//	LoggerRepository h = (LoggerRepository) server.hierarchyMap.get(inetAddress);
//	if(h == null) {
//	  h = server.configureHierarchy(inetAddress);
//	}
	if(server.hierarchyMap.isEmpty()){
		server.configureHierarchy(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 portStr, String configFile, String dirStr) {
    try {
      port = Integer.parseInt(portStr);
    }
    catch(java.lang.NumberFormatException e) {
      e.printStackTrace();
      usage("Could not interpret port number ["+ portStr +"].");
    }

    PropertyConfigurator.configure(configFile);

    File dir = new File(dirStr);
    if(!dir.isDirectory()) {
      usage("["+dirStr+"] is not a directory.");
    }
    server = new SocketServer(dir);
  }


  public
  SocketServer(File directory) {
    this.dir = directory;
    hierarchyMap = new Hashtable(11);
  }

  // This method assumes that there is no hiearchy for inetAddress
  // yet. It will configure one and return it.
  void configureHierarchy(InetAddress inetAddress) {
    cat.info("Locating configuration file for "+inetAddress);
    // We assume that the toSting method of InetAddress returns is in
    // the format hostname/d1.d2.d3.d4 e.g. torino/192.168.1.1
//    String s = inetAddress.toString();
//    int i = s.indexOf("/");
//    if(i == -1) {
//      cat.warn("Could not parse the inetAddress ["+inetAddress+
//	       "]. Using default hierarchy.");
//      return genericHierarchy();
//    } else {
    	String key = inetAddress.getHostAddress();  
    	File configDir = new File(dir, key + File.separator + CONFIG_DIR);
    	if(configDir.exists() && configDir.isDirectory()){
    		File[] configFiles = configDir.listFiles();
    		for(int j = 0;j < configFiles.length;j++){
    			File configFile = configFiles[j];
    			String fileNameAll = configFile.getName();
    			String fileName = fileNameAll.substring(0, fileNameAll.indexOf("."));
    			
    			Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));
    			hierarchyMap.put(key+fileName, h);
    			new PropertyConfigurator().doConfigure(configFile.getAbsolutePath(), h);
    		}
    	}

//      File configFile = new File(dir, key+CONFIG_FILE_EXT);
//      if(configFile.exists()) {
//	Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));
//	hierarchyMap.put(inetAddress, h);
//
//	new PropertyConfigurator().doConfigure(configFile.getAbsolutePath(), h);
//
//	return h;
//      } else {
//	cat.warn("Could not find config file ["+configFile+"].");
//	return genericHierarchy();
//      }
//    }
  }

  LoggerRepository  genericHierarchy() {
    if(genericHierarchy == null) {
      File f = new File(dir, GENERIC+CONFIG_FILE_EXT);
      if(f.exists()) {
	genericHierarchy = new Hierarchy(new RootLogger(Level.DEBUG));
	new PropertyConfigurator().doConfigure(f.getAbsolutePath(), genericHierarchy);
      } else {
	cat.warn("Could not find config file ["+f+
		 "]. Will use the default hierarchy.");
	genericHierarchy = LogManager.getLoggerRepository();
      }
    }
    return genericHierarchy;
  }
}






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