[LOGBack-dev] svn commit: r480 - in logback/trunk/logback-classic/src: main/java/ch/qos/logback/classic main/java/ch/qos/logback/classic/net test/input/socket test/java/ch/qos/logback/classic/net

noreply.seb at qos.ch noreply.seb at qos.ch
Thu Aug 24 10:02:37 CEST 2006


Author: seb
Date: Thu Aug 24 10:02:35 2006
New Revision: 480

Added:
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SimpleSocketServer.java
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketAppender.java
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketNode.java
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketServer.java
   logback/trunk/logback-classic/src/test/input/socket/
   logback/trunk/logback-classic/src/test/input/socket/clientConfig.xml
   logback/trunk/logback-classic/src/test/input/socket/serverConfig.xml
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTest.java
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketMin.java
Modified:
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/Logger.java
Log:
SocketAppender: work in progress
Logger: had to make public method getEffectiveLevel()

Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/Logger.java
==============================================================================
--- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/Logger.java	(original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/Logger.java	Thu Aug 24 10:02:35 2006
@@ -77,7 +77,7 @@
     instanceCount++;
   }
 
-  final Level getEffectiveLevel() {
+  public final Level getEffectiveLevel() {
     return Level.toLevel(effectiveLevelInt);
   }
 

Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SimpleSocketServer.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SimpleSocketServer.java	Thu Aug 24 10:02:35 2006
@@ -0,0 +1,95 @@
+/**
+ * Logback: the reliable, generic, fast and flexible logging framework.
+ * 
+ * Copyright (C) 1999-2006, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+package ch.qos.logback.classic.net;
+
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+
+/**
+ * A simple {@link SocketNode} based server.
+ * 
+ * <pre>
+ *   &lt;b&gt;Usage:&lt;/b&gt; java ch.qos.logback.classic.net.SimpleSocketServer port configFile
+ *  
+ *   where
+ * <em>
+ * port
+ * </em>
+ *   is a part number where the server listens and
+ * <em>
+ * configFile
+ * </em>
+ *   is an xml configuration file fed to {@link JoranConfigurator}.
+ * </pre>
+ * 
+ * @author Ceki G&uuml;lc&uuml;
+ * @author S&eacute;bastien Pennec
+ * 
+ * @since 0.8.4
+ */
+public class SimpleSocketServer {
+
+	static Logger logger = LoggerFactory.getLogger(SimpleSocketServer.class);
+
+	static int port;
+
+	public static void main(String argv[]) {
+		if (argv.length == 2) {
+			init(argv[0], argv[1]);
+		} else {
+			usage("Wrong number of arguments.");
+		}
+
+		try {
+			logger.info("Listening on port " + port);
+			ServerSocket serverSocket = new ServerSocket(port);
+			while (true) {
+				logger.info("Waiting to accept a new client.");
+				Socket socket = serverSocket.accept();
+				logger.info("Connected to client at " + socket.getInetAddress());
+				logger.info("Starting new socket node.");
+				LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();	
+				new Thread(new SocketNode(socket, lc))
+						.start();
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	static void usage(String msg) {
+		System.err.println(msg);
+		System.err.println("Usage: java " + SimpleSocketServer.class.getName()
+				+ " port configFile");
+		System.exit(1);
+	}
+
+	static void init(String portStr, String configFile) {
+		try {
+			port = Integer.parseInt(portStr);
+		} catch (java.lang.NumberFormatException e) {
+			e.printStackTrace();
+			usage("Could not interpret port number [" + portStr + "].");
+		}
+
+		if (configFile.endsWith(".xml")) {
+			LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();	
+			JoranConfigurator configurator = new JoranConfigurator();
+			configurator.setContext(lc);
+			configurator.doConfigure(configFile);
+		}
+	}
+}

Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketAppender.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketAppender.java	Thu Aug 24 10:02:35 2006
@@ -0,0 +1,396 @@
+/**
+ * Logback: the reliable, generic, fast and flexible logging framework.
+ * 
+ * Copyright (C) 1999-2006, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+
+// Contributors: Dan MacDonald <dan at redknee.com>
+package ch.qos.logback.classic.net;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+import ch.qos.logback.core.Layout;
+
+/**
+ * Sends {@link LoggingEvent} objects to a remote a log server, usually a
+ * {@link SocketNode}.
+ * 
+ * <p>
+ * The SocketAppender has the following properties:
+ * 
+ * <ul>
+ * 
+ * <p>
+ * <li>If sent to a {@link SocketNode}, remote logging is non-intrusive as far
+ * as the log event is concerned. In other words, the event will be logged with
+ * the same time stamp, {@link org.apache.log4j.NDC}, location info as if it
+ * were logged locally by the client.
+ * 
+ * <p>
+ * <li>SocketAppenders do not use a layout. They ship a serialized
+ * {@link LoggingEvent} object to the server side.
+ * 
+ * <p>
+ * <li>Remote logging uses the TCP protocol. Consequently, if the server is
+ * reachable, then log events will eventually arrive at the server.
+ * 
+ * <p>
+ * <li>If the remote server is down, the logging requests are simply dropped.
+ * However, if and when the server comes back up, then event transmission is
+ * resumed transparently. This transparent reconneciton is performed by a
+ * <em>connector</em> thread which periodically attempts to connect to the
+ * server.
+ * 
+ * <p>
+ * <li>Logging events are automatically <em>buffered</em> by the native TCP
+ * implementation. This means that if the link to server is slow but still
+ * faster than the rate of (log) event production by the client, the client will
+ * not be affected by the slow network connection. However, if the network
+ * connection is slower then the rate of event production, then the client can
+ * only progress at the network rate. In particular, if the network link to the
+ * the server is down, the client will be blocked.
+ * 
+ * <p>
+ * On the other hand, if the network link is up, but the server is down, the
+ * client will not be blocked when making log requests but the log events will
+ * be lost due to server unavailability.
+ * 
+ * <p>
+ * <li>Even if a <code>SocketAppender</code> is no longer attached to any
+ * category, it will not be garbage collected in the presence of a connector
+ * thread. A connector thread exists only if the connection to the server is
+ * down. To avoid this garbage collection problem, you should {@link #close} the
+ * the <code>SocketAppender</code> explicitly. See also next item.
+ * 
+ * <p>
+ * Long lived applications which create/destroy many <code>SocketAppender</code>
+ * instances should be aware of this garbage collection problem. Most other
+ * applications can safely ignore it.
+ * 
+ * <p>
+ * <li>If the JVM hosting the <code>SocketAppender</code> exits before the
+ * <code>SocketAppender</code> is closed either explicitly or subsequent to
+ * garbage collection, then there might be untransmitted data in the pipe which
+ * might be lost. This is a common problem on Windows based systems.
+ * 
+ * <p>
+ * To avoid lost data, it is usually sufficient to {@link #close} the
+ * <code>SocketAppender</code> either explicitly or by calling the
+ * {@link org.apache.log4j.LogManager#shutdown} method before exiting the
+ * application.
+ * 
+ * 
+ * </ul>
+ * 
+ * @author Ceki G&uuml;lc&uuml;
+ * @author S&eacute;bastien Pennec
+ * 
+ * @since 0.8.4
+ */
+
+public class SocketAppender extends AppenderBase {
+
+	/**
+	 * The default port number of remote logging server (4560).
+	 */
+	static final int DEFAULT_PORT = 4560;
+
+	/**
+	 * The default reconnection delay (30000 milliseconds or 30 seconds).
+	 */
+	static final int DEFAULT_RECONNECTION_DELAY = 30000;
+
+	/**
+	 * We remember host name as String in addition to the resolved InetAddress so
+	 * that it can be returned via getOption().
+	 */
+	String remoteHost;
+
+	InetAddress address;
+	int port = DEFAULT_PORT;
+	ObjectOutputStream oos;
+	int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
+
+	private Connector connector;
+
+	int counter = 0;
+
+	Layout layout;
+	
+	// reset the ObjectOutputStream every 70 calls
+	// private static final int RESET_FREQUENCY = 70;
+	private static final int RESET_FREQUENCY = 1;
+
+	public SocketAppender() {
+	}
+
+	/**
+	 * Connects to remote server at <code>address</code> and <code>port</code>.
+	 */
+	public SocketAppender(InetAddress address, int port) {
+		this.address = address;
+		this.remoteHost = address.getHostName();
+		this.port = port;
+		connect(address, port);
+	}
+
+	/**
+	 * Connects to remote server at <code>host</code> and <code>port</code>.
+	 */
+	public SocketAppender(String host, int port) {
+		this.port = port;
+		this.address = getAddressByName(host);
+		this.remoteHost = host;
+		connect(address, port);
+	}
+
+	/**
+	 * Connect to the specified <b>RemoteHost</b> and <b>Port</b>.
+	 */
+	public void activateOptions() {
+		connect(address, port);
+	}
+	
+	/**
+	 * Start this appender
+	 */
+	public void start() {
+		//TODO More tests before starting the Appender.
+		this.started = true;
+	}
+	
+
+	/**
+	 * Strop this appender.
+	 * 
+	 * <p>
+	 * This will mark the appender as closed and call then {@link #cleanUp}
+	 * method.
+	 */
+	@Override
+	public void stop() {
+		if (!isStarted())
+			return;
+
+		this.started = false;
+		cleanUp();
+	}
+
+	/**
+	 * Drop the connection to the remote host and release the underlying connector
+	 * thread if it has been created
+	 */
+	public void cleanUp() {
+		if (oos != null) {
+			try {
+				oos.close();
+			} catch (IOException e) {
+				addError("Could not close oos.", e);
+			}
+			oos = null;
+		}
+		if (connector != null) {
+			addInfo("Interrupting the connector.");
+			connector.interrupted = true;
+			connector = null; // allow gc
+		}
+	}
+
+	void connect(InetAddress address, int port) {
+		if (this.address == null)
+			return;
+		try {
+			// First, close the previous connection if any.
+			cleanUp();
+			oos = new ObjectOutputStream(new Socket(address, port).getOutputStream());
+		} catch (IOException e) {
+
+			String msg = "Could not connect to remote log4j server at ["
+					+ address.getHostName() + "].";
+			if (reconnectionDelay > 0) {
+				msg += " We will try again later.";
+				fireConnector(); // fire the connector thread
+			}
+			addError(msg, e);
+		}
+	}
+
+	@Override
+	protected void append(Object event) {
+		
+		if (event == null)
+			return;
+
+		if (address == null) {
+			addError("No remote host is set for SocketAppender named \"" + this.name
+					+ "\".");
+			return;
+		}
+
+		if (oos != null) {
+			try {
+				oos.writeObject(event);
+				addInfo("=========Flushing.");
+				oos.flush();
+				if (++counter >= RESET_FREQUENCY) {
+					counter = 0;
+					// Failing to reset the object output stream every now and
+					// then creates a serious memory leak.
+					// System.err.println("Doing oos.reset()");
+					oos.reset();
+				}
+			} catch (IOException e) {
+				oos = null;
+				addWarn("Detected problem with connection: " + e);
+				if (reconnectionDelay > 0) {
+					fireConnector();
+				}
+			}
+		}
+	}
+
+	void fireConnector() {
+		if (connector == null) {
+			addInfo("Starting a new connector thread.");
+			connector = new Connector();
+			connector.setDaemon(true);
+			connector.setPriority(Thread.MIN_PRIORITY);
+			connector.start();
+		}
+	}
+
+	static InetAddress getAddressByName(String host) {
+		try {
+			return InetAddress.getByName(host);
+		} catch (Exception e) {
+			// addError("Could not find address of [" + host + "].", e);
+			return null;
+		}
+	}
+
+	/**
+	 * The SocketAppender does not use a layout. Hence, this method returns
+	 * <code>false</code>.
+	 */
+	public boolean requiresLayout() {
+		return false;
+	}
+
+	/**
+	 * The <b>RemoteHost</b> option takes a string value which should be the host
+	 * name of the server where a {@link SocketNode} is running.
+	 */
+	public void setRemoteHost(String host) {
+		address = getAddressByName(host);
+		remoteHost = host;
+	}
+
+	/**
+	 * Returns value of the <b>RemoteHost</b> option.
+	 */
+	public String getRemoteHost() {
+		return remoteHost;
+	}
+
+	/**
+	 * The <b>Port</b> option takes a positive integer representing the port
+	 * where the server is waiting for connections.
+	 */
+	public void setPort(int port) {
+		this.port = port;
+	}
+
+	/**
+	 * Returns value of the <b>Port</b> option.
+	 */
+	public int getPort() {
+		return port;
+	}
+
+	/**
+	 * The <b>ReconnectionDelay</b> option takes a positive integer representing
+	 * the number of milliseconds to wait between each failed connection attempt
+	 * to the server. The default value of this option is 30000 which corresponds
+	 * to 30 seconds.
+	 * 
+	 * <p>
+	 * Setting this option to zero turns off reconnection capability.
+	 */
+	public void setReconnectionDelay(int delay) {
+		this.reconnectionDelay = delay;
+	}
+
+	/**
+	 * Returns value of the <b>ReconnectionDelay</b> option.
+	 */
+	public int getReconnectionDelay() {
+		return reconnectionDelay;
+	}
+
+	public Layout getLayout() {
+		return layout;
+	}
+
+	public void setLayout(Layout layout) {
+		this.layout = layout;
+	}
+
+	/**
+	 * The Connector will reconnect when the server becomes available again. It
+	 * does this by attempting to open a new connection every
+	 * <code>reconnectionDelay</code> milliseconds.
+	 * 
+	 * <p>
+	 * It stops trying whenever a connection is established. It will restart to
+	 * try reconnect to the server when previpously open connection is droppped.
+	 * 
+	 * @author Ceki G&uuml;lc&uuml;
+	 * @since 0.8.4
+	 */
+	class Connector extends Thread {
+
+		boolean interrupted = false;
+
+		public void run() {
+			Socket socket;
+			while (!interrupted) {
+				try {
+					sleep(reconnectionDelay);
+					addInfo("Attempting connection to " + address.getHostName());
+					socket = new Socket(address, port);
+					synchronized (this) {
+						oos = new ObjectOutputStream(socket.getOutputStream());
+						connector = null;
+						addInfo("Connection established. Exiting connector thread.");
+						break;
+					}
+				} catch (InterruptedException e) {
+					addInfo("Connector interrupted. Leaving loop.");
+					return;
+				} catch (java.net.ConnectException e) {
+					addInfo("Remote host " + address.getHostName()
+							+ " refused connection.");
+				} catch (IOException e) {
+					addInfo("Could not connect to " + address.getHostName()
+							+ ". Exception is " + e);
+				}
+			}
+			// addInfo("Exiting Connector.run() method.");
+		}
+
+		/**
+		 * public void finalize() { LogLog.debug("Connector finalize() has been
+		 * called."); }
+		 */
+	}
+
+}

Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketNode.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketNode.java	Thu Aug 24 10:02:35 2006
@@ -0,0 +1,99 @@
+/**
+ * Logback: the reliable, generic, fast and flexible logging framework.
+ * 
+ * Copyright (C) 1999-2006, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+
+package ch.qos.logback.classic.net;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.net.Socket;
+
+import ch.qos.logback.classic.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.LoggingEvent;
+
+// Contributors: Moses Hohman <mmhohman at rainbow.uchicago.edu>
+
+/**
+ * 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&uuml;lc&uuml;
+ * @author S&eacute;bastien Pennec
+ * 
+ * @since 0.8.4
+ */
+public class SocketNode implements Runnable {
+
+	Socket socket;
+	LoggerContext context;
+	ObjectInputStream ois;
+
+	static Logger logger = (Logger) LoggerFactory.getLogger(SocketNode.class);
+
+	public SocketNode(Socket socket, LoggerContext context) {
+		this.socket = socket;
+		this.context = context;
+		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 {
+			while (true) {
+				// read an event from the wire
+				event = (LoggingEvent) ois.readObject();
+				// get a logger from the hierarchy. The name of the logger is taken to
+				// be the name contained in the event.
+				remoteLogger = context.getLogger(event.getLogger().getName());
+				// 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 (IOException e) {
+			logger.info("Caught java.io.IOException: " + e);
+			logger.info("Closing connection.");
+		} catch (Exception e) {
+			logger.error("Unexpected exception. Closing conneciton.", e);
+		}
+
+		try {
+			ois.close();
+		} catch (Exception e) {
+			logger.info("Could not close connection.", e);
+		}
+	}
+}

Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketServer.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketServer.java	Thu Aug 24 10:02:35 2006
@@ -0,0 +1,203 @@
+/**
+ * Logback: the reliable, generic, fast and flexible logging framework.
+ * 
+ * Copyright (C) 1999-2006, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+
+package ch.qos.logback.classic.net;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Hashtable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+
+/**
+ * A {@link SocketNode} based server that uses a different hierarchy for each
+ * client.
+ * 
+ * <pre>
+ *    &lt;b&gt;Usage:&lt;/b&gt; java ch.qos.logback.classic.net.SocketServer port configFile configDir
+ *   
+ *    where &lt;b&gt;port&lt;/b&gt; is a part number where the server listens,
+ *    &lt;b&gt;configFile&lt;/b&gt; is an xml configuration file fed to the {@link JoranConfigurator} and
+ *    &lt;b&gt;configDir&lt;/b&gt; 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&uuml;lc&uuml;
+ * 
+ * @since 1.0
+ */
+
+public class SocketServer {
+
+	static String GENERIC = "generic";
+	static String CONFIG_FILE_EXT = ".lcf";
+
+	static Logger logger = LoggerFactory.getLogger(SocketServer.class);
+	static SocketServer server;
+	static int port;
+
+	// key=inetAddress, value=hierarchy
+	Hashtable<InetAddress, LoggerContext> hierarchyMap;
+	LoggerContext 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 {
+			logger.info("Listening on port " + port);
+			ServerSocket serverSocket = new ServerSocket(port);
+			while (true) {
+				logger.info("Waiting to accept a new client.");
+				Socket socket = serverSocket.accept();
+				InetAddress inetAddress = socket.getInetAddress();
+				logger.info("Connected to client at " + inetAddress);
+
+				LoggerContext h = (LoggerContext) server.hierarchyMap.get(inetAddress);
+				if (h == null) {
+					h = server.configureHierarchy(inetAddress);
+				}
+
+				logger.info("Starting new socket node.");
+				new Thread(new SocketNode(socket, h)).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 + "].");
+		}
+
+		LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+		JoranConfigurator configurator = new JoranConfigurator();
+		configurator.setContext(lc);
+		configurator.doConfigure(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<InetAddress, LoggerContext>(11);
+	}
+
+	// This method assumes that there is no hiearchy for inetAddress
+	// yet. It will configure one and return it.
+	LoggerContext configureHierarchy(InetAddress inetAddress) {
+		logger.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) {
+			logger.warn("Could not parse the inetAddress [" + inetAddress
+					+ "]. Using default hierarchy.");
+			return genericHierarchy();
+		} else {
+			String key = s.substring(0, i);
+
+			File configFile = new File(dir, key + CONFIG_FILE_EXT);
+			if (configFile.exists()) {
+				LoggerContext lc = new LoggerContext();
+				hierarchyMap.put(inetAddress, lc);
+
+				JoranConfigurator configurator = new JoranConfigurator();
+				configurator.setContext(lc);
+				configurator.doConfigure(configFile);
+
+				return lc;
+			} else {
+				logger.warn("Could not find config file [" + configFile + "].");
+				return genericHierarchy();
+			}
+		}
+	}
+
+	LoggerContext genericHierarchy() {
+		if (genericHierarchy == null) {
+			File f = new File(dir, GENERIC + CONFIG_FILE_EXT);
+			if (f.exists()) {
+				genericHierarchy = new LoggerContext();
+				JoranConfigurator configurator = new JoranConfigurator();
+				configurator.setContext(genericHierarchy);
+				configurator.doConfigure(f.getAbsolutePath());
+
+			} else {
+				logger.warn("Could not find config file [" + f
+						+ "]. Will use the default hierarchy.");
+				genericHierarchy = new LoggerContext();
+			}
+		}
+		return genericHierarchy;
+	}
+}

Added: logback/trunk/logback-classic/src/test/input/socket/clientConfig.xml
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/input/socket/clientConfig.xml	Thu Aug 24 10:02:35 2006
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<configuration>
+
+	<appender name="STDOUT"
+		class="ch.qos.logback.core.ConsoleAppender">
+		<layout class="ch.qos.logback.classic.PatternLayout">
+			<param name="pattern"
+				value="CLI: %-4relative [%thread] %-5level %class - %msg%n" />
+		</layout>
+	</appender>
+
+	<appender name="SOCKET"
+		class="ch.qos.logback.classic.net.SocketAppender">
+		<param name="remoteHost" value="127.0.0.1" />
+		<layout class="ch.qos.logback.classic.PatternLayout">
+			<param name="pattern"
+				value="SO: %-4relative [%thread] %-5level %class - %msg%n" />
+		</layout>
+	</appender>
+
+	<root>
+		<level value="debug" />
+		<appender-ref ref="STDOUT" />
+		<appender-ref ref="SOCKET" />
+	</root>
+</configuration>

Added: logback/trunk/logback-classic/src/test/input/socket/serverConfig.xml
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/input/socket/serverConfig.xml	Thu Aug 24 10:02:35 2006
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<configuration>
+
+	<appender name="stdout"
+		class="ch.qos.logback.core.ConsoleAppender">
+		<layout class="ch.qos.logback.classic.PatternLayout">
+			<param name="pattern" value="SERV: %p %t %c - %m%n" />
+		</layout>
+	</appender>
+
+	<appender name="Rolling"
+		class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<triggeringPolicy
+			class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
+			<param name="maxFileSize" value="100" />
+		</triggeringPolicy>
+		<param name="file" value="example.log" />
+		<layout class="ch.qos.logback.classic.PatternLayout">
+			<param name="pattern" value="SERV: %p %t %c - %m%n" />
+		</layout>
+	</appender>
+
+	<root>
+		<level value="debug" />
+		<appender-ref ref="stdout" />
+		<appender-ref ref="Rolling" />
+	</root>
+</configuration>
\ No newline at end of file

Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTest.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTest.java	Thu Aug 24 10:02:35 2006
@@ -0,0 +1,36 @@
+package ch.qos.logback.classic.net;
+
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.classic.util.Constants;
+
+public class SocketAppenderTest {
+
+
+	public static void main(String[] args) {
+		
+//		Thread t = new Thread(new Runnable() {
+//			public void run() {
+//				SimpleSocketServer.main(new String[]{"4560", Constants.TEST_DIR_PREFIX + "input/socket/serverConfig.xml"});		
+//			}
+//		});
+		
+//		t.start();
+		
+		Logger logger = (Logger) LoggerFactory.getLogger(SocketAppenderTest.class);
+		LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+		JoranConfigurator configurator = new JoranConfigurator();
+		configurator.setContext(lc);
+		configurator.doConfigure(Constants.TEST_DIR_PREFIX + "input/socket/clientConfig.xml");
+			
+		logger.debug("************* Hello world.");
+		
+//		t.interrupt();
+//		System.exit(0);
+		
+	}
+
+}

Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketMin.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketMin.java	Thu Aug 24 10:02:35 2006
@@ -0,0 +1,105 @@
+/**
+ * Logback: the reliable, generic, fast and flexible logging framework.
+ * 
+ * Copyright (C) 1999-2006, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+
+package ch.qos.logback.classic.net;
+
+import java.io.InputStreamReader;
+
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.BasicConfigurator;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+
+public class SocketMin {
+
+	static Logger logger = (Logger) LoggerFactory.getLogger(SocketMin.class
+			.getName());
+	static SocketAppender s;
+
+	public static void main(String argv[]) {
+		if (argv.length == 3) {
+			init(argv[0], argv[1]);
+		} else {
+			usage("Wrong number of arguments.");
+		}
+
+		// NDC.push("some context");
+		if (argv[2].equals("true")) {
+			loop();
+		} else {
+			test();
+		}
+
+		s.stop();
+	}
+
+	static void usage(String msg) {
+		System.err.println(msg);
+		System.err.println("Usage: java " + SocketMin.class
+				+ " host port true|false");
+		System.exit(1);
+	}
+
+	static void init(String host, String portStr) {
+		Logger root = (Logger) LoggerFactory.getLogger(LoggerContext.ROOT_NAME);
+		BasicConfigurator.configure(root.getLoggerContext());
+		try {
+			int port = Integer.parseInt(portStr);
+			logger.info("Creating socket appender (" + host + "," + port + ").");
+			s = new SocketAppender(host, port);
+			s.setName("S");
+			root.addAppender(s);
+		} catch (java.lang.NumberFormatException e) {
+			e.printStackTrace();
+			usage("Could not interpret port number [" + portStr + "].");
+		} catch (Exception e) {
+			System.err.println("Could not start!");
+			e.printStackTrace();
+			System.exit(1);
+		}
+	}
+
+	static void loop() {
+		Logger root = (Logger) LoggerFactory.getLogger(LoggerContext.ROOT_NAME);
+		InputStreamReader in = new InputStreamReader(System.in);
+		System.out.println("Type 'q' to quit");
+		int i;
+		int k = 0;
+		while (true) {
+			logger.debug("Message " + k++);
+			logger.info("Message " + k++);
+			logger.warn("Message " + k++);
+			logger.error("Message " + k++, new Exception("Just testing"));
+			try {
+				i = in.read();
+			} catch (Exception e) {
+				return;
+			}
+			if (i == -1)
+				break;
+			if (i == 'q')
+				break;
+			if (i == 'r') {
+				System.out.println("Removing appender S");
+				root.detachAppender("S");
+			}
+		}
+	}
+
+	static void test() {
+		int i = 0;
+		logger.debug("Message " + i++);
+		logger.info("Message " + i++);
+		logger.warn("Message " + i++);
+		logger.error("Message " + i++);
+		logger.debug("Message " + i++, new Exception("Just testing."));
+	}
+}



More information about the logback-dev mailing list