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,425 @@ +/* + * 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.Level; +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. + * Should this be volatile? + */ + private Level 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 activate() { + 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 Level 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(Level level) { + Level 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; + + try { + + // Prevent concurrent re-entry + // (There might be a cheaper way to do this) + // (Or maybe we remove this lock) + if (guard.get() != null) + return; + guard.set(this); // arbitrary thread lock object + + 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. + * + * @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(Level 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-aquire 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,97 @@ +/* + * 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; + +/** + * 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ü + * @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; + + /** + * As in most cases, the default constructor does nothing. + */ + public ConsoleAppender() { + } + + public ConsoleAppender(Layout layout) { + this(layout, SYSTEM_OUT); + } + + public ConsoleAppender(Layout layout, 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(String value) { + String v = value.trim(); + + if (SYSTEM_OUT.equalsIgnoreCase(v)) { + target = SYSTEM_OUT; + } else if (SYSTEM_ERR.equalsIgnoreCase(v)) { + target = SYSTEM_ERR; + } else { + targetWarn(value); + } + } + + /** + * 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; + } + + void targetWarn(String val) { + getLogger().warn("[{}] should be System.out or System.err.", val); + getLogger().warn("Using previously set target, System.out by default."); + } + + public void activateOptions() { + if (target.equals(SYSTEM_OUT)) { + setWriter(createWriter(System.out)); + } else { + setWriter(createWriter(System.err)); + } + super.activateOptions(); + } + + /** + * This method overrides the parent {@link + * WriterAppender#closeWriter} implementation to do nothing because + * the console stream is not ours to close. + * */ + protected final void closeWriter() { + } +} 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,309 @@ +/* + * 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 modify this class without obtaining a write lock. + */ + protected Writer writer; + + /** + * This 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) { + 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 (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,269 @@ +/* + * 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.FileOutputStream; +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; + + /** + * Append to or truncate the 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); + 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); + } catch (java.io.IOException e) { + getLogger().error( + "setFile(" + fileName + "," + fileAppend + ") call failed.", e); + } + } else { + //LogLog.error("File option not set for appender ["+name+"]."); + getLogger().warn("File option not set for appender [{}].", name); + getLogger().warn("Are you using FileAppender instead of ConsoleAppender?"); + } + super.activateOptions(); + } + + /** + * 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 significatnly 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"); + + Writer writer = createWriter(new FileOutputStream(filename, append)); + + 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/AppenderSkeleton.java =================================================================== --- src/java/org/apache/log4j/AppenderSkeleton.java (revision 351847) +++ src/java/org/apache/log4j/AppenderSkeleton.java (working copy) @@ -50,13 +50,6 @@ protected Level threshold; /** - * 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 first filter in the filter chain. Set to null initially. */ protected Filter headFilter; @@ -165,13 +158,10 @@ /** * Return the hardcoded OnlyOnceErrorHandler for this Appender. - * ErrorHandler's are no longer utilized as of version 1.3. - * - * @since 0.9.0 - * @deprecated As of 1.3 + * ErrorHandlers are no longer utilized as of version 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/PatternLayout.java =================================================================== --- src/java/org/apache/log4j/PatternLayout.java (revision 351847) +++ src/java/org/apache/log4j/PatternLayout.java (working copy) @@ -444,11 +444,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; @@ -537,23 +532,23 @@ } /** - * Formats a logging event to a writer. - * @param output writer to receive output. - * @param event logging event to be formatted. - * @throws IOException if unable to write content. - */ - public void format(Writer output, LoggingEvent event) - throws IOException { - 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); } + return buf.toString(); + } - output.write(buf.toString()); - buf.setLength(0); + /** + Produces a formatted string as specified by the conversion pattern. + */ + public void format(Writer output, LoggingEvent event) throws IOException { + output.write(format(event)); } /** Index: src/java/org/apache/log4j/spi/location/LocationInfo.java =================================================================== --- src/java/org/apache/log4j/spi/location/LocationInfo.java (revision 351847) +++ src/java/org/apache/log4j/spi/location/LocationInfo.java (working copy) @@ -41,7 +41,7 @@ * without real location info available. * @since 1.3 */ - public static LocationInfo NA_LOCATION_INFO = new LocationInfo(NA, NA, NA, NA); + public static final LocationInfo NA_LOCATION_INFO = new LocationInfo(NA, NA, NA, NA); Index: src/java/org/apache/log4j/spi/ThrowableInformation.java =================================================================== --- src/java/org/apache/log4j/spi/ThrowableInformation.java (revision 351847) +++ src/java/org/apache/log4j/spi/ThrowableInformation.java (working copy) @@ -62,6 +62,7 @@ public void extractStringRep(Throwable t, VectorWriter vw) { t.printStackTrace(vw); + /* // Check if the Throwable t has a nested Throwable. If so, invoke // extractStringRep recursively. // Note that the Throwable.getCause was added in JDK 1.4. The printStackTrace @@ -98,6 +99,7 @@ } catch (Exception e) { // do nothing } + */ } /** Index: src/java/org/apache/log4j/Layout.java =================================================================== --- src/java/org/apache/log4j/Layout.java (revision 351847) +++ src/java/org/apache/log4j/Layout.java (working copy) @@ -38,11 +38,6 @@ public static final String LINE_SEP = System.getProperty("line.separator"); public static final int LINE_SEP_LEN = LINE_SEP.length(); - - - - public CharArrayWriter charArrayWriter = new CharArrayWriter(1024); - String header; String footer; @@ -52,7 +47,7 @@ * Implement this method to create your own layout format. * */ public String format(LoggingEvent event) { - charArrayWriter.reset(); + CharArrayWriter charArrayWriter = new CharArrayWriter(80); try { format(charArrayWriter, event); } catch(IOException ie) { Index: src/java/org/apache/log4j/helpers/SyslogWriter.java =================================================================== --- src/java/org/apache/log4j/helpers/SyslogWriter.java (revision 351847) +++ 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 351847) +++ src/java/org/apache/log4j/helpers/LogLog.java (working copy) @@ -42,7 +42,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 "; @@ -65,6 +65,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 351847) +++ src/java/org/apache/log4j/helpers/OnlyOnceErrorHandler.java (working copy) @@ -33,6 +33,12 @@ @deprecated As of 1.3 */ public final class OnlyOnceErrorHandler implements 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 351847) +++ 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/pattern/DatePatternConverter.java =================================================================== --- src/java/org/apache/log4j/pattern/DatePatternConverter.java (revision 351847) +++ 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/plugins/Receiver.java =================================================================== --- src/java/org/apache/log4j/plugins/Receiver.java (revision 351847) +++ 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 351847) +++ src/java/org/apache/log4j/net/SyslogAppender.java (working copy) @@ -57,7 +57,6 @@ //SyslogTracerPrintWriter stp; SyslogWriter sw; - Calendar calendar = Calendar.getInstance(); long now = -1; Date date = new Date(); StringBuffer timestamp = new StringBuffer(); @@ -67,8 +66,6 @@ private SimpleDateFormat sdf = new SimpleDateFormat("MMM dd hh:mm:ss", new DateFormatSymbols(Locale.US)); - Layout layout; - public SyslogAppender() { super(false); } Index: src/java/org/apache/log4j/net/SocketNode.java =================================================================== --- src/java/org/apache/log4j/net/SocketNode.java (revision 351847) +++ 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 351847) +++ src/java/org/apache/log4j/xml/XMLLayout.java (working copy) @@ -206,7 +206,7 @@ String propName = propIter.next().toString(); output.write(" \r\n"); } Index: src/java/org/apache/log4j/config/PropertySetter.java =================================================================== --- src/java/org/apache/log4j/config/PropertySetter.java (revision 351847) +++ src/java/org/apache/log4j/config/PropertySetter.java (working copy) @@ -57,7 +57,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[] propertyDescriptors; Index: src/java/org/apache/log4j/RollingFileAppender.java =================================================================== --- src/java/org/apache/log4j/RollingFileAppender.java (revision 351847) +++ 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 351847) +++ src/java/org/apache/log4j/SimpleLayout.java (working copy) @@ -36,7 +36,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 351847) +++ 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 351847) +++ 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 351847) +++ tests/build.xml (working copy) @@ -753,6 +753,14 @@ + + + + + + + +