Index: src/java/org/apache/log4j/concurrent/SynchronizedBoolean.java =================================================================== --- src/java/org/apache/log4j/concurrent/SynchronizedBoolean.java (revision 0) +++ src/java/org/apache/log4j/concurrent/SynchronizedBoolean.java (revision 0) @@ -0,0 +1,54 @@ + +/* + File: SynchronizedBoolean.java + + Originally written by Doug Lea and released into the public domain. + This may be used for any purposes whatsoever without acknowledgment. + Thanks for the assistance and support of Sun Microsystems Labs, + and everyone contributing, testing, and using this code. + + History: + Date Who What + 19Jun1998 dl Create public version +*/ + +package org.apache.log4j.concurrent; + +/** + * A class useful for offloading synch for boolean instance variables. + * A cut down version of the original Doug Lea class. + */ +public final class SynchronizedBoolean { + + private boolean value; + + /** + * Make a new SynchronizedBoolean with the given initial value, + * and using its own internal lock. + **/ + public SynchronizedBoolean(boolean initialValue) { + value = initialValue; + } + + /** + * Return the current value + **/ + public synchronized boolean get() { return value; } + + /** + * Set to newValue. + * @return the old value + **/ + public synchronized boolean set(boolean newValue) { + boolean old = value; + value = newValue; + return old; + } + + /** + * Returns String.valueOf(get())). + */ + public String toString() { return String.valueOf(get()); } + +} + Index: src/java/org/apache/log4j/concurrent/ConcurrentAppender.java =================================================================== --- src/java/org/apache/log4j/concurrent/ConcurrentAppender.java (revision 0) +++ src/java/org/apache/log4j/concurrent/ConcurrentAppender.java (revision 0) @@ -0,0 +1,429 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.concurrent; + +import org.apache.log4j.Layout; +import org.apache.log4j.Appender; +import org.apache.log4j.Priority; +import org.apache.log4j.helpers.ReaderWriterLock; +import org.apache.log4j.spi.ComponentBase; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.OptionHandler; + +/** + * Base class for appenders that can benefit from a concurrency strategy. + * Classes derived from this appender may have the {@link #append} method + * called by multiple threads. Derived classes must also override {@link + * #internalClose}. + *

+ * Locking strategy: Internally, there is a read-write lock to handle + * concurrent modification. A write lock is obtained to change states + * (including before {@link #close}.) A read lock is obtained to read + * options. Subclasses interested in state may check state using a public + * method or within their own {@link #append} method. + *

+ *

+ * This class is heavily based on the {@link + * #org.apache.log4j.AppenderSkeleton} class. It may be a useful base class + * for creating appenders that can benefit from concurrent I/O access. + *

+ * + * @see #getWriteLock + */ +public abstract class ConcurrentAppender + extends ComponentBase implements Appender, OptionHandler +{ + + /** + * The layout variable does not need to be set if the appender + * implementation has its own layout. + */ + private Layout layout; + + /** + * The name of this appender. + */ + protected String name; + + /** + * There is no level threshold filtering by default. + */ + private volatile Priority threshold; + + /** + * Internal class, internally locked. + */ + private FilterChain filters = new FilterChain(); + + /** + * Is this appender closed? + */ + private SynchronizedBoolean closed = new SynchronizedBoolean(false); + + /** + * Set to true when the appender is activated. + * Subclasses can set this to false to indicate things are not in order. + */ + protected SynchronizedBoolean active = new SynchronizedBoolean(false); + + /** + * The guard prevents an appender from repeatedly calling its own doAppend + * method. This prevents same-thread re-entry looping. + */ + private ThreadLocal guard = new ThreadLocal(); + + /** + * A write lock is obtained to change options, a read lock is obtained to + * append events. + */ + private ReaderWriterLock lock = new ReaderWriterLock(); + + + /** + * Constructs a ConcurrentAppender. + * + * @param isActive true if appender is ready for use upon construction. + */ + protected ConcurrentAppender(final boolean isActive) { + active.set(isActive); + } + + /** + * Derived appenders should override this method if option structure + * requires it. + * By default, sets {@link #active} to true. + */ + public void activateOptions() { + active.set(true); + } + + /** + * Indicates if the appender is active and not closed. + */ + public boolean isActive() { + return active.get() && !closed.get(); + } + + /** + * Adds a filter to end of the filter list. + * @param filter filter to use; cannot be null + */ + public void addFilter(Filter filter) { + filters.addFilter(filter); + } + + /** + * Clears the filters chain. + */ + public void clearFilters() { + filters.clear(); + } + + /** + * Returns the first {@link Filter}. + */ + public Filter getFilter() { + return filters.getHead(); + } + + /** + * Returns the layout of this appender. May return null if not set. + */ + public Layout getLayout() { + return this.layout; + } + + /** + * Returns the name of this appender. + */ + public final String getName() { + return this.name; + } + + /** + * Returns this appender's threshold level. See the {@link #setThreshold} + * method for the meaning of this option. + */ + public Priority getThreshold() { + return threshold; + } + + /** + * Returns true if the message level is below the appender's threshold. If + * there is no threshold set, returns true. + */ + public boolean isAsSevereAsThreshold(final Priority level) { + Priority copy = threshold; + return ((copy == null) || copy.isGreaterOrEqual(level)); + } + + /** + * Performs threshold checks and checks filters before delegating actual + * logging to the subclasses specific {@link #append} method. + * This implementation also checks if this thread already is logging using + * this appender, preventing possible stack overflow. + */ + public final void doAppend(LoggingEvent event) { + + if (!isAsSevereAsThreshold(event.getLevel())) + return; + + if (!filters.accept(event)) + return; + + // Prevent concurrent re-entry by this thread + // (There might be a cheaper way to do this) + // (Or maybe this lock is not necessary) + if (guard.get() != null) + return; + + guard.set(this); // arbitrary thread lock object + try { + + lock.getReadLock(); + try { + + + if (closed.get()) { + getNonFloodingLogger().error( + "Attempted to use closed appender named [" + name + "]."); + return; + } + + if (!active.get()) { + getNonFloodingLogger().error( + "Attempted to log with inactive named [" + name + "]."); + return; + } + + append(event); + + } finally { + lock.releaseReadLock(); + } + + } finally { + guard.set(null); + } + } + + /** + * Sets the layout for this appender. Note that some appenders have their own + * (fixed) layouts or do not use one. For example, the {@link + * org.apache.log4j.net.SocketAppender} ignores the layout set here. + *

+ * Note that the implementation of {@link Layout} must be thread-safe. + * Common layouts such as {@link org.apache.log4j.PatternLayout} are + * thread-safe. + *

+ * + * @param layout new layout to use; may be null + */ + public void setLayout(Layout layout) { + this.layout = layout; + } + + /** + * Sets the name of this Appender. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Sets the threshold level. + * All log events with lower level than the threshold level are ignored by + * the appender. + * + * @param threshold new threshold; may be null + */ + public void setThreshold(final Priority threshold) { + this.threshold = threshold; + } + + /** + * Returns true if this appender is closed. + * An appender, once closed, is closed forever. + */ + public boolean getClosed() { + return closed.get(); + } + + /** + * Cleans up this appender. + * Marked as final to prevent subclasses from accidentally + * overriding and forgetting to call super.close() or obtain a + * write lock. + * Calls {@link #internalClose} when completed. + * Implementation note: Obtains a write lock before starting close. + * Calling this method more than once does nothing. + */ + public final void close() { + boolean wasClosed; + getWriteLock(); + try { + wasClosed = closed.set(true); + } finally { + lock.releaseWriteLock(); + } + + if (!wasClosed) + internalClose(); + } + + /** + * Called to check if the appender is closed. + */ + public boolean isClosed() { + return closed.get(); + } + + public final org.apache.log4j.spi.ErrorHandler getErrorHandler() { + return org.apache.log4j.helpers.OnlyOnceErrorHandler.INSTANCE; + } + + public final void setErrorHandler(org.apache.log4j.spi.ErrorHandler eh) { + } + + /** + * Returns a string representation of this object. + */ + public String toString() { + return super.toString() + " name=" + name + + " threshold=" + threshold + + " layout=" + layout + + " filters=" + filters; + } + + // PROTECTED METHODS + + /** + * Subclasses of ConcurrentAppender should implement this method + * to perform actual logging. + * This object holds a read lock during this method. This method may be + * called simultaneously by multiple threads. + */ + protected abstract void append(LoggingEvent event); + + /** + * Subclasses must implement their own close routines. + * This method is called by the {@link #close} method. + * This is guaranteed to be called only once, even if {@link #close} is + * invoked more than once. + * Note that further locking is not required, as {@link #append} can no + * longer be called. + */ + protected abstract void internalClose(); + + /** + * Obtains a write lock that blocks logging to {@link #append}. + * This is normally done when changing output behavior, closing and reopening + * streams, etc. Call {@link #releaseWriteLock} to resume logging. + *

+ * Usage pattern: +

+   getWriteLock();
+   try {
+      // ...
+   } finally {
+      releaseWriteLock();
+   }
+   
+ * Note: Do not attempt to re-acquire this lock. This lock should only be + * used for critical sections and not for long periods of time. + */ + protected void getWriteLock() { + lock.getWriteLock(); + } + + /** + * Releases a write lock; allows calls to the {@link #append} method. + * @see #getWriteLock + */ + protected void releaseWriteLock() { + lock.releaseWriteLock(); + } + + /** + * Finalizes this appender by calling this {@link #close} method. + */ + protected void finalize() { + if (!getClosed()) + getLogger().debug("Finalizing appender named [{}].", name); + close(); + } + + /** + * A simple linked-list data structure containing filters. + */ + private static class FilterChain { + + private Filter headFilter = null; + private Filter tailFilter = null; + + public synchronized boolean accept(LoggingEvent event) { + Filter f = headFilter; + while (f != null) { + switch (f.decide(event)) { + case Filter.DENY: + return false; + case Filter.ACCEPT: + return true; + case Filter.NEUTRAL: + f = f.getNext(); + } + } + return true; + } + + public synchronized void addFilter(Filter newFilter) { + if (newFilter == null) + throw new NullPointerException(); + if (headFilter == null) { + headFilter = newFilter; + tailFilter = newFilter; + } else { + tailFilter.setNext(newFilter); + tailFilter = newFilter; + } + } + + public synchronized Filter getHead() { + return headFilter; + } + + public synchronized void clear() { + headFilter = null; + tailFilter = null; + } + + public synchronized String toString() { + StringBuffer sb = new StringBuffer(); + Filter f = headFilter; + while (f != null) { + sb.append(f); + f = f.getNext(); + if (f != null) + sb.append(','); + } + return f.toString(); + } + + } + +} Index: src/java/org/apache/log4j/concurrent/PerformanceTest.java =================================================================== --- src/java/org/apache/log4j/concurrent/PerformanceTest.java (revision 0) +++ src/java/org/apache/log4j/concurrent/PerformanceTest.java (revision 0) @@ -0,0 +1,119 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.concurrent; + +import java.io.Writer; +import java.io.StringWriter; +import java.io.FileWriter; + +import org.apache.log4j.Logger; +import org.apache.log4j.Appender; +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.PatternLayout; +import org.apache.log4j.spi.OptionHandler; +import org.apache.log4j.spi.LoggingEvent; + +public class PerformanceTest implements Runnable { + + static Logger log = Logger.getLogger(PerformanceTest.class); + + String LAYOUT = "%d{ABSOLUTE} [%24c{1}] (%-8X{con} %X{opr}) %m%n"; + int times = 1000; + int threads = 5; + + public void run() { + for (int i = 0; i < times; i++) { + log.info("Hello world", new Exception()); + } + } + + public PerformanceTest(Appender a) throws Exception { + + log.removeAllAppenders(); + log.addAppender(a); + + Thread t[] = new Thread[threads]; + a.setLayout(new PatternLayout(LAYOUT)); + ((OptionHandler)a).activateOptions(); + + long start = System.currentTimeMillis(); + + for (int i = 0; i < threads; i++) { + t[i] = new Thread(this); + t[i].start(); + } + for (int i = 0; i < threads; i++) { + t[i].join(); + } + + long end = System.currentTimeMillis(); + a.close(); + + System.out.println("Appender " + a.getClass()); + String msg = "Took " + (end - start) + "ms for " + times + " logs * " + " threads " + threads; + System.out.println(msg); + System.out.println(); + } + + public static void main(String s[]) throws Exception { + + System.out.println("Hit CTRL-\\ now"); + Thread.sleep(1000); + + Writer w; + for (int i = 0; i < 5; i++) { + + /* + ConcurrentAppender ca = new ConcurrentAppender() { + protected void append(LoggingEvent event) { + try { Thread.sleep(1); } catch (InterruptedException e) {} + } + protected void internalClose() {} + }; + + AppenderSkeleton as = new AppenderSkeleton() { + protected void append(LoggingEvent event) { + try { Thread.sleep(1); } catch (InterruptedException e) {} + } + public void close() {} + }; + + System.out.println("ConcurrentAppender"); + new PerformanceTest(ca); + + System.out.println("AppenderSkeleton"); + new PerformanceTest(as); + */ + + org.apache.log4j.FileAppender wa = new org.apache.log4j.FileAppender(); + wa.setFile("/tmp/blah"); + new PerformanceTest(wa); + + org.apache.log4j.concurrent.FileAppender wa2 = new org.apache.log4j.concurrent.FileAppender(); + wa2.setFile("/tmp/blah"); + new PerformanceTest(wa2); + /* + */ + + } + + System.out.println("Hit CTRL-\\ now"); + Thread.sleep(1000); + + } + +} Index: src/java/org/apache/log4j/concurrent/ConsoleAppender.java =================================================================== --- src/java/org/apache/log4j/concurrent/ConsoleAppender.java (revision 0) +++ src/java/org/apache/log4j/concurrent/ConsoleAppender.java (revision 0) @@ -0,0 +1,214 @@ +/* + * Copyright 1999,2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.log4j.concurrent; + +import java.io.IOException; +import java.io.OutputStream; +import org.apache.log4j.Layout; + + +/** + * ConsoleAppender appends log events to System.out or + * System.err using a layout specified by the user. The + * default target is System.out. + * + * @author Ceki Gülcü + * @author Curt Arnold + * @since 1.1 */ +public class ConsoleAppender extends WriterAppender { + public static final String SYSTEM_OUT = "System.out"; + public static final String SYSTEM_ERR = "System.err"; + protected String target = SYSTEM_OUT; + + /** + * Determines if the appender honors reassignments of System.out + * or System.err made after configuration. + */ + private boolean follow = false; + + + /** + * Constructs an unconfigured appender. + */ + public ConsoleAppender() { + } + + /** + * Creates a configured appender. + * + * @param layout layout, may not be null. + */ + public ConsoleAppender(final Layout layout) { + setLayout(layout); + activateOptions(); + } + + /** + * Creates a configured appender. + * @param layout layout, may not be null. + * @param targetStr target, either "System.err" or "System.out". + */ + public ConsoleAppender(final Layout layout, final String targetStr) { + setLayout(layout); + setTarget(targetStr); + activateOptions(); + } + + /** + * Sets the value of the Target option. Recognized values + * are "System.out" and "System.err". Any other value will be + * ignored. + * */ + public void setTarget(final String value) { + String v = value.trim(); + + if (SYSTEM_OUT.equalsIgnoreCase(v)) { + target = SYSTEM_OUT; + } else if (SYSTEM_ERR.equalsIgnoreCase(v)) { + target = SYSTEM_ERR; + } else { + getLogger().warn("[{}] should be System.out or System.err.", value); + getLogger().warn("Using previously set target, System.out by default."); + } + } + + /** + * Returns the current value of the Target property. The + * default value of the option is "System.out". + * + * See also {@link #setTarget}. + * */ + public String getTarget() { + return target; + } + + /** + * Sets whether the appender honors reassignments of System.out + * or System.err made after configuration. + * @param newValue if true, appender will use value of System.out or + * System.err in force at the time when logging events are appended. + * @since 1.2.13 + */ + public final void setFollow(final boolean newValue) { + follow = newValue; + } + + /** + * Gets whether the appender honors reassignments of System.out + * or System.err made after configuration. + * @return true if appender will use value of System.out or + * System.err in force at the time when logging events are appended. + * @since 1.2.13 + */ + public final boolean getFollow() { + return follow; + } + + + /** + * Prepares the appender for use. + */ + public void activateOptions() { + if (follow) { + if (target.equals(SYSTEM_ERR)) { + setWriter(createWriter(new SystemErrStream())); + } else { + setWriter(createWriter(new SystemOutStream())); + } + } else { + if (target.equals(SYSTEM_ERR)) { + setWriter(createWriter(System.err)); + } else { + setWriter(createWriter(System.out)); + } + } + + super.activateOptions(); + } + + /** + * {@inheritDoc} + */ + protected + final + void closeWriter() { + if (follow) { + super.closeWriter(); + } + } + + + /** + * An implementation of OutputStream that redirects to the + * current System.err. + * + */ + private static class SystemErrStream extends OutputStream { + public SystemErrStream() { + } + + public void close() { + } + + public void flush() { + System.err.flush(); + } + + public void write(final byte[] b) throws IOException { + System.err.write(b); + } + + public void write(final byte[] b, final int off, final int len) + throws IOException { + System.err.write(b, off, len); + } + + public void write(final int b) throws IOException { + System.err.write(b); + } + } + + /** + * An implementation of OutputStream that redirects to the + * current System.out. + * + */ + private static class SystemOutStream extends OutputStream { + public SystemOutStream() { + } + + public void close() { + } + + public void flush() { + System.out.flush(); + } + + public void write(final byte[] b) throws IOException { + System.out.write(b); + } + + public void write(final byte[] b, final int off, final int len) + throws IOException { + System.out.write(b, off, len); + } + + public void write(final int b) throws IOException { + System.out.write(b); + } + } +} + Index: src/java/org/apache/log4j/concurrent/WriterAppender.java =================================================================== --- src/java/org/apache/log4j/concurrent/WriterAppender.java (revision 0) +++ src/java/org/apache/log4j/concurrent/WriterAppender.java (revision 0) @@ -0,0 +1,314 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.concurrent; + +import org.apache.log4j.spi.LoggingEvent; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import org.apache.log4j.Layout; + +// Contibutors: Jens Uwe Pipka +// Ben Sandee + +/** + WriterAppender appends log events to a {@link java.io.Writer} or an + {@link java.io.OutputStream} depending on the user's choice. + + @author Ceki Gülcü + @since 1.1 */ +public class WriterAppender extends ConcurrentAppender { + + /** + Immediate flush means that the underlying writer or output stream + will be flushed at the end of each append operation. Immediate + flush is slower but ensures that each append request is actually + written. If immediateFlush is set to + false, then there is a good chance that the last few + logs events are not actually written to persistent media if and + when the application crashes. + +

The immediateFlush variable is set to + true by default. + + */ + protected boolean immediateFlush = true; + + /** + The encoding to use when opening an InputStream.

The + encoding variable is set to null by + default which results in the utilization of the system's default + encoding. */ + protected String encoding; + + /** + * This is the {@link Writer Writer} where we will write to. + * Do not directly use this object without obtaining a write lock. + */ + protected Writer writer; + + /** + * The default constructor does nothing. + * */ + public WriterAppender() { + super(false); + } + + /** + If the ImmediateFlush option is set to + true, the appender will flush at the end of each + write. This is the default behavior. If the option is set to + false, then the underlying stream can defer writing + to physical medium to a later time. + +

Avoiding the flush operation at the end of each append results in + a performance gain of 10 to 20 percent. However, there is safety + tradeoff involved in skipping flushing. Indeed, when flushing is + skipped, then it is likely that the last few log events will not + be recorded on disk when the application exits. This is a high + price to pay even for a 20% performance gain. + */ + public void setImmediateFlush(boolean value) { + immediateFlush = value; + } + + /** + Returns value of the ImmediateFlush option. + */ + public boolean getImmediateFlush() { + return immediateFlush; + } + + /** + * Activates options. Should be called only once. + */ + public void activateOptions() { + + if (getLayout() == null) { + getLogger().error( + "No layout set for the appender named [{}].", name); + return; + } + + if (this.writer == null) { + getLogger().error( + "No writer set for the appender named [{}].", name); + return; + } + + active.set(true); + + } + + /** + This method is called by the {@link AppenderSkeleton#doAppend} + method. + +

If the output stream exists and is writable then write a log + statement to the output stream. Otherwise, write a single warning + message to System.err. + +

The format of the output will depend on this appender's + layout. + + */ + public void append(LoggingEvent event) { + subAppend(event); + } + + /** + Close this appender instance. The underlying stream or writer is + also closed. +

Closed appenders cannot be reused. + */ + protected void internalClose() { + closeWriter(); + } + + /** + * Close the underlying {@link java.io.Writer}. + */ + protected void closeWriter() { + getWriteLock(); + try { + if (this.writer == null) + return; + try { + // before closing we have to output the footer + writeFooter(); + this.writer.close(); + this.writer = null; + } catch (IOException e) { + getLogger().error("Could not close writer for WriterAppener named "+name, e); + } + } finally { + releaseWriteLock(); + } + } + + /** + Returns an OutputStreamWriter when passed an OutputStream. The + encoding used will depend on the value of the + encoding property. If the encoding value is + specified incorrectly the writer will be opened using the default + system encoding (an error message will be printed to the loglog. */ + protected OutputStreamWriter createWriter(OutputStream os) { + OutputStreamWriter retval = null; + + String enc = getEncoding(); + + if (enc != null) { + try { + retval = new OutputStreamWriter(os, enc); + } catch (IOException e) { + getLogger().warn("Error initializing output writer."); + getLogger().warn("Unsupported encoding?"); + } + } + + if (retval == null) { + retval = new OutputStreamWriter(os); + } + + return retval; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String value) { + encoding = value; + } + + /** +

Sets the Writer where the log output will go. The + specified Writer must be opened by the user and be + writable. + +

The java.io.Writer will be closed when the + appender instance is closed. + + +

WARNING: Logging to an unopened Writer will fail. +

+ @param writer An already opened Writer. */ + public void setWriter(Writer writer) { + // close any previously opened writer + closeWriter(); + + getWriteLock(); + try { + this.writer = writer; + writeHeader(); + } finally { + releaseWriteLock(); + } + } + + /** + * Actual writing occurs here. + *

Most subclasses of WriterAppender will need to override + * this method. + */ + protected void subAppend(LoggingEvent event) { + try { + + // Format first + Layout layout = getLayout(); + String se = layout.format(event); + String st[] = null; + if (layout.ignoresThrowable()) { + st = event.getThrowableStrRep(); + } + + // Write as one message + synchronized (this.writer) { + this.writer.write(se); + if (st != null) { + int len = st.length; + for (int i = 0; i < len; i++) { + this.writer.write(st[i]); + this.writer.write(Layout.LINE_SEP); + } + } + } + + if (this.immediateFlush) + this.writer.flush(); + + } catch (IOException ioe) { + boolean wasOrder = active.set(false); + if (wasOrder) { + getLogger().error("IO failure for appender named "+name, ioe); + } + } + } + + /** + The WriterAppender requires a layout. Hence, this method returns + true. + */ + public boolean requiresLayout() { + return true; + } + + /** + * Write a footer as produced by the embedded layout's {@link + * Layout#getFooter} method. + */ + protected void writeFooter() { + Layout layout = getLayout(); + if (layout != null) { + String f = layout.getFooter(); + + if ((f != null) && (this.writer != null)) { + try { + this.writer.write(f); + } catch(IOException ioe) { + active.set(false); + getLogger().error("Failed to write footer for Appender named "+name, ioe); + } + } + } + } + + /** + * Write a header as produced by the embedded layout's {@link + * Layout#getHeader} method. + */ + protected void writeHeader() { + Layout layout = getLayout(); + if (layout != null) { + String h = layout.getHeader(); + + if ((h != null) && (this.writer != null)) { + try { + this.writer.write(h); + this.writer.flush(); + } catch(IOException ioe) { + active.set(false); + getLogger().error("Failed to write header for WriterAppender named "+name, ioe); + } + } + } + } + + +} Index: src/java/org/apache/log4j/concurrent/FileAppender.java =================================================================== --- src/java/org/apache/log4j/concurrent/FileAppender.java (revision 0) +++ src/java/org/apache/log4j/concurrent/FileAppender.java (revision 0) @@ -0,0 +1,290 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.log4j.concurrent; + +import java.io.Writer; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.apache.log4j.Layout; +import org.apache.log4j.helpers.OptionConverter; + + +// Contibutors: Jens Uwe Pipka +// Ben Sandee + +/** + * FileAppender appends log events to a file. + * + *

Support for java.io.Writer and console appending + * has been deprecated and then removed. See the replacement + * solutions: {@link WriterAppender} and {@link ConsoleAppender}. + * + * @author Ceki Gülcü + * */ +public class FileAppender extends WriterAppender { + + public static final int DEFAULT_BUFFER_SIZE = 8 * 1024; + + /** + * Controls whether to append to or truncate an existing file. + * The default value for this variable is + * true, meaning that by default a FileAppender will + * append to an existing file and not truncate it. + * + *

This option is meaningful only if the FileAppender opens the file. + */ + protected boolean fileAppend = true; + + /** + The name of the log file. */ + protected String fileName = null; + + /** + Do we do bufferedIO? */ + protected boolean bufferedIO = true; + + /** + The size of the IO buffer. Default is 8K. */ + protected int bufferSize = DEFAULT_BUFFER_SIZE; + + /** + The default constructor does not do anything. + */ + public FileAppender() { + } + + /** + Instantiate a FileAppender and open the file + designated by filename. The opened filename will + become the output destination for this appender. + +

If the append parameter is true, the file will be + appended to. Otherwise, the file designated by + filename will be truncated before being opened. + +

If the bufferedIO parameter is true, + then buffered IO will be used to write to the output file. + + */ + public FileAppender( + Layout layout, String filename, boolean append, boolean bufferedIO, + int bufferSize) throws IOException { + setLayout(layout); + this.setFile(filename, append, bufferedIO, bufferSize); + activateOptions(); + } + + /** + Instantiate a FileAppender and open the file designated by + filename. The opened filename will become the output + destination for this appender. + +

If the append parameter is true, the file will be + appended to. Otherwise, the file designated by + filename will be truncated before being opened. + */ + public FileAppender(Layout layout, String filename, boolean append) + throws IOException { + this(layout, filename, append, false, DEFAULT_BUFFER_SIZE); + } + + /** + Instantiate a FileAppender and open the file designated by + filename. The opened filename will become the output + destination for this appender. + +

The file will be appended to. */ + public FileAppender(Layout layout, String filename) throws IOException { + this(layout, filename, true); + activateOptions(); + } + + /** + The File property takes a string value which should be the + name of the file to append to. + +

Note that the special values + "System.out" or "System.err" are no longer honored. + +

Note: Actual opening of the file is made when {@link + #activateOptions} is called, not when the options are set. */ + public void setFile(String file) { + // Trim spaces from both ends. The users probably does not want + // trailing spaces in file names. + String val = file.trim(); + fileName = OptionConverter.stripDuplicateBackslashes(val); + } + + /** + Returns the value of the Append option. + */ + public boolean getAppend() { + return fileAppend; + } + + /** Returns the value of the File option. */ + public String getFile() { + return fileName; + } + + /** + If the value of File is not null, then {@link + #setFile} is called with the values of File and + Append properties. + + @since 0.8.1 */ + public void activateOptions() { + if (fileName != null) { + try { + setFile(fileName, fileAppend, bufferedIO, bufferSize); + super.activateOptions(); + } catch (java.io.IOException e) { + getLogger().error( + "setFile(" + fileName + "," + fileAppend + ") call failed.", e); + } + } else { + getLogger().error("File option not set for appender [{}].", name); + getLogger().warn("Are you using FileAppender instead of ConsoleAppender?"); + } + } + + /** + * Closes the previously opened file. + * + * @deprecated Use the super class' {@link #closeWriter} method instead. + */ + protected void closeFile() { + closeWriter(); + } + + /** + Get the value of the BufferedIO option. + +

BufferedIO will significantly increase performance on heavily + loaded systems. + + */ + public boolean getBufferedIO() { + return this.bufferedIO; + } + + /** + Get the size of the IO buffer. + */ + public int getBufferSize() { + return this.bufferSize; + } + + /** + The Append option takes a boolean value. It is set to + true by default. If true, then File + will be opened in append mode by {@link #setFile setFile} (see + above). Otherwise, {@link #setFile setFile} will open + File in truncate mode. + +

Note: Actual opening of the file is made when {@link + #activateOptions} is called, not when the options are set. + */ + public void setAppend(boolean flag) { + fileAppend = flag; + } + + /** + The BufferedIO option takes a boolean value. It is set to + false by default. If true, then File + will be opened and the resulting {@link java.io.Writer} wrapped + around a {@link BufferedWriter}. + + BufferedIO will significantly increase performance on heavily + loaded systems. + + */ + public void setBufferedIO(boolean bufferedIO) { + this.bufferedIO = bufferedIO; + } + + /** + Set the size of the IO buffer. + */ + public void setBufferSize(int bufferSize) { + this.bufferSize = bufferSize; + } + + /** +

Sets and opens the file where the log output will + go. The specified file must be writable. + +

If there was already an opened file, then the previous file + is closed first. + +

Do not use this method directly. To configure a FileAppender + or one of its subclasses, set its properties one by one and then + call activateOptions. + + @param filename The path to the log file. + @param append If true will append to fileName. Otherwise will + truncate fileName. + @param bufferedIO + @param bufferSize + + @throws IOException + + */ + public void setFile( + String filename, boolean append, boolean bufferedIO, int bufferSize) + throws IOException { + getLogger().debug("setFile called: {}, {}", fileName, append?"true":"false"); + + FileOutputStream ostream; + try { + // + // attempt to create file + // + ostream = new FileOutputStream(filename, append); + } catch(FileNotFoundException ex) { + // + // if parent directory does not exist then + // attempt to create it and try to create file + // see bug 9150 + // + File parentDir = new File(new File(filename).getParent()); + if(!parentDir.exists() && parentDir.mkdirs()) { + ostream = new FileOutputStream(filename, append); + } else { + throw ex; + } + } + Writer writer = createWriter(ostream); + + if (bufferedIO) { + writer = new BufferedWriter(writer, bufferSize); + } + + this.fileAppend = append; + this.bufferedIO = bufferedIO; + this.fileName = filename; + this.bufferSize = bufferSize; + + setWriter(writer); + getLogger().debug("setFile ended"); + } + +} Index: src/java/org/apache/log4j/HTMLLayout.java =================================================================== --- src/java/org/apache/log4j/HTMLLayout.java (revision 375405) +++ src/java/org/apache/log4j/HTMLLayout.java (working copy) @@ -38,6 +38,10 @@ * are specified using a conversion pattern. See * {@link org.apache.log4j.PatternLayout} for documentation on the available * patterns. + +

This class is thread safe; it may be called by multiple threads + simultaneously to format messages. + * * @author Ceki Gülcü * @author Steve Mactaggart @@ -103,7 +107,7 @@ // counter keeping track of the rows output - private long counter = 0; + private int counter = 0; /** * Constructs a PatternLayout using the DEFAULT_LAYOUT_PATTERN. @@ -369,9 +373,9 @@ */ public String format(LoggingEvent event) { - boolean odd = true; - if(((counter++) & 1) == 0) { - odd = false; + boolean odd; + synchronized (this) { + odd = ((counter++) & 1) != 0; } String level = event.getLevel().toString().toLowerCase(); Index: src/java/org/apache/log4j/AppenderSkeleton.java =================================================================== --- src/java/org/apache/log4j/AppenderSkeleton.java (revision 375405) +++ src/java/org/apache/log4j/AppenderSkeleton.java (working copy) @@ -54,7 +54,7 @@ * * @deprecated as of 1.3 */ - protected org.apache.log4j.spi.ErrorHandler errorHandler = new org.apache.log4j.helpers.OnlyOnceErrorHandler(); + protected org.apache.log4j.spi.ErrorHandler errorHandler = org.apache.log4j.helpers.OnlyOnceErrorHandler.INSTANCE; /** * The first filter in the filter chain. Set to null initially. @@ -165,13 +165,13 @@ /** * Return the hardcoded OnlyOnceErrorHandler for this Appender. - * ErrorHandler's are no longer utilized as of version 1.3. + * ErrorHandlers are no longer utilized as of version 1.3. * * @since 0.9.0 * @deprecated As of 1.3 */ public org.apache.log4j.spi.ErrorHandler getErrorHandler() { - return this.errorHandler; + return org.apache.log4j.helpers.OnlyOnceErrorHandler.INSTANCE; } /** @@ -199,7 +199,7 @@ } /** - * Returns the name of this FileAppender. + * Returns the name of this appender. */ public final String getName() { return this.name; Index: src/java/org/apache/log4j/PatternLayout.java =================================================================== --- src/java/org/apache/log4j/PatternLayout.java (revision 375405) +++ src/java/org/apache/log4j/PatternLayout.java (working copy) @@ -405,6 +405,9 @@ Philip E. Margolis' highly recommended book "C -- a Software Engineering Approach", ISBN 0-387-97389-3. +

This class is thread safe; it may be called by multiple threads + simultaneously to format messages. + @author James P. Cakalic @author Ceki Gülcü @@ -455,11 +458,6 @@ private FormattingInfo[] patternFields; /** - * String buffer used in formatting. - */ - private StringBuffer buf = new StringBuffer(); - - /** * True if any element in pattern formats information from exceptions. */ private boolean handlesExceptions; @@ -550,21 +548,16 @@ } /** - * Formats a logging event to a writer. - * @param event logging event to be formatted. - */ - public String format(final LoggingEvent event) { - buf.setLength(0); - + * Formats an event. + */ + public String format(LoggingEvent event) { + StringBuffer buf = new StringBuffer(80); for (int i = 0; i < patternConverters.length; i++) { int startField = buf.length(); patternConverters[i].format(event, buf); patternFields[i].format(startField, buf); } - - String retval = buf.toString(); - buf.setLength(0); - return retval; + return buf.toString(); } /** Index: src/java/org/apache/log4j/spi/LocationInfo.java =================================================================== --- src/java/org/apache/log4j/spi/LocationInfo.java (revision 375405) +++ src/java/org/apache/log4j/spi/LocationInfo.java (working copy) @@ -106,9 +106,9 @@ } if(PlatformInfo.hasStackTraceElement()) { - StackTraceElementExtractor.extract(this, t, fqnOfInvokingClass); + // StackTraceElementExtractor.extract(this, t, fqnOfInvokingClass); } else { - LegacyExtractor.extract(this, t, fqnOfInvokingClass); + // LegacyExtractor.extract(this, t, fqnOfInvokingClass); } } Index: src/java/org/apache/log4j/spi/location/LegacyExtractor.java =================================================================== --- src/java/org/apache/log4j/spi/location/LegacyExtractor.java (revision 375405) +++ src/java/org/apache/log4j/spi/location/LegacyExtractor.java (working copy) @@ -22,9 +22,7 @@ import java.io.PrintWriter; import java.io.StringWriter; -import org.apache.log4j.spi.LocationInfo; - /** * Extract location information from a throwable. The techniques used here * work on all JDK platforms including those prior to JDK 1.4. Index: src/java/org/apache/log4j/spi/location/StackTraceElementExtractor.java =================================================================== --- src/java/org/apache/log4j/spi/location/StackTraceElementExtractor.java (revision 375405) +++ src/java/org/apache/log4j/spi/location/StackTraceElementExtractor.java (working copy) @@ -15,7 +15,6 @@ */ package org.apache.log4j.spi.location; -import org.apache.log4j.spi.LocationInfo; import java.lang.reflect.Method; Index: src/java/org/apache/log4j/helpers/SyslogWriter.java =================================================================== --- src/java/org/apache/log4j/helpers/SyslogWriter.java (revision 375405) +++ src/java/org/apache/log4j/helpers/SyslogWriter.java (working copy) @@ -36,7 +36,7 @@ @since 0.7.3 */ public class SyslogWriter extends Writer { - final int SYSLOG_PORT = 514; + final static int SYSLOG_PORT = 514; String syslogHost; private InetAddress address; private DatagramSocket ds; Index: src/java/org/apache/log4j/helpers/LogLog.java =================================================================== --- src/java/org/apache/log4j/helpers/LogLog.java (revision 375405) +++ src/java/org/apache/log4j/helpers/LogLog.java (working copy) @@ -64,7 +64,7 @@

Note that the search for all option names is case sensitive. */ public static final String CORE_DEBUG_KEY = "log4j.coreDebug"; - protected static boolean debugEnabled = false; + private static boolean debugEnabled = false; private static final String PREFIX = "log4j: "; private static final String ERR_PREFIX = "log4j:ERROR "; @@ -87,6 +87,13 @@ } /** + Returns true if debug is enabled. + */ + public static boolean isDebugEnabled() { + return debugEnabled; + } + + /** This method is used to output log4j internal debug statements. Output goes to System.out. */ Index: src/java/org/apache/log4j/helpers/OnlyOnceErrorHandler.java =================================================================== --- src/java/org/apache/log4j/helpers/OnlyOnceErrorHandler.java (revision 375405) +++ src/java/org/apache/log4j/helpers/OnlyOnceErrorHandler.java (working copy) @@ -19,6 +19,7 @@ import org.apache.log4j.Appender; import org.apache.log4j.Logger; import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.ErrorHandler; /** ErrorHandler and its implementations are no longer @@ -32,6 +33,12 @@ @deprecated As of 1.3 */ public class OnlyOnceErrorHandler implements org.apache.log4j.spi.ErrorHandler { + + /** + * Default instance of this class. + */ + public static final ErrorHandler INSTANCE = new OnlyOnceErrorHandler(); + public void setLogger(Logger logger) {} public void activateOptions() {} public void error(String message, Exception e, int errorCode) {} Index: src/java/org/apache/log4j/joran/JoranConfigurator.java =================================================================== --- src/java/org/apache/log4j/joran/JoranConfigurator.java (revision 375405) +++ src/java/org/apache/log4j/joran/JoranConfigurator.java (working copy) @@ -71,7 +71,6 @@ public class JoranConfigurator extends ConfiguratorBase { Interpreter joranInterpreter; LoggerRepository repository; - boolean listAppnderAttached = false; public JoranConfigurator() { } Index: src/java/org/apache/log4j/WriterAppender.java =================================================================== --- src/java/org/apache/log4j/WriterAppender.java (revision 375405) +++ src/java/org/apache/log4j/WriterAppender.java (working copy) @@ -24,7 +24,6 @@ import java.io.OutputStreamWriter; import java.io.Writer; import org.apache.log4j.helpers.QuietWriter; -import org.apache.log4j.helpers.LogLog; // Contibutors: Jens Uwe Pipka // Ben Sandee Index: src/java/org/apache/log4j/pattern/DatePatternConverter.java =================================================================== --- src/java/org/apache/log4j/pattern/DatePatternConverter.java (revision 375405) +++ src/java/org/apache/log4j/pattern/DatePatternConverter.java (working copy) @@ -110,7 +110,7 @@ /** * {@inheritDoc} */ - public void format(final LoggingEvent event, final StringBuffer output) { + public synchronized void format(final LoggingEvent event, final StringBuffer output) { df.format(event.getTimeStamp(), output); } @@ -130,7 +130,7 @@ * @param date date * @param toAppendTo buffer to which formatted date is appended. */ - public void format(final Date date, final StringBuffer toAppendTo) { + public synchronized void format(final Date date, final StringBuffer toAppendTo) { df.format(date.getTime(), toAppendTo); } } Index: src/java/org/apache/log4j/pattern/PatternConverter.java =================================================================== --- src/java/org/apache/log4j/pattern/PatternConverter.java (revision 375405) +++ src/java/org/apache/log4j/pattern/PatternConverter.java (working copy) @@ -26,6 +26,9 @@ individual PatternConverters. Each of which is responsible for converting an object in a converter specific manner. +

A PatternConverter should be written to be thread-safe. + This allows multiple threads to safely use the same pattern converter. + @author James P. Cakalic @author Ceki Gülcü @author Chris Nokes @@ -56,6 +59,8 @@ /** * Formats an object into a string buffer. + * This method may be called simultaneously by multiple threads. + * * @param obj event to format, may not be null. * @param toAppendTo string buffer to which the formatted event will be appended. May not be null. */ Index: src/java/org/apache/log4j/FileAppender.java =================================================================== --- src/java/org/apache/log4j/FileAppender.java (revision 375405) +++ src/java/org/apache/log4j/FileAppender.java (working copy) @@ -39,6 +39,9 @@ * @author Ceki Gülcü * */ public class FileAppender extends WriterAppender { + + public static final int DEFAULT_BUFFER_SIZE = 8 * 1024; + /** * Controls whether to append to or truncate an existing file. * The default value for this variable is @@ -55,11 +58,11 @@ /** Do we do bufferedIO? */ - protected boolean bufferedIO = false; + protected boolean bufferedIO = true; /** The size of the IO buffer. Default is 8K. */ - protected int bufferSize = 8 * 1024; + protected int bufferSize = DEFAULT_BUFFER_SIZE; /** The default constructor does not do anything. @@ -176,7 +179,7 @@ /** Get the value of the BufferedIO option. -

BufferedIO will significatnly increase performance on heavily +

BufferedIO will significantly increase performance on heavily loaded systems. */ @@ -211,16 +214,12 @@ will be opened and the resulting {@link java.io.Writer} wrapped around a {@link BufferedWriter}. - BufferedIO will significatnly increase performance on heavily + BufferedIO will significantly increase performance on heavily loaded systems. */ public void setBufferedIO(boolean bufferedIO) { this.bufferedIO = bufferedIO; - - if (bufferedIO) { - immediateFlush = false; - } } /** @@ -255,14 +254,9 @@ throws IOException { getLogger().debug("setFile called: {}, {}", fileName, append?"true":"false"); - // It does not make sense to have immediate flush and bufferedIO. - if (bufferedIO) { - setImmediateFlush(false); - } - closeWriter(); - FileOutputStream ostream = null; + FileOutputStream ostream; try { // // attempt to create file Index: src/java/org/apache/log4j/plugins/Receiver.java =================================================================== --- src/java/org/apache/log4j/plugins/Receiver.java (revision 375405) +++ src/java/org/apache/log4j/plugins/Receiver.java (working copy) @@ -59,14 +59,6 @@ public abstract class Receiver extends PluginSkeleton implements Thresholdable { protected Level thresholdLevel; - /* - * An instance specific logger which must be accessed through the getLogger() - * method. - */ - private Logger logger; - - - /** Sets the receiver theshold to the given level. Index: src/java/org/apache/log4j/net/SyslogAppender.java =================================================================== --- src/java/org/apache/log4j/net/SyslogAppender.java (revision 375405) +++ src/java/org/apache/log4j/net/SyslogAppender.java (working copy) @@ -128,7 +128,6 @@ //SyslogTracerPrintWriter stp; private SyslogWriter sw; - private final Calendar calendar = Calendar.getInstance(); private long now = -1; private Date date = new Date(); private StringBuffer timestamp = new StringBuffer(); Index: src/java/org/apache/log4j/net/SocketNode.java =================================================================== --- src/java/org/apache/log4j/net/SocketNode.java (revision 375405) +++ src/java/org/apache/log4j/net/SocketNode.java (working copy) @@ -53,7 +53,6 @@ private boolean paused; private Socket socket; private Receiver receiver; - private SocketNodeEventListener listener; private List listenerList = Collections.synchronizedList(new ArrayList()); /** @@ -186,7 +185,7 @@ } // send event to listener, if configured - if (listener != null || listenerList.size()>0) { + if (listenerList.size()>0) { fireSocketClosedEvent(listenerException); } } Index: src/java/org/apache/log4j/xml/XMLLayout.java =================================================================== --- src/java/org/apache/log4j/xml/XMLLayout.java (revision 375405) +++ src/java/org/apache/log4j/xml/XMLLayout.java (working copy) @@ -59,6 +59,9 @@ * for output generated by log4j versions prior to log4j 1.2 (final release) and * "1.2" for relase 1.2 and later. * +

This class is thread safe; it may be called by multiple threads + simultaneously to format messages. + * Contributors: Mathias Bogaert * * @author Ceki Gülcü @@ -207,7 +210,7 @@ String propName = propIter.next().toString(); buf.append(" \r\n"); } Index: src/java/org/apache/log4j/config/PropertySetter.java =================================================================== --- src/java/org/apache/log4j/config/PropertySetter.java (revision 375405) +++ src/java/org/apache/log4j/config/PropertySetter.java (working copy) @@ -60,7 +60,6 @@ public static final int NOT_FOUND = 0; public static final int AS_PROPERTY = 1; public static final int AS_COLLECTION = 2; - Logger logger; protected Object obj; protected Class objClass; protected PropertyDescriptor[] props; Index: src/java/org/apache/log4j/RollingFileAppender.java =================================================================== --- src/java/org/apache/log4j/RollingFileAppender.java (revision 375405) +++ src/java/org/apache/log4j/RollingFileAppender.java (working copy) @@ -41,13 +41,6 @@ public class RollingFileAppender implements Appender, OptionHandler { /** - * It is assumed and enforced that errorHandler is never null. - * - * @deprecated as of 1.3 - */ - private final org.apache.log4j.spi.ErrorHandler errorHandler = new org.apache.log4j.helpers.OnlyOnceErrorHandler(); - - /** The default maximum file size is 10MB. */ private long maxFileSize = 10 * 1024 * 1024; @@ -398,7 +391,7 @@ * @deprecated As of 1.3 */ public final org.apache.log4j.spi.ErrorHandler getErrorHandler() { - return this.errorHandler; + return org.apache.log4j.helpers.OnlyOnceErrorHandler.INSTANCE; } /** Index: src/java/org/apache/log4j/SimpleLayout.java =================================================================== --- src/java/org/apache/log4j/SimpleLayout.java (revision 375405) +++ src/java/org/apache/log4j/SimpleLayout.java (working copy) @@ -29,6 +29,9 @@ DEBUG - Hello world +

This class is thread safe; it may be called by multiple threads + simultaneously to format messages. +

@author Ceki Gülcü @since version 0.7.0 @@ -36,7 +39,6 @@

{@link PatternLayout} offers a much more powerful alternative. */ public class SimpleLayout extends Layout { - StringBuffer sbuf = new StringBuffer(128); public SimpleLayout() { } Index: src/java/org/apache/log4j/scheduler/Scheduler.java =================================================================== --- src/java/org/apache/log4j/scheduler/Scheduler.java (revision 375405) +++ src/java/org/apache/log4j/scheduler/Scheduler.java (working copy) @@ -82,7 +82,7 @@ // if the job is the first on the list, then notify the scheduler thread // to schedule a new job if(i == 0) { - this.notify(); + this.notifyAll(); } return true; } else { @@ -154,11 +154,11 @@ jobList.add(i, newSJE); // if the jobList was empty, then notify the scheduler thread if(i == 0) { - this.notify(); + this.notifyAll(); } } - public void shutdown() { + public synchronized void shutdown() { shutdown = true; } Index: tests/src/java/org/apache/log4j/DeadlockTest.java =================================================================== --- tests/src/java/org/apache/log4j/DeadlockTest.java (revision 375405) +++ tests/src/java/org/apache/log4j/DeadlockTest.java (working copy) @@ -22,8 +22,6 @@ /** * Test case for bug http://nagoya.apache.org/bugzilla/show_bug.cgi?id=24159 * - * Actually this one is impossible to fix. - * * @author Elias Ross * @author Ceki Gulcu */ @@ -31,14 +29,18 @@ static long RUNLENGTH = 10000; Logger logger = Logger.getLogger("DeadlockTest"); - public DeadlockTest() { - super("DeadlockTest"); + public DeadlockTest(String name) { + super(name); } protected void setUp() throws Exception { super.setUp(); System.out.println("in setup"); - BasicConfigurator.configure(); + + Logger root = Logger.getRootLogger(); + root.addAppender( + new org.apache.log4j.concurrent.ConsoleAppender( + new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN))); } protected void tearDown() throws Exception { Index: tests/build.xml =================================================================== --- tests/build.xml (revision 375405) +++ tests/build.xml (working copy) @@ -798,6 +798,14 @@ + + + + + + + +