logback-dev
Threads by month
- ----- 2025 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- 9940 discussions

svn commit: r486 - in logback/trunk/logback-classic/src: main/java/ch/qos/logback/classic/net main/java/ch/qos/logback/classic/spi test/java/ch/qos/logback/classic/net
by noreply.seb@qos.ch 29 Aug '06
by noreply.seb@qos.ch 29 Aug '06
29 Aug '06
Author: seb
Date: Tue Aug 29 15:00:10 2006
New Revision: 486
Modified:
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/spi/LoggingEvent.java
logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTestApp.java
Log:
on going work on SocketAppender
Modified: 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/SimpleSocketServer.java (original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SimpleSocketServer.java Tue Aug 29 15:00:10 2006
@@ -22,17 +22,17 @@
* A simple {@link SocketNode} based server.
*
* <pre>
- * <b>Usage:</b> java ch.qos.logback.classic.net.SimpleSocketServer port configFile
- *
- * where
+ * <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
+ * is a part number where the server listens and
* <em>
* configFile
* </em>
- * is an xml configuration file fed to {@link JoranConfigurator}.
+ * is an xml configuration file fed to {@link JoranConfigurator}.
* </pre>
*
* @author Ceki Gülcü
@@ -53,6 +53,10 @@
usage("Wrong number of arguments.");
}
+ runServer();
+ }
+
+ static void runServer() {
try {
logger.info("Listening on port " + port);
ServerSocket serverSocket = new ServerSocket(port);
@@ -61,9 +65,8 @@
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();
+ LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+ new Thread(new SocketNode(socket, lc)).start();
}
} catch (Exception e) {
e.printStackTrace();
@@ -86,7 +89,7 @@
}
if (configFile.endsWith(".xml")) {
- LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+ LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(lc);
configurator.doConfigure(configFile);
Modified: 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/SocketAppender.java (original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketAppender.java Tue Aug 29 15:00:10 2006
@@ -15,7 +15,6 @@
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
-import java.util.Calendar;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.AppenderBase;
@@ -139,7 +138,7 @@
this.address = address;
this.remoteHost = address.getHostName();
this.port = port;
- //connect(address, port);
+ // connect(address, port);
}
/**
@@ -149,15 +148,15 @@
this.port = port;
this.address = getAddressByName(host);
this.remoteHost = host;
- //connect(address, port);
+ // connect(address, port);
}
- // /**
- // * Connect to the specified <b>RemoteHost</b> and <b>Port</b>.
- // */
- // public void activateOptions() {
- // connect(address, port);
- // }
+ // /**
+ // * Connect to the specified <b>RemoteHost</b> and <b>Port</b>.
+ // */
+ // public void activateOptions() {
+ // connect(address, port);
+ // }
/**
* Start this appender.
@@ -226,7 +225,7 @@
oos = new ObjectOutputStream(new Socket(address, port).getOutputStream());
} catch (IOException e) {
- String msg = "Could not connect to remote log4j server at ["
+ String msg = "Could not connect to remote logback server at ["
+ address.getHostName() + "].";
if (reconnectionDelay > 0) {
msg += " We will try again later.";
@@ -236,6 +235,9 @@
}
}
+
+ int count = 0;
+ long total = 0;
@Override
protected void append(Object event) {
@@ -250,11 +252,13 @@
if (oos != null) {
try {
- Long t1 = Calendar.getInstance().getTimeInMillis();
+ Long t1 = System.nanoTime();
oos.writeObject(event);
- Long t2 = Calendar.getInstance().getTimeInMillis();
- addInfo("=========Writing time: " + Long.toString(t2-t1));
- addInfo("=========Flushing.");
+ Long t2 = System.nanoTime();
+ long delta = t2-t1;
+ total += delta;
+ addInfo("** Writing time: " + Long.toString(delta) + " total: " + ++count + " median: " + total/count);
+ //addInfo("=========Flushing.");
oos.flush();
if (++counter >= RESET_FREQUENCY) {
counter = 0;
@@ -293,14 +297,6 @@
}
/**
- * 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.
*/
Modified: 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/SocketNode.java (original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketNode.java Tue Aug 29 15:00:10 2006
@@ -80,14 +80,14 @@
}
}
} catch (java.io.EOFException e) {
- logger.info("Caught java.io.EOFException closing conneciton.");
+ logger.info("Caught java.io.EOFException closing connection.");
} catch (java.net.SocketException e) {
- logger.info("Caught java.net.SocketException closing conneciton.");
+ logger.info("Caught java.net.SocketException closing connection.");
} catch (IOException e) {
logger.info("Caught java.io.IOException: " + e);
logger.info("Closing connection.");
} catch (Exception e) {
- logger.error("Unexpected exception. Closing conneciton.", e);
+ logger.error("Unexpected exception. Closing connection.", e);
}
try {
Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java
==============================================================================
--- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java (original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java Tue Aug 29 15:00:10 2006
@@ -10,10 +10,13 @@
package ch.qos.logback.classic.spi;
-
-import java.io.Serializable;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import ch.qos.logback.classic.Level;
@@ -22,219 +25,253 @@
* The internal representation of logging events. When an affirmative decision
* is made to log then a <code>LoggingEvent</code> instance is created. This
* instance is passed around to the different Logback components.
- *
- * <p>Writers of Logback components such as appenders should be
- * aware of that some of the LoggingEvent fields are initialized lazily.
- * Therefore, an appender wishing to output data to be later correctly read
- * by a receiver, must initialize "lazy" fields prior to writing them out.
- * See the {@link #prepareForDeferredProcessing()} method for the exact list.</p>
+ *
+ * <p>
+ * Writers of Logback components such as appenders should be aware of that some
+ * of the LoggingEvent fields are initialized lazily. Therefore, an appender
+ * wishing to output data to be later correctly read by a receiver, must
+ * initialize "lazy" fields prior to writing them out. See the
+ * {@link #prepareForDeferredProcessing()} method for the exact list.
+ * </p>
*
* @author Ceki Gülcü
*/
-public class LoggingEvent implements Serializable {
+public class LoggingEvent implements Externalizable {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 3022264832697160750L;
+
+ /**
+ *
+ */
+ private static long startTime = System.currentTimeMillis();
+
+ /**
+ * Fully qualified name of the calling Logger class. This field does not
+ * survive serialization.
+ *
+ * <p>
+ * Note that the getCallerInformation() method relies on this fact.
+ */
+ transient String fqnOfLoggerClass;
+
+ /**
+ * The name of thread in which this logging event was generated.
+ */
+ private String threadName;
-
- /**
+ /**
+ * Level of logging event.
+ *
+ * <p>
+ * This field should not be accessed directly. You shoud use the {@link
+ * #getLevel} method instead.
+ * </p>
*
*/
- private static final long serialVersionUID = -7298433437463204531L;
+ private Level level;
+
+ private String message;
+ private Object[] argumentArray;
+
+ private Logger logger;
+
+ private ThrowableInformation throwableInfo;
+
+ private CallerData[] callerDataArray;
+
+ private Marker marker;
/**
- *
- */
- private static long startTime = System.currentTimeMillis();
-
- /**
- * Fully qualified name of the calling Logger class. This field does not
- * survive serialization.
- *
- * <p>Note that the getCallerInformation() method relies on this fact.
- */
- transient String fqnOfLoggerClass;
-
-
- /**
- * The name of thread in which this logging event was generated.
- */
- private String threadName;
-
- /**
- * Level of logging event.
- *
- * <p>
- * This field should not be accessed directly. You shoud use the {@link
- * #getLevel} method instead.
- * </p>
- *
- */
- private Level level;
-
- private String message;
- private Object[] argumentArray;
-
- private Logger logger;
-
- private ThrowableInformation throwableInfo;
-
- private CallerData[] callerDataArray;
-
- private Marker marker;
-
- /**
- * The number of milliseconds elapsed from 1/1/1970 until logging event was
- * created.
- */
- private long timeStamp;
-
- public LoggingEvent(String fqcn, Logger logger, Level level, String message, Throwable throwable) {
- this.fqnOfLoggerClass = fqcn;
- this.logger = logger;
- this.level = level;
- this.message = message;
-
- if (throwable != null) {
- this.throwableInfo = new ThrowableInformation(throwable);
- }
- timeStamp = System.currentTimeMillis();
- }
-
- public void setArgumentArray(Object[] argArray) {
- if (this.argumentArray != null) {
- throw new IllegalStateException("argArray has been already set");
- }
- this.argumentArray = argArray;
- }
-
- public Object[] getArgumentArray() {
- return this.argumentArray;
- }
-
- public Level getLevel() {
- return level;
- }
-
- public String getThreadName() {
- if (threadName == null) {
- threadName = (Thread.currentThread()).getName();
- }
- return threadName;
- }
-
- /**
- * @param threadName The threadName to set.
- * @throws IllegalStateException If threadName has been already set.
- */
- public void setThreadName(String threadName)
- throws IllegalStateException {
- if (this.threadName != null) {
- throw new IllegalStateException("threadName has been already set");
- }
- this.threadName = threadName;
- }
-
- /**
- * Returns the throwable information contained within this event. May be
- * <code>null</code> if there is no such information.
- */
- public ThrowableInformation getThrowableInformation() {
- return throwableInfo;
- }
-
- /**
- * Set this event's throwable information.
- */
- public void setThrowableInformation(ThrowableInformation ti) {
- if (throwableInfo != null) {
- throw new IllegalStateException("ThrowableInformation has been already set.");
- } else {
- throwableInfo = ti;
- }
- }
-
- /**
- * This method should be called prior to serializing an event. It should also
- * be called when using asynchronous logging.
- */
- public void prepareForDeferredProcessing() {
- this.getThreadName();
- }
-
- public Logger getLogger() {
- return logger;
- }
-
- public void setLogger(Logger logger) {
- this.logger = logger;
- }
-
- public String getMessage() {
- return message;
- }
-
- public void setMessage(String message) {
- if (this.message != null) {
- throw new IllegalStateException("The message for this event has been set already.");
- }
- this.message = message;
- }
-
- public long getTimeStamp() {
- return timeStamp;
- }
-
- public void setTimeStamp(long timeStamp) {
- this.timeStamp = timeStamp;
- }
-
- public void setLevel(Level level) {
- if (this.level != null) {
- throw new IllegalStateException("The level has been already set for this event.");
- }
- this.level = level;
- }
-
- /**
- * The time at which this class was loaded into memory, expressed in millisecond
- * elapsed since the epoch (1.1.1970).
- *
- * @return The time as measured when this class was loaded into memory.
- */
- public static final long getStartTime() {
- return startTime;
- }
-
- /**
- * Get the caller information for this logging event. If caller
- * information is null at the time of its invocation, this method extracts
- * location information. The collected information is cached for future use.
- *
- * <p>Note that after serialization it is impossible to correctly extract
- * caller information. </p>
- */
- public CallerData[] getCallerData() {
- // we rely on the fact that fqnOfLoggerClass does not survive
- // serialization
- if (callerDataArray == null && fqnOfLoggerClass != null) {
- callerDataArray = CallerData.extract(new Throwable(), fqnOfLoggerClass);
- }
- return callerDataArray;
- }
-
- public void setCallerInformation(CallerData[] callerDataArray) {
- this.callerDataArray = callerDataArray;
- }
-
- public Marker getMarker() {
- return marker;
- }
-
- public void setMarker(Marker marker) {
- if (this.marker != null) {
- throw new IllegalStateException("The marker has been already set for this event.");
- }
- this.marker = marker;
- }
-
-
+ * The number of milliseconds elapsed from 1/1/1970 until logging event was
+ * created.
+ */
+ private long timeStamp;
+
+ public LoggingEvent() {
+ }
+
+ public LoggingEvent(String fqcn, Logger logger, Level level, String message,
+ Throwable throwable) {
+ this.fqnOfLoggerClass = fqcn;
+ this.logger = logger;
+ this.level = level;
+ this.message = message;
+
+ if (throwable != null) {
+ this.throwableInfo = new ThrowableInformation(throwable);
+ }
+ timeStamp = System.currentTimeMillis();
+ }
+
+ public void setArgumentArray(Object[] argArray) {
+ if (this.argumentArray != null) {
+ throw new IllegalStateException("argArray has been already set");
+ }
+ this.argumentArray = argArray;
+ }
+
+ public Object[] getArgumentArray() {
+ return this.argumentArray;
+ }
+
+ public Level getLevel() {
+ return level;
+ }
+
+ public String getThreadName() {
+ if (threadName == null) {
+ threadName = (Thread.currentThread()).getName();
+ }
+ return threadName;
+ }
+
+ /**
+ * @param threadName
+ * The threadName to set.
+ * @throws IllegalStateException
+ * If threadName has been already set.
+ */
+ public void setThreadName(String threadName) throws IllegalStateException {
+ if (this.threadName != null) {
+ throw new IllegalStateException("threadName has been already set");
+ }
+ this.threadName = threadName;
+ }
+
+ /**
+ * Returns the throwable information contained within this event. May be
+ * <code>null</code> if there is no such information.
+ */
+ public ThrowableInformation getThrowableInformation() {
+ return throwableInfo;
+ }
+
+ /**
+ * Set this event's throwable information.
+ */
+ public void setThrowableInformation(ThrowableInformation ti) {
+ if (throwableInfo != null) {
+ throw new IllegalStateException(
+ "ThrowableInformation has been already set.");
+ } else {
+ throwableInfo = ti;
+ }
+ }
+
+ /**
+ * This method should be called prior to serializing an event. It should also
+ * be called when using asynchronous logging.
+ */
+ public void prepareForDeferredProcessing() {
+ this.getThreadName();
+ }
+
+ public Logger getLogger() {
+ return logger;
+ }
+
+ public void setLogger(Logger logger) {
+ this.logger = logger;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ if (this.message != null) {
+ throw new IllegalStateException(
+ "The message for this event has been set already.");
+ }
+ this.message = message;
+ }
+
+ public long getTimeStamp() {
+ return timeStamp;
+ }
+
+ public void setTimeStamp(long timeStamp) {
+ this.timeStamp = timeStamp;
+ }
+
+ public void setLevel(Level level) {
+ if (this.level != null) {
+ throw new IllegalStateException(
+ "The level has been already set for this event.");
+ }
+ this.level = level;
+ }
+
+ /**
+ * The time at which this class was loaded into memory, expressed in
+ * millisecond elapsed since the epoch (1.1.1970).
+ *
+ * @return The time as measured when this class was loaded into memory.
+ */
+ public static final long getStartTime() {
+ return startTime;
+ }
+
+ /**
+ * Get the caller information for this logging event. If caller information is
+ * null at the time of its invocation, this method extracts location
+ * information. The collected information is cached for future use.
+ *
+ * <p>
+ * Note that after serialization it is impossible to correctly extract caller
+ * information.
+ * </p>
+ */
+ public CallerData[] getCallerData() {
+ // we rely on the fact that fqnOfLoggerClass does not survive
+ // serialization
+ if (callerDataArray == null && fqnOfLoggerClass != null) {
+ callerDataArray = CallerData.extract(new Throwable(), fqnOfLoggerClass);
+ }
+ return callerDataArray;
+ }
+
+ public void setCallerInformation(CallerData[] callerDataArray) {
+ this.callerDataArray = callerDataArray;
+ }
+
+ public Marker getMarker() {
+ return marker;
+ }
+
+ public void setMarker(Marker marker) {
+ if (this.marker != null) {
+ throw new IllegalStateException(
+ "The marker has been already set for this event.");
+ }
+ this.marker = marker;
+ }
+
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ threadName = (String)in.readObject();
+ message = (String) in.readObject();
+ int levelInt = in.readInt();
+ level = Level.toLevel(levelInt);
+ String loggerName = (String)in.readObject();
+ logger = LoggerFactory.getLogger(loggerName);
+ }
+
+ public void writeExternal(ObjectOutput out) throws IOException {
+ if (threadName != null) {
+ out.writeObject(threadName);
+ } else {
+ out.writeObject("noThreadName");
+ }
+ out.writeObject(message);
+ out.writeInt(level.levelInt);
+ out.writeObject(logger.getName());
+
+ // out.writeObject(throwableInfo);
+ // out.writeObject(callerDataArray);
+ // out.writeObject(marker);
+ }
}
Modified: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTestApp.java
==============================================================================
--- logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTestApp.java (original)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTestApp.java Tue Aug 29 15:00:10 2006
@@ -23,8 +23,7 @@
for (int i = 0; i <= 1000; i++) {
logger.debug("** Hello world. n=" + i);
}
-
-
+
StatusPrinter.print(lc.getStatusManager());
}
1
0

svn commit: r485 - logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net
by noreply.seb@qos.ch 24 Aug '06
by noreply.seb@qos.ch 24 Aug '06
24 Aug '06
Author: seb
Date: Thu Aug 24 16:26:25 2006
New Revision: 485
Added:
logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTestApp.java
- copied, changed from r483, logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTest.java
Removed:
logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTest.java
Modified:
logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppenderTest.java
Log:
renamed SocketAppenderTest to SockerAppenderTestApp to free the space for a real SocketAppenderTest unit test class
Modified: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppenderTest.java
==============================================================================
--- logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppenderTest.java (original)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppenderTest.java Thu Aug 24 16:26:25 2006
@@ -12,7 +12,7 @@
public static void main(String[] args) {
- Logger logger = (Logger) LoggerFactory.getLogger(SocketAppenderTest.class);
+ Logger logger = (Logger) LoggerFactory.getLogger(SocketAppenderTestApp.class);
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
SMTPAppender appender = new SMTPAppender();
appender.setContext(lc);
Copied: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTestApp.java (from r483, 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/SocketAppenderTest.java (original)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTestApp.java Thu Aug 24 16:26:25 2006
@@ -7,11 +7,11 @@
import ch.qos.logback.core.util.StatusPrinter;
-public class SocketAppenderTest {
+public class SocketAppenderTestApp {
public static void main(String[] args) {
- Logger logger = (Logger) LoggerFactory.getLogger(SocketAppenderTest.class);
+ Logger logger = (Logger) LoggerFactory.getLogger(SocketAppenderTestApp.class);
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
SocketAppender appender = new SocketAppender("localhost", 4560);
appender.setContext(lc);
1
0

svn commit: r484 - in logback/trunk: logback-classic/examples logback-classic/examples/classes logback-classic/src/main/java/ch/qos/logback/classic/helpers logback-classic/src/main/java/ch/qos/logback/classic/net logback-classic/src/test/java/ch/qos/logback/classic/net logback-site
by noreply.seb@qos.ch 24 Aug '06
by noreply.seb@qos.ch 24 Aug '06
24 Aug '06
Author: seb
Date: Thu Aug 24 16:24:39 2006
New Revision: 484
Added:
logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/helpers/
logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/helpers/CyclicBuffer.java
logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SMTPAppender.java
logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppenderTest.java
Modified:
logback/trunk/logback-classic/examples/ (props changed)
logback/trunk/logback-classic/examples/classes/ (props changed)
logback/trunk/logback-site/ (props changed)
Log:
- SMTPAppender: on going work.
- A working version of this appender is commited. Reliable tests will follow.
- Minor ignore-list modifications.
Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/helpers/CyclicBuffer.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/helpers/CyclicBuffer.java Thu Aug 24 16:24:39 2006
@@ -0,0 +1,153 @@
+/**
+ * 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.helpers;
+
+import ch.qos.logback.classic.spi.LoggingEvent;
+
+
+/**
+
+ CyclicBuffer is used by other appenders to hold {@link LoggingEvent
+ LoggingEvents} for immediate or differed display.
+
+ <p>This buffer gives read access to any element in the buffer not
+ just the first or last element.
+
+ @author Ceki Gülcü
+ @since 0.9.0
+
+ */
+public class CyclicBuffer {
+
+ LoggingEvent[] ea;
+ int first;
+ int last;
+ int numElems;
+ int maxSize;
+
+ /**
+ Instantiate a new CyclicBuffer of at most <code>maxSize</code> events.
+
+ The <code>maxSize</code> argument must a positive integer.
+
+ @param maxSize The maximum number of elements in the buffer.
+ */
+ public CyclicBuffer(int maxSize) throws IllegalArgumentException {
+ if(maxSize < 1) {
+ throw new IllegalArgumentException("The maxSize argument ("+maxSize+
+ ") is not a positive integer.");
+ }
+ this.maxSize = maxSize;
+ ea = new LoggingEvent[maxSize];
+ first = 0;
+ last = 0;
+ numElems = 0;
+ }
+
+ /**
+ Add an <code>event</code> as the last event in the buffer.
+
+ */
+ public
+ void add(LoggingEvent event) {
+ ea[last] = event;
+ if(++last == maxSize)
+ last = 0;
+
+ if(numElems < maxSize)
+ numElems++;
+ else if(++first == maxSize)
+ first = 0;
+ }
+
+
+ /**
+ Get the <i>i</i>th oldest event currently in the buffer. If
+ <em>i</em> is outside the range 0 to the number of elements
+ currently in the buffer, then <code>null</code> is returned.
+
+
+ */
+ public
+ LoggingEvent get(int i) {
+ if(i < 0 || i >= numElems)
+ return null;
+
+ return ea[(first + i) % maxSize];
+ }
+
+ public
+ int getMaxSize() {
+ return maxSize;
+ }
+
+ /**
+ Get the oldest (first) element in the buffer. The oldest element
+ is removed from the buffer.
+ */
+ public
+ LoggingEvent get() {
+ LoggingEvent r = null;
+ if(numElems > 0) {
+ numElems--;
+ r = ea[first];
+ ea[first] = null;
+ if(++first == maxSize)
+ first = 0;
+ }
+ return r;
+ }
+
+ /**
+ Get the number of elements in the buffer. This number is
+ guaranteed to be in the range 0 to <code>maxSize</code>
+ (inclusive).
+ */
+ public
+ int length() {
+ return numElems;
+ }
+
+ /**
+ Resize the cyclic buffer to <code>newSize</code>.
+
+ @throws IllegalArgumentException if <code>newSize</code> is negative.
+ */
+ public
+ void resize(int newSize) {
+ if(newSize < 0) {
+ throw new IllegalArgumentException("Negative array size ["+newSize+
+ "] not allowed.");
+ }
+ if(newSize == numElems)
+ return; // nothing to do
+
+ LoggingEvent[] temp = new LoggingEvent[newSize];
+
+ int loopLen = newSize < numElems ? newSize : numElems;
+
+ for(int i = 0; i < loopLen; i++) {
+ temp[i] = ea[first];
+ ea[first] = null;
+ if(++first == numElems)
+ first = 0;
+ }
+ ea = temp;
+ first = 0;
+ numElems = loopLen;
+ maxSize = newSize;
+ if (loopLen == newSize) {
+ last = 0;
+ } else {
+ last = loopLen;
+ }
+ }
+}
Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SMTPAppender.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SMTPAppender.java Thu Aug 24 16:24:39 2006
@@ -0,0 +1,399 @@
+/**
+ * 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.util.Date;
+import java.util.Properties;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.helpers.CyclicBuffer;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+import ch.qos.logback.core.Layout;
+import ch.qos.logback.core.rolling.TriggeringPolicy;
+import ch.qos.logback.core.util.OptionHelper;
+
+/**
+ * Send an e-mail when a specific logging event occurs, typically on errors or
+ * fatal errors.
+ *
+ * <p>
+ * The number of logging events delivered in this e-mail depend on the value of
+ * <b>BufferSize</b> option. The <code>SMTPAppender</code> keeps only the
+ * last <code>BufferSize</code> logging events in its cyclic buffer. This
+ * keeps memory requirements at a reasonable level while still delivering useful
+ * application context.
+ *
+ * @author Ceki Gülcü
+ * @author Sébastien Pennec
+ *
+ * @since 1.0
+ */
+public class SMTPAppender extends AppenderBase {
+ private Layout layout;
+
+ private String to;
+ private String from;
+ private String subject;
+ private String smtpHost;
+ private int bufferSize = 512;
+ private boolean locationInfo = false;
+
+ protected CyclicBuffer cb = new CyclicBuffer(bufferSize);
+ protected Message msg;
+
+ protected TriggeringPolicy evaluator;
+
+ /**
+ * The default constructor will instantiate the appender with a
+ * {@link TriggeringEventEvaluator} that will trigger on events with level
+ * ERROR or higher.
+ */
+ public SMTPAppender() {
+ this(new DefaultEvaluator());
+ }
+
+ /**
+ * Use <code>evaluator</code> passed as parameter as the {@link
+ * TriggeringEventEvaluator} for this SMTPAppender.
+ */
+ public SMTPAppender(TriggeringPolicy evaluator) {
+ this.evaluator = evaluator;
+ }
+
+ /**
+ * Start the appender
+ */
+ public void start() {
+ Properties props = new Properties(System.getProperties());
+ if (smtpHost != null) {
+ props.put("mail.smtp.host", smtpHost);
+ }
+
+ Session session = Session.getInstance(props, null);
+ // session.setDebug(true);
+ msg = new MimeMessage(session);
+
+ try {
+ if (from != null) {
+ msg.setFrom(getAddress(from));
+ } else {
+ msg.setFrom();
+ }
+
+ msg.setRecipients(Message.RecipientType.TO, parseAddress(to));
+ if (subject != null) {
+ msg.setSubject(subject);
+ }
+
+ started = true;
+
+ } catch (MessagingException e) {
+ addError("Could not activate SMTPAppender options.", e);
+ }
+ }
+
+ /**
+ * Perform SMTPAppender specific appending actions, mainly adding the event to
+ * a cyclic buffer and checking if the event triggers an e-mail to be sent.
+ */
+ protected void append(Object eventObject) {
+ LoggingEvent event = (LoggingEvent) eventObject;
+
+ if (!checkEntryConditions()) {
+ return;
+ }
+
+ event.getThreadName();
+ // event.getNDC();
+ // if (locationInfo) {
+ // event.getLocationInformation();
+ // }
+ cb.add(event);
+ //addInfo("Added event to the cyclic buffer: " + event.getMessage());
+
+ if (evaluator.isTriggeringEvent(null, event)) {
+ sendBuffer();
+ }
+ }
+
+ /**
+ * This method determines if there is a sense in attempting to append.
+ *
+ * <p>
+ * It checks whether there is a set output target and also if there is a set
+ * layout. If these checks fail, then the boolean value <code>false</code>
+ * is returned.
+ */
+ protected boolean checkEntryConditions() {
+ if (this.msg == null) {
+ addError("Message object not configured.");
+ return false;
+ }
+
+ if (this.evaluator == null) {
+ addError("No TriggeringPolicy is set for appender [" + name + "].");
+ return false;
+ }
+
+ if (this.layout == null) {
+ addError("No layout set for appender named [" + name + "].");
+ return false;
+ }
+ return true;
+ }
+
+ synchronized public void stop() {
+ this.started = false;
+ }
+
+ InternetAddress getAddress(String addressStr) {
+ try {
+ return new InternetAddress(addressStr);
+ } catch (AddressException e) {
+ addError("Could not parse address [" + addressStr + "].", e);
+ return null;
+ }
+ }
+
+ InternetAddress[] parseAddress(String addressStr) {
+ try {
+ return InternetAddress.parse(addressStr, true);
+ } catch (AddressException e) {
+ addError("Could not parse address [" + addressStr + "].", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns value of the <b>To</b> option.
+ */
+ public String getTo() {
+ return to;
+ }
+
+ /**
+ * The <code>SMTPAppender</code> requires a {@link org.apache.log4j.Layout
+ * layout}.
+ */
+ public boolean requiresLayout() {
+ return true;
+ }
+
+ /**
+ * Send the contents of the cyclic buffer as an e-mail message.
+ */
+ protected void sendBuffer() {
+
+ // Note: this code already owns the monitor for this
+ // appender. This frees us from needing to synchronize on 'cb'.
+ try {
+ MimeBodyPart part = new MimeBodyPart();
+
+ StringBuffer sbuf = new StringBuffer();
+ String t = layout.getHeader();
+ if (t != null)
+ sbuf.append(t);
+ int len = cb.length();
+ for (int i = 0; i < len; i++) {
+ //sbuf.append(MimeUtility.encodeText(layout.format(cb.get())));
+ LoggingEvent event = cb.get();
+ sbuf.append(layout.doLayout(event));
+ // if (layout.ignoresThrowable()) {
+ // String[] s = event.getThrowableStrRep();
+ // if (s != null) {
+ // for (int j = 0; j < s.length; j++) {
+ // sbuf.append(s[j]);
+ // }
+ // }
+ // }
+ }
+ t = layout.getFooter();
+ if (t != null)
+ sbuf.append(t);
+ part.setContent(sbuf.toString(), "text/plain");
+
+ Multipart mp = new MimeMultipart();
+ mp.addBodyPart(part);
+ msg.setContent(mp);
+
+ msg.setSentDate(new Date());
+ Transport.send(msg);
+ } catch (Exception e) {
+ addError("Error occured while sending e-mail notification.", e);
+ }
+ }
+
+ /**
+ * Returns value of the <b>EvaluatorClass</b> option.
+ */
+ public String getEvaluatorClass() {
+ return evaluator == null ? null : evaluator.getClass().getName();
+ }
+
+ /**
+ * Returns value of the <b>From</b> option.
+ */
+ public String getFrom() {
+ return from;
+ }
+
+ /**
+ * Returns value of the <b>Subject</b> option.
+ */
+ public String getSubject() {
+ return subject;
+ }
+
+ /**
+ * The <b>From</b> option takes a string value which should be a e-mail
+ * address of the sender.
+ */
+ public void setFrom(String from) {
+ this.from = from;
+ }
+
+ /**
+ * The <b>Subject</b> option takes a string value which should be a the
+ * subject of the e-mail message.
+ */
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+ /**
+ * The <b>BufferSize</b> option takes a positive integer representing the
+ * maximum number of logging events to collect in a cyclic buffer. When the
+ * <code>BufferSize</code> is reached, oldest events are deleted as new
+ * events are added to the buffer. By default the size of the cyclic buffer is
+ * 512 events.
+ */
+ public void setBufferSize(int bufferSize) {
+ this.bufferSize = bufferSize;
+ cb.resize(bufferSize);
+ }
+
+ /**
+ * The <b>SMTPHost</b> option takes a string value which should be a the host
+ * name of the SMTP server that will send the e-mail message.
+ */
+ public void setSMTPHost(String smtpHost) {
+ this.smtpHost = smtpHost;
+ }
+
+ /**
+ * Returns value of the <b>SMTPHost</b> option.
+ */
+ public String getSMTPHost() {
+ return smtpHost;
+ }
+
+ /**
+ * The <b>To</b> option takes a string value which should be a comma
+ * separated list of e-mail address of the recipients.
+ */
+ public void setTo(String to) {
+ this.to = to;
+ }
+
+ /**
+ * Returns value of the <b>BufferSize</b> option.
+ */
+ public int getBufferSize() {
+ return bufferSize;
+ }
+
+ /**
+ * The <b>EvaluatorClass</b> option takes a string value representing the
+ * name of the class implementing the {@link TriggeringEventEvaluator}
+ * interface. A corresponding object will be instantiated and assigned as the
+ * triggering event evaluator for the SMTPAppender.
+ */
+ public void setEvaluatorClass(String value) {
+ try {
+ evaluator = (TriggeringPolicy) OptionHelper.instantiateByClassName(value,
+ TriggeringPolicy.class);
+ } catch (Exception ex) {
+ addError("Evaluator class instanciation failed");
+ }
+ }
+
+ /**
+ * The <b>LocationInfo</b> option takes a boolean value. By default, it is
+ * set to false which means there will be no effort to extract the location
+ * information related to the event. As a result, the layout that formats the
+ * events as they are sent out in an e-mail is likely to place the wrong
+ * location information (if present in the format).
+ *
+ * <p>
+ * Location information extraction is comparatively very slow and should be
+ * avoided unless performance is not a concern.
+ */
+ public void setLocationInfo(boolean locationInfo) {
+ this.locationInfo = locationInfo;
+ }
+
+ /**
+ * Returns value of the <b>LocationInfo</b> option.
+ */
+ public boolean getLocationInfo() {
+ return locationInfo;
+ }
+
+ public Layout getLayout() {
+ return layout;
+ }
+
+ public void setLayout(Layout layout) {
+ this.layout = layout;
+ }
+}
+
+class DefaultEvaluator implements TriggeringPolicy {
+
+ private boolean started;
+
+ /**
+ * Is this <code>event</code> the e-mail triggering event?
+ *
+ * <p>
+ * This method returns <code>true</code>, if the event level has ERROR
+ * level or higher. Otherwise it returns <code>false</code>.
+ */
+ public boolean isTriggeringEvent(File file, Object eventObject) {
+ LoggingEvent event = (LoggingEvent) eventObject;
+ return event.getLevel().isGreaterOrEqual(Level.ERROR);
+ }
+
+ public boolean isStarted() {
+ return started == true;
+ }
+
+ public void start() {
+ started = true;
+ }
+
+ public void stop() {
+ started = false;
+ }
+}
Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppenderTest.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppenderTest.java Thu Aug 24 16:24:39 2006
@@ -0,0 +1,48 @@
+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.PatternLayout;
+import ch.qos.logback.core.Layout;
+import ch.qos.logback.core.util.StatusPrinter;
+
+public class SMTPAppenderTest {
+
+ public static void main(String[] args) {
+
+ Logger logger = (Logger) LoggerFactory.getLogger(SocketAppenderTest.class);
+ LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+ SMTPAppender appender = new SMTPAppender();
+ appender.setContext(lc);
+ appender.setName("smtp");
+ appender.setFrom("user(a)host.dom");
+ appender.setLayout(buildLayout(lc));
+ appender.setSMTPHost("mail.qos.ch");
+ appender.setSubject("logging report");
+ appender.setTo("sebastien(a)qos.ch");
+
+ appender.start();
+
+ logger.addAppender(appender);
+
+ for (int i = 0; i <= 10; i++) {
+ logger.debug("** Hello world. n=" + i);
+ }
+ logger.error("Triggering request");
+
+ StatusPrinter.print(lc.getStatusManager());
+ }
+
+ private static Layout buildLayout(LoggerContext lc) {
+ PatternLayout layout = new PatternLayout();
+ layout.setContext(lc);
+ layout.setHeader("Some header\n");
+ layout.setPattern("%-4relative [%thread] %-5level %class - %msg%n");
+ layout.setFooter("Some footer");
+ layout.start();
+ return layout;
+ }
+
+}
1
0

svn commit: r483 - in logback/trunk/logback-classic/src: main/java/ch/qos/logback/classic main/java/ch/qos/logback/classic/net main/java/ch/qos/logback/classic/spi test/input/socket test/java/ch/qos/logback/classic/net
by noreply.seb@qos.ch 24 Aug '06
by noreply.seb@qos.ch 24 Aug '06
24 Aug '06
Author: seb
Date: Thu Aug 24 14:57:24 2006
New Revision: 483
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/net/SocketAppender.java
logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java
logback/trunk/logback-classic/src/test/input/socket/serverConfig.xml
logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTest.java
Log:
- SocketAppender, on going work.
- Some attributes of Logger have been set transient
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 14:57:24 2006
@@ -10,6 +10,7 @@
package ch.qos.logback.classic;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -24,9 +25,14 @@
import ch.qos.logback.core.spi.AppenderAttachableImpl;
-public final class Logger implements org.slf4j.Logger, AppenderAttachable {
+public final class Logger implements org.slf4j.Logger, AppenderAttachable, Serializable {
/**
+ *
+ */
+ private static final long serialVersionUID = 5454405123156820674L;
+
+ /**
* The fully qualified name of this class. Used in gathering caller
* information.
*/
@@ -57,7 +63,7 @@
*/
private List<Logger> childrenList;
- private AppenderAttachableImpl aai;
+ private transient AppenderAttachableImpl aai;
/**
* Additivity is set to true by default, that is children inherit the
* appenders of their ancestors by default. If this variable is set to
@@ -68,7 +74,7 @@
*/
private boolean additive = true;
- final LoggerContext loggerContext;
+ final transient LoggerContext loggerContext;
Logger(String name, Logger parent, LoggerContext loggerContext) {
this.name = name;
Modified: 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/SocketAppender.java (original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketAppender.java Thu Aug 24 14:57:24 2006
@@ -15,6 +15,7 @@
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
+import java.util.Calendar;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.AppenderBase;
@@ -249,7 +250,10 @@
if (oos != null) {
try {
+ Long t1 = Calendar.getInstance().getTimeInMillis();
oos.writeObject(event);
+ Long t2 = Calendar.getInstance().getTimeInMillis();
+ addInfo("=========Writing time: " + Long.toString(t2-t1));
addInfo("=========Flushing.");
oos.flush();
if (++counter >= RESET_FREQUENCY) {
Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java
==============================================================================
--- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java (original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java Thu Aug 24 14:57:24 2006
@@ -1,16 +1,18 @@
/**
- * LOGBack: the reliable, fast and flexible logging library for Java.
- *
- * Copyright (C) 1999-2005, QOS.ch, LOGBack.com
- *
- * 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.
+ * 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.spi;
+import java.io.Serializable;
+
import org.slf4j.Logger;
import org.slf4j.Marker;
@@ -19,9 +21,9 @@
/**
* The internal representation of logging events. When an affirmative decision
* is made to log then a <code>LoggingEvent</code> instance is created. This
- * instance is passed around to the different LogBack components.
+ * instance is passed around to the different Logback components.
*
- * <p>Writers of LogBack components such as appenders should be
+ * <p>Writers of Logback components such as appenders should be
* aware of that some of the LoggingEvent fields are initialized lazily.
* Therefore, an appender wishing to output data to be later correctly read
* by a receiver, must initialize "lazy" fields prior to writing them out.
@@ -29,10 +31,15 @@
*
* @author Ceki Gülcü
*/
-public class LoggingEvent {
+public class LoggingEvent implements Serializable {
/**
+ *
+ */
+ private static final long serialVersionUID = -7298433437463204531L;
+
+ /**
*
*/
private static long startTime = System.currentTimeMillis();
@@ -166,7 +173,7 @@
public void setMessage(String message) {
if (this.message != null) {
- throw new IllegalStateException("The message for this event has been set alredy.");
+ throw new IllegalStateException("The message for this event has been set already.");
}
this.message = message;
}
@@ -227,5 +234,7 @@
}
this.marker = marker;
}
+
+
}
Modified: logback/trunk/logback-classic/src/test/input/socket/serverConfig.xml
==============================================================================
--- logback/trunk/logback-classic/src/test/input/socket/serverConfig.xml (original)
+++ logback/trunk/logback-classic/src/test/input/socket/serverConfig.xml Thu Aug 24 14:57:24 2006
@@ -9,18 +9,6 @@
</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" />
Modified: 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/SocketAppenderTest.java (original)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTest.java Thu Aug 24 14:57:24 2006
@@ -4,12 +4,13 @@
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.core.util.StatusPrinter;
-public class SocketAppenderTest {
+public class SocketAppenderTest {
public static void main(String[] args) {
-
+
Logger logger = (Logger) LoggerFactory.getLogger(SocketAppenderTest.class);
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
SocketAppender appender = new SocketAppender("localhost", 4560);
@@ -19,8 +20,12 @@
logger.addAppender(appender);
- logger.debug("************* Hello world.");
+ for (int i = 0; i <= 1000; i++) {
+ logger.debug("** Hello world. n=" + i);
+ }
+
+
+ StatusPrinter.print(lc.getStatusManager());
}
-
}
1
0

svn commit: r482 - in logback/trunk/logback-classic/src: main/java/ch/qos/logback/classic/net test/input/socket test/java/ch/qos/logback/classic/net
by noreply.seb@qos.ch 24 Aug '06
by noreply.seb@qos.ch 24 Aug '06
24 Aug '06
Author: seb
Date: Thu Aug 24 10:50:07 2006
New Revision: 482
Modified:
logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketAppender.java
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
Log:
on going work:
- removed Layout from SocketAppender
- modified configFiles and test
Modified: 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/SocketAppender.java (original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SocketAppender.java Thu Aug 24 10:50:07 2006
@@ -124,8 +124,6 @@
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;
@@ -140,7 +138,7 @@
this.address = address;
this.remoteHost = address.getHostName();
this.port = port;
- connect(address, port);
+ //connect(address, port);
}
/**
@@ -150,24 +148,37 @@
this.port = port;
this.address = getAddressByName(host);
this.remoteHost = host;
- connect(address, port);
+ //connect(address, port);
}
+ // /**
+ // * Connect to the specified <b>RemoteHost</b> and <b>Port</b>.
+ // */
+ // public void activateOptions() {
+ // connect(address, port);
+ // }
+
/**
- * Connect to the specified <b>RemoteHost</b> and <b>Port</b>.
- */
- public void activateOptions() {
- connect(address, port);
- }
-
- /**
- * Start this appender
+ * Start this appender.
*/
public void start() {
- //TODO More tests before starting the Appender.
- this.started = true;
+ int errorCount = 0;
+ if (port == 0) {
+ errorCount++;
+ addError("No port was configured for appender" + name);
+ }
+
+ if (address == null) {
+ errorCount++;
+ addError("No remote address was configured for appender" + name);
+ }
+
+ connect(address, port);
+
+ if (errorCount == 0) {
+ this.started = true;
+ }
}
-
/**
* Strop this appender.
@@ -226,7 +237,7 @@
@Override
protected void append(Object event) {
-
+
if (event == null)
return;
@@ -337,11 +348,10 @@
}
public Layout getLayout() {
- return layout;
+ return null;
}
public void setLayout(Layout layout) {
- this.layout = layout;
}
/**
Modified: logback/trunk/logback-classic/src/test/input/socket/clientConfig.xml
==============================================================================
--- logback/trunk/logback-classic/src/test/input/socket/clientConfig.xml (original)
+++ logback/trunk/logback-classic/src/test/input/socket/clientConfig.xml Thu Aug 24 10:50:07 2006
@@ -13,10 +13,7 @@
<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>
+ <param name="port" value="4560" />
</appender>
<root>
Modified: logback/trunk/logback-classic/src/test/input/socket/serverConfig.xml
==============================================================================
--- logback/trunk/logback-classic/src/test/input/socket/serverConfig.xml (original)
+++ logback/trunk/logback-classic/src/test/input/socket/serverConfig.xml Thu Aug 24 10:50:07 2006
@@ -9,7 +9,7 @@
</layout>
</appender>
- <appender name="Rolling"
+ <appender name="rolling"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
@@ -24,6 +24,6 @@
<root>
<level value="debug" />
<appender-ref ref="stdout" />
- <appender-ref ref="Rolling" />
+ <appender-ref ref="rolling" />
</root>
</configuration>
\ No newline at end of file
Modified: 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/SocketAppenderTest.java (original)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SocketAppenderTest.java Thu Aug 24 10:50:07 2006
@@ -4,33 +4,23 @@
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");
-
+ SocketAppender appender = new SocketAppender("localhost", 4560);
+ appender.setContext(lc);
+ appender.setName("socket");
+ appender.start();
+
+ logger.addAppender(appender);
+
logger.debug("************* Hello world.");
-
-// t.interrupt();
-// System.exit(0);
-
+
}
}
1
0
Author: seb
Date: Thu Aug 24 10:03:08 2006
New Revision: 481
Modified:
logback/trunk/logback-classic/pom.xml
logback/trunk/pom.xml
Log:
added javax.mail dependency to the poms
Modified: logback/trunk/logback-classic/pom.xml
==============================================================================
--- logback/trunk/logback-classic/pom.xml (original)
+++ logback/trunk/logback-classic/pom.xml Thu Aug 24 10:03:08 2006
@@ -36,6 +36,12 @@
<artifactId>logback-core</artifactId>
<scope>compile</scope>
</dependency>
+
+ <dependency>
+ <groupId>javax.mail</groupId>
+ <artifactId>mail</artifactId>
+ <scope>compile</scope>
+ </dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
Modified: logback/trunk/pom.xml
==============================================================================
--- logback/trunk/pom.xml (original)
+++ logback/trunk/pom.xml Thu Aug 24 10:03:08 2006
@@ -57,6 +57,14 @@
<version>2.4.3</version>
</dependency>
+ <!-- Classic Module Dependencies -->
+ <dependency>
+ <groupId>javax.mail</groupId>
+ <artifactId>mail</artifactId>
+ <version>1.4</version>
+ </dependency>
+
+
<!-- Access Module Dependencies -->
<dependency>
<groupId>tomcat</groupId>
1
0

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
by noreply.seb@qos.ch 24 Aug '06
by noreply.seb@qos.ch 24 Aug '06
24 Aug '06
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(a)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(a)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."));
+ }
+}
1
0

svn commit: r479 - in logback/trunk: . logback-access logback-classic logback-core logback-site
by noreply.seb@qos.ch 23 Aug '06
by noreply.seb@qos.ch 23 Aug '06
23 Aug '06
Author: seb
Date: Wed Aug 23 10:13:14 2006
New Revision: 479
Modified:
logback/trunk/logback-access/pom.xml
logback/trunk/logback-classic/pom.xml
logback/trunk/logback-core/pom.xml
logback/trunk/logback-site/pom.xml
logback/trunk/pom.xml
Log:
upgraded poms to 0.3-SNAPSHOT
Modified: logback/trunk/logback-access/pom.xml
==============================================================================
--- logback/trunk/logback-access/pom.xml (original)
+++ logback/trunk/logback-access/pom.xml Wed Aug 23 10:13:14 2006
@@ -5,7 +5,7 @@
<parent>
<groupId>ch.qos.logback</groupId>
<artifactId>logback</artifactId>
- <version>0.2.5</version>
+ <version>0.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
Modified: logback/trunk/logback-classic/pom.xml
==============================================================================
--- logback/trunk/logback-classic/pom.xml (original)
+++ logback/trunk/logback-classic/pom.xml Wed Aug 23 10:13:14 2006
@@ -3,7 +3,7 @@
<parent>
<groupId>ch.qos.logback</groupId>
<artifactId>logback</artifactId>
- <version>0.2.5</version>
+ <version>0.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
Modified: logback/trunk/logback-core/pom.xml
==============================================================================
--- logback/trunk/logback-core/pom.xml (original)
+++ logback/trunk/logback-core/pom.xml Wed Aug 23 10:13:14 2006
@@ -3,7 +3,7 @@
<parent>
<groupId>ch.qos.logback</groupId>
<artifactId>logback</artifactId>
- <version>0.2.5</version>
+ <version>0.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
Modified: logback/trunk/logback-site/pom.xml
==============================================================================
--- logback/trunk/logback-site/pom.xml (original)
+++ logback/trunk/logback-site/pom.xml Wed Aug 23 10:13:14 2006
@@ -5,7 +5,7 @@
<parent>
<groupId>ch.qos.logback</groupId>
<artifactId>logback</artifactId>
- <version>0.2.5</version>
+ <version>0.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
Modified: logback/trunk/pom.xml
==============================================================================
--- logback/trunk/pom.xml (original)
+++ logback/trunk/pom.xml Wed Aug 23 10:13:14 2006
@@ -6,7 +6,7 @@
<groupId>ch.qos.logback</groupId>
<artifactId>logback</artifactId>
- <version>0.2.5</version>
+ <version>0.3-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Logback</name>
1
0
Author: seb
Date: Wed Aug 23 10:11:47 2006
New Revision: 478
Added:
logback/tags/release-0.2.5/
- copied from r477, logback/trunk/
Log:
Tagging the 0.2.5 release
1
0

svn commit: r477 - logback/trunk/logback-site/src/site/xdocTemplates
by noreply.seb@qos.ch 23 Aug '06
by noreply.seb@qos.ch 23 Aug '06
23 Aug '06
Author: seb
Date: Wed Aug 23 09:58:23 2006
New Revision: 477
Modified:
logback/trunk/logback-site/src/site/xdocTemplates/news.xml
Log:
updated news.xml file to show the 0.2.5 version
Modified: logback/trunk/logback-site/src/site/xdocTemplates/news.xml
==============================================================================
--- logback/trunk/logback-site/src/site/xdocTemplates/news.xml (original)
+++ logback/trunk/logback-site/src/site/xdocTemplates/news.xml Wed Aug 23 09:58:23 2006
@@ -11,6 +11,21 @@
<p>Here are the last news about the project.</p>
+ <h3>August 23th, 2006 - 0.2.5 version available</h3>
+ <p>
+ A 0.2.5 version of all three modules of logback
+ is now available.
+ </p>
+ <p>
+ This release offers a better documentation. The introduction to logback classic
+ was corrected and improved.
+ An archive containing source code, binaries and docs is available
+ at the <a href="download.html">download page</a>.
+ </p>
+
+ <hr width="50%" align="center" />
+
+
<h3>August 15th, 2006 - 0.2 version available</h3>
<p>
A 0.2 version of all three modules of logback
@@ -19,8 +34,6 @@
<p>
It offers better tests, a few more functionalities, and an improved documentation.
We also improved the site design to make it simpler and more efficient.
- An archive containing source code, binaries and docs is available
- at the <a href="download.html">download page</a>.
</p>
<hr width="50%" align="center" />
1
0