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

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@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@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.")); + } +}
participants (1)
-
noreply.seb@qos.ch