Jim,

  I do not know the internals of Log4J, but from what I see, everything
looks good.  Thank you for your prompt solution to the problem.  It has
saved me days of work :-)

Thanks,
Jim Prokash

-----Original Message-----
From: Jim Moore [mailto:[EMAIL PROTECTED]]
Sent: Friday, June 15, 2001 12:35 PM
To: 'LOG4J Users Mailing List'
Subject: RE: Do I need to use multiple hierarchies, and how?


Here it is.  Both of Jim's comments are handled, though as noted in the
class comment, #2 isn't as nice as I'd like.  If people think this is useful
(and agree that the basic idea is sound), then I'll go ahead and do the
substitution logic so you can have more control over the file name.

Please review it and let me know what you think.  It, at least, passes the
tests I put in the main() method.

-Jim Moore


/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 * 
 * This software is published under the terms of the Apache Software
 * License version 1.1, a copy of which has been included with this
 * distribution in the LICENSE.APL file.
 */

import java.io.*;
import java.util.*;

import org.apache.log4j.*;
import org.apache.log4j.spi.*;
import org.apache.log4j.helpers.*;


/**
 * ThreadFileAppender writes log events out to a file based upon
 * which thread is calling the appender.<p>
 * 
 * Currently, the naming of the file is very rudimentry, appending
 * the name of the thread (ie, <tt>Thread.getName()</tt>) and then
 * ".log" to the end of what was given to the appender in the
 * {@link #setFileName(String)} message.  It should use parameter
 * substitution instead.
 * 
 * @author {@link mailto:[EMAIL PROTECTED] Jim Moore}
 * @author Ceki G&uuml;lc&uuml; (much of this code was snagged from
WritterAppender)
 */
public class ThreadFileAppender extends AppenderSkeleton {

  private final HashMap outStreamCache;

  /**
   * 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 <code>immediateFlush</code> is set to
   * <code>false</code>, 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.<p>
   *
   * The <code>immediateFlush</code> variable is set to
   * <code>true</code> by default.
   */
  private boolean immediateFlush = true;

  /**
   * The name of the log file.
   */
  private String fileName;

  /**
   * Append to or truncate the file? The default value for this
   * variable is <code>true</code>, meaning that by default this
   * will append to an existing file and not truncate it.
   */
  private boolean fileAppend = true;


  public ThreadFileAppender() {
    super();
    outStreamCache = new HashMap();
  }


  /**
   * If the <b>ImmediateFlush</b> option is set to
   * <code>true</code>, the appender will flush at the end of each
   * write. This is the default behavior. If the option is set to
   * <code>false</code>, then the underlying stream can defer writing
   * to physical medium to a later time.
   * 
   * <p>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.
   * 
   * @param value
   */
  public void setImmediateFlush(boolean value) {
    immediateFlush = value;
  }


  /**
   * Returns value of the <b>ImmediateFlush</b> option.
   */
  public boolean getImmediateFlush() {
    return immediateFlush;
  }


  /**
   * The <b>File</b> option takes a string value which should be
   * the name of the file to append to. Special values "System.out" or
   * "System.err" are interpreted as the standard out and standard
   * error streams.<p>
   *
   * Note: Actual opening of the file is made when {@link
   * #subAppend(LogEvent)} is called, not when the options are set.
   * 
   * @param file
   */
  public void setFileName(String file) {
    // Trim spaces from both ends. The users probably does not want 
    // trailing spaces in file names.
    fileName = file.trim();
  }


  /**
   * Returns the value of the <b>FileName</b> option.
   */
  public String getFileName() {
    return fileName;
  }


  /**
   * The <b>Append</b> option takes a boolean value. It is set to
   * <code>true</code> by default. If true, then <code>File</code>
   * will be opened in append mode by {@link #setFileName(String)}.
   * Otherwise, {@link #setFileName(String) will open
   * <code>File</code> in truncate mode.<p>
   * 
   * Note: Actual opening of the file is made when {@link
   * #subAppend(LogEvent)} is called, not when the options are set.
   */
  public void setAppend(boolean flag) {
    fileAppend = flag;
  }


  /**
   * Returns the value of the <b>Append</b> option.
   */
  public boolean getAppend() {
    return fileAppend;
  }


  /**
   * Does nothing.
   */
  public void activateOptions() {
  }



  /**
   * This method is called by the {@link AppenderSkeleton#doAppend}
   * method.<p>
   *
   * If the output stream exists and is writable then write a log
   * statement to the output stream. Otherwise, write a single warning
   * message to <code>System.err</code>.<p>
   *
   * The format of the output will depend on this appender's
   * layout.
   * 
   * @param event
   */
  public void append(LoggingEvent event) {
    if (!checkEntryConditions()) {
      return;
    }
    subAppend(event);
  } 


  /**
   * This method determines if there is a sense in attempting to append.<p>
   * 
   * It checks whether there is a set output target and also if
   * there is a set layout. If these checks fail, then the boolean
   * value <code>false</code> is returned.
   */
  protected boolean checkEntryConditions() {
    if (closed) {
      LogLog.warn("Not allowed to write to a closed appender.");
      return false;
    }

    if (layout == null) {
      errorHandler.error("No layout set for the appender named
["+getName()+"].");
      return false;
    }

    return true;
  }


  /**
   * Actual writing occurs here.
   */
  protected void subAppend(LoggingEvent event) {
    try {
      String threadName = event.getThreadName();
      Writer out = (Writer)outStreamCache.get(threadName);
      if (out == null) {
        out = new BufferedWriter(new
FileWriter(getFileName()+"."+threadName+".log", getAppend()));
        writeHeader(out);
        outStreamCache.put(threadName, out);
      }

      out.write(layout.format(event));

      if (layout.ignoresThrowable()) {
        String[] s = event.getThrowableStrRep();
        if (s != null) {
          int len = s.length;
          for (int i = 0; i < len; i++) {
            out.write(s[i]);
            out.write(Layout.LINE_SEP);
          }
        }
      }

      if (immediateFlush) {
        out.flush();
      }
    }
    catch (FileNotFoundException exp) {
      LogLog.error("Could not find file:
"+getFileName()+"."+event.getThreadName()+".log", exp);
    }
    catch (IOException exp) {
      LogLog.error("Could write to file:
"+getFileName()+"."+event.getThreadName()+".log", exp);
    }
  }


  /**
   * Close this appender instance. The underlying writers are
   * also closed.<p>
   * 
   * Closed appenders cannot be reused.
   */
  public synchronized void close() {
    if (closed)
      return;
    closed = true;
    writeFooters();
    reset();
  }


  /**
   * Clear internal references to the writer and other variables.<p>
   * 
   * Subclasses can override this method for an alternate closing
   * behavior.
   */
  protected void reset() {
    Iterator iter = outStreamCache.values().iterator();
    while (iter.hasNext()) {
      try {
        ((Writer)iter.next()).close();
      }
      catch (IOException exp) {
        LogLog.error("Couldn't close writer", exp);
      }
    }
  }


  /**
   * The ThreadFileAppender requires a layout. Hence, this method returns
   * <code>true</code>.
   * 
   * @return true
   */
  public boolean requiresLayout() {
    return true;
  }


  /**
   * Write a footer as produced by the embedded layout's {@link
   * Layout#getFooter} method.
   */
  private void writeFooters() {
    if (layout != null) {
      String f = layout.getFooter();
      if (f != null) {
        Iterator iter = outStreamCache.values().iterator();
        while (iter.hasNext()) {
          try {
            ((Writer)iter.next()).write(f);
          }
          catch (IOException exp) {
            LogLog.error("Couldn't write footer", exp);
          }
        }
      }
    }
  }


  /**
   *    Write a header as produced by the embedded layout's {@link
   *    Layout#getHeader} method.
   */
  private void writeHeader(Writer out) {
    if (layout != null) {
      String h = layout.getHeader();
      if (h != null) {
        if (layout != null) {
          String f = layout.getFooter();
          if (f != null) {
            try {
              out.write(f);
            }
            catch (IOException exp) {
              LogLog.error("Couldn't write footer", exp);
            }
          }
        }
      }
    }
  }


  /**
   * Test the class
   */
  public static void main(String[] argv) {
    Category cat = Category.getRoot();
    ThreadFileAppender appender = new ThreadFileAppender();
    appender.setFileName("Test");
    appender.setLayout(new SimpleLayout());
    cat.addAppender(appender);

    for (int i=0; i < 5; i++) {
      (new Thread() {
        public void run() {
          Category.getRoot().warn("Hello from "+this);
        }
      }).start();
    }

    (new Thread("NamedThread") {
      public void run() {
        Category.getRoot().warn("First line");
        Category.getRoot().warn("Second line");
      }
    }).start();

    (new Thread("NamedThread") {
      public void run() {
        Category.getRoot().warn("Third line");
      }
    }).start();
  }

}




-----Original Message-----
From: Prokash, Jim [mailto:[EMAIL PROTECTED]]
Sent: Friday, June 15, 2001 9:20 AM
To: 'LOG4J Users Mailing List'
Subject: RE: Do I need to use multiple hierarchies, and how?


Chris,

  I see a few issues with this solution.  I also thought of this, however.

     1. Each worker thread makes use of other shared objects.  Each of these
"utility" objects have their own category.  If I were to use your
suggestion, each "utility" object would have to create a thread unique
category, which would cause a proliferation of categories. Instead of having
a category per class, I would now have a category per class per thread.

     2. Since the thread ids are generated at runtime, there would be no way
to configure the categories through a configuration file.

  I'm looking forward to the ThreadAppender that Jim Moore is working on.

Thanks for the feedback.

-Jim
-----Original Message-----
From: Christopher Taylor [mailto:[EMAIL PROTECTED]]
Sent: Thursday, June 14, 2001 7:33 PM
To: LOG4J Users Mailing List
Subject: Re: Do I need to use multiple hierarchies, and how?


Wouldn't the easiest way to accomplish this is create Category and Appender
for each new Thread as it enters the pool (where the name of the category
and appender are the unique thread identifier assigned in the constructor):


Thread t = new Thread(xxxSomeRunnablexxx,"xxxSome Thread Id");
Category cat = Category.getInstance("xxxSomeRunnablexxx");
FileAppender appender = new FileAppender(xxxSomeLayoutxxx,"xxxSome Thread
Id"+".log");
cat.addAppender(appender);
And then from within the thread:

Thread t = Thread.currentThread();
Category cat = t.getInstance(t.getName());
cat.ERROR(xxx); // or whatever

That would probably be the easiest way to do this without making changes to
Log4J.

-Chris
----- Original Message ----- 
From: "Ceki Gülcü" <[EMAIL PROTECTED]>
To: "LOG4J Users Mailing List" <[EMAIL PROTECTED]>
Sent: Thursday, June 14, 2001 3:56 PM
Subject: RE: Do I need to use multiple hierarchies, and how?



Jim,

No actually not. I had not fully read the question previously. I don't think
log4j is designed for thread based logging. This had never come up before.
Regards, Ceki 

At 17:54 14.06.2001 -0400, Jim Moore wrote:
>Is multiple hierarchies the most appropriate way of doing this?
>
>
>-----Original Message-----
>From: Ceki Gülcü [mailto:[EMAIL PROTECTED]]
>Sent: Thursday, June 14, 2001 4:56 PM
>To: LOG4J Users Mailing List
>Subject: Re: Do I need to use multiple hierarchies, and how?
>
>
>
>Jim,
>
>Look a the SocketServer for an example of using different hierarchies. You
>have understand the rest of log4j pretty well before you use multiple
>hierarchies. Otherwise, it's pretty straightforward. Regards, Ceki
>
>At 15:21 14.06.2001 -0400, you wrote:
>>> Hi,
>>> 
>>>   I apologize, in advance, if this question has already been answered.
>If
>>> it has, please point me to the existing reply.  I am using worker
threads
>>> in the same JVM and would like each thread to maintain its own log file.
>>> Other than that, I would like to use the same categories and layouts.
>>> Only the appenders would be thread specific.  Multiple hierarchies were
>>> mentioned in the introductory manual as an advanced topic.  Do multiple
>>> hierarchies make sense in this situation?  Is there an example of
>multiple
>>> hierarchy usage?  Is there more documentation on this topic?
>>> 
>>> Thank You,
>>> Jim Prokash
>
>--
>Ceki Gülcü

--
Ceki Gülcü

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to