[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>
+ * <b>Usage:</b> 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ülcü
+ * @author Sé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ülcü
+ * @author Sé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ülcü
+ * @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ülcü
+ * @author Sé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>
+ * <b>Usage:</b> java ch.qos.logback.classic.net.SocketServer port configFile configDir
+ *
+ * where <b>port</b> is a part number where the server listens,
+ * <b>configFile</b> is an xml configuration file fed to the {@link JoranConfigurator} 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 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