You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@avalon.apache.org by Anton Tagunov <at...@mail.cnt.ru> on 2003/06/05 15:15:57 UTC

[PATCH] [LogKitLoggerManager] cleanup. ( prefix/getDefaultLogger issue, etc)

Hello, Developers!

Close examination of the prefix/getDefaultLogger() has brought
me to deep embarassment.

0.

Terminilogy: "internal" LogKit logger is the logger that the
LoggerManager implementation logs its own messages to, like

    added logger for category xxx

1.

Here's a excerpt from LoggerManager.java:

    /**
     * Return the default Logger.  This is basically the same
     * as getting the Logger for the "" category.
     */
    Logger getDefaultLogger();


So far, so good, All is Clear (OK, as Lincoln used to abbrevate it :-)

2.

Here are excerpts from LogKitLoggerManager.java

    /** The root logger to configure */
    private String m_prefix;

    /** The default logger used for this system */
    final private Logger m_defaultLogger;

    /** The hierarchy private to LogKitManager */
    private Hierarchy m_hierarchy;

    ...
    
    public final Logger getLoggerForCategory( final String categoryName )
    {
        final String fullCategoryName = getFullCategoryName( m_prefix, categoryName );
        ...
        return new LogKitLogger( m_hierarchy.getLoggerFor( fullCategoryName ) );
    }

So far, so good. We could expect that

    m_defaultLogger == getLoggerForCategory("") ==
    m_hierarchy.getLoggerFor( m_prefix )

But instead we surprisingly get
    
    public LogKitLoggerManager( final String prefix ... )
    {
        this( prefix, ...
              new LogKitLogger( hierarchy.getRootLogger() ) );
    }

???

In fact Fortress ContextManager does enforce the same contract
( getLoggerForCategory("") == getDefaultLogger ), but it has to
go into quirck and tricks to do this

lmDefaultLoggerName = "fortress"; {pseudocode :-) }

lmDefaultLogger = Hierarchy.getDefaultHierarchy().getLoggerFor( lmDefaultLoggerName );
logManager = new LogKitLoggerManager( lmDefaultLoggerName, Hierarchy.getDefaultHierarchy(),
        new LogKitLogger( lmDefaultLogger ),

3.

3.1

Let me as a trying ball propose a patch. This is a RADICAL patch.

  <second-pass>No longer a trial ball, but a full-blown patch,
  have changed my mind as I was writing :-)</second-pass>
  
It changes the contract of the 3 argument constructor

    LogKitLogger(String, Hierarchy, o.a.a.framework.logger.Logger )

The new contract is: never supply a default logger for LogKitLogger.
Why do it? The default logger will be well configured by the
configuration file.

Instead the supplied logger will be considered the "internal"
one for the LogKitLoggerManager.

So, the radicality of the change is the following:

- now the 3 arg constructor supplis both
  * default logger    - for getDefaultLogger() { return  m_defaultLogger ); }
  * "internal" logger - for m_logger.log( "created logger for category " + ... );
  
- after this PATCH the 3 arg constructor supplies
  * only the "internal" logger
  * the default logger is configured from configuration file

If we can afford this it's great, let's go for it.
Then we just deprecate the 4 arg constructor, as the patch proposes.

3.2

If we can't afford this and have to retain compatibility,
let's make "proper" way to create a LogKitLogger the 4 arg constructor
with the 3-rd arg set to null:

    new LogKitLooger( prefix, new Hierarchy, null, loggerManagerLogger);

and deprecate the 3-arg constructor.

3.3

There is one more issue that worries me deeply.
Currently Fortress ContextManager does the following:
first it creates a o.a.log.Logger for category
    "fortress.system.logkit"
without assigning any targets to it.

This results in the default LogKit's
    StreamTarget( System.out, .. )
target be assigned to it.

Fortress then supplies this logger as the "internal" logger
to LogKitLoggerManager. In the process of configure()
LogKitLoggerManager (hopefully) assigns some other
target(s) to this logger.

So, from the moment LogKitLoggerManager is created
and till the moment the "fortress.system.logkit"
logger get targets assigned to it it has a System.out
logger assigned to it.

Right?

But System.out is not always the best place to send
any messages. In particular I do suffer from this
in the servlet environment.

The most peculiar situtaion happens when the
configuration of LogKitLoggerManager from the config
file fails (error in config or file permissions problem, etc.)
-- I get a lot of messages to the console.

What do I propose?

* first, lets pass the normal logger to the LogKitLoggerManager
  as it is being created. Let this be the logger that
  DefaultContainerManager/ContextManager uses for itself.

  we won't have a fine control over debug/info level for
  LogKitLoggerManager then, but I think it's worth it.

* next, if we want to switch to logger extrected from
  the config file, plz see the new porposed 4-argument
  constructor

  LogKitLoggerManager( String prefix, Hierarchy h,
      o.a.a.framework.Logger innerLogger, String switchToLogger )

  at the end of configure() if switchToLogger has been supplied
  LogKitLoggerManager will try to get a logger of that
  category, make sure that it works for the first time
  emmiting "Logging started" (like Cocoon did in the old times :-)
  and "blows up" with a Configuration Exception if that fails.
  SHOULD I DO THIS???

Yes, and before doing any configuration, my patch cleares
any default (in fact the System.out) targets from the newly
created Hierarchy() and makes sure that something has
been configured instead.

One more funny issue: DefaultErrorHandler (that handles
logging errors) installed by Hierarchy into itself
used happily to log these errors to System.err :-)

This has also been fixed (these errors are logged to the
logger explicitly passed to LogKitManager _if_
logging to "fortress.system.logkit" fails,
see the patch :-)))

4.

If this patch makes through we will be able to simplify the
section in Fortress ContextManager that initializes the
LogKitLoggerManager. While testing this patch I use the
following

                final LoggerManager logManager = new LogKitLoggerManager(
                        lmDefaultLoggerName,
                        getLogger(), "system.logkit" );
                ContainerUtil.contextualize( logManager, m_rootContext );
                ContainerUtil.configure( logManager, loggerManagerConfig );

and it works fine for me :-)))
(note that I nad to switch from
   lmDefaultLoggerName + ".system.logkit" to
   "system.logkit"
this is one of the features of the patch, the switchToCategory
argument to the LogKitLoggerManager constructor is now relative to
prefix. (Nothing special, just commenting on the patch.)


BTW, an orthogonal issue, Fortress ContextManager should
also switch from
    Hierarchy.getDefaultHierarch() to
    new Hierarchy()
no matter if this patch is applied or not. Not doing this
will prevent two instances of Fortress from running in
a single classloader which might be desirable f.e. if we
have two servlets in one web app completely independent
but both using Fortress.

5.
Okay, most of this has been written before I started to create
the patch, now that the patch is created I have done a bit
more then what is there in the test. Plz just read the patch :-)

I needed two more classes:

AbstractLoggerManager
LoggerSwitch

both of which are currently in o.a.a.e.logger
package (the second one, LoggerSwitch might probably move
to smth like o.a.a.e.logger.util.

I have created AbstractLoggerManager because I beleive
a similar refactoring is pending for Log4JLoggerManager,
this class makes it simple :-))

Yep, looks like untill present it just ignored prefix,
but with this we can easily teach it to!

BTW, are we allowed to edit that file?
I heard it was passed for maintanance somewhere to Log4J team?

6.

There was also a small change that is easy to miss in the
large patch. I have added the boolean root attribute
to LogKitLoggerManager.setupLoggers(), you'll see how
I use it. When I did not have it I was always hitting
my own exception (a few lines bellow in the setupLoggers()).


Cheers, Anton

Glad I have finally done this :-))))

---------------

So, here goes

AbstractLoggerManager.java--------------------------------------------------------
/*

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Jakarta", "Avalon", "Excalibur" and "Apache Software Foundation"
    must not be used to endorse or promote products derived from this  software
    without  prior written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation. For more  information on the
 Apache Software Foundation, please see <http://www.apache.org/>.

*/
package org.apache.avalon.excalibur.logger;

import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.logger.LogEnabled;

/**
 *
 * This abstract class implements LogEnabled and accepts a Logger
 * in its constructor. This logger is considered to be the
 * "fallback" logger. It is possible to set it later by invoking
 * <code>enableLogging()</code>. Another "preferred" logger
 * is setup in the <code>switchOwnLoggin()</code> method by
 * retrieving it from ourselves (we're a LoggerManager after
 * all :-). Special measures have been taken to avoid infinite
 * recursion when reporting errors.
 *
 * @author <a href="mailto:giacomo@apache.org">Giacomo Pati</a>
 * @author <a href="mailto:bloritsch@apache.org">Berin Loritsch</a>
 * @author <a href="mailto:proyal@apache.org">Peter Royal</a>
 * @author <a href="mailto:tagunov@motor.ru">Anton Tagunov</a>
 * @version CVS $Revision: 1.16 $ $Date: 2003/05/27 07:40:10 $
 * @since 4.0
 */
public abstract class AbstractLoggerManager
    implements LogEnabled, LoggerManager
{
    /** The root logger to configure */
    protected String m_prefix;

    private LoggerSwitch m_switch;
    private Logger m_logger;
    private String m_switchTo;

    private boolean m_fallbackLoggerProvided;
    private boolean m_switchToInvoked = false;

    protected Logger getLogger()
    {
        return m_logger;
    }

    /**
     * Initialize AbstractLoggerManager.
     * @param prefix the prefix to prepended to the category
     *         on each invocation of getLoggerForCategory before
     *         passing the category name on to the underlying logging
     *         system (currently LogKit or Log4J).
     * @param fallbackLogger may be null, then it may be set later by 
     *         <code>enableLogging</code>.
     * @param switchTo fuel for the <code>switchOwnLogging()</code> method; if null
     *         <code>switchOwnLogging()</code> will do nothing; if empty string
     *         will switch to <code>getLoggerForCategory("")</code>.
     */
    public AbstractLoggerManager( final String prefix,
            final Logger fallbackLogger, final String switchTo )
    {
        m_prefix = prefix;

        // use the supplied logger as a fallback
        m_switch = new LoggerSwitch( fallbackLogger );
        m_fallbackLoggerProvided = fallbackLogger != null;
        m_logger = m_switch.get();
        m_switchTo = switchTo;
    }

    public void enableLogging( final Logger fallbackLogger )
    {
        if ( m_fallbackLoggerProvided )
        {
            throw new IllegalStateException( "fallback logger already provided" );
        }
        if ( fallbackLogger == null )
        {
            throw new NullPointerException( "fallbackLogger" );
        }
        m_switch.setFallback( fallbackLogger );
        m_fallbackLoggerProvided = true;
    }

    /**
     * Get a logger from ourselves and use it for our own messages.
     * Infinite recursion won't happen even if we try to log
     * messages about logging errors via <code>m_logger</code>:
     * it is an instance of <code>LoggerSwitch.SwitchingLogger</code>.
     * This class will detect recursive invocation and will
     * output errors via the fallback logger instead (or, if no
     * fallback logger has been provided the message will go into
     * the void. However, the original message will still be lost
     * and only the message about the logging error will get through
     * to the fallback logger.
     */
    protected void switchOwnLogging()
    {
        if ( m_switchToInvoked )
        {
            throw new IllegalStateException( "switchOwnLogging() already invoked or prohibited" );
        }

        if ( m_switchTo != null )
        {
            if ( m_logger.isDebugEnabled() )
            {
                final String message = "LogKitLoggerManager: switching logging to " + 
                        "this.getLoggerForCategory('" +
                        getFullCategoryName( m_prefix, m_switchTo) + "').";
                m_logger.debug( message );
            }

            final Logger ourOwn = this.getLoggerForCategory( m_switchTo );
        
            if ( ourOwn == null )
            {
                throw new NullPointerException( "ourOwn" );
            }
            
            m_switch.setPreferred( ourOwn );

            if ( m_logger.isDebugEnabled() )
            {
                final String message = "LogKitLoggerManager: have switched logging to " + 
                        "this.getLoggerForCategory('" +
                        getFullCategoryName( m_prefix, m_switchTo) + "').";
                m_logger.debug( message );
            }
        }
        else
        {
            if ( m_logger.isDebugEnabled() )
            {
                final String message = "LogKitLoggerManager: m_switchTo is null, " +
                        "no switch of my own logging.";
                m_logger.debug( message );
            }
        }
        m_switchToInvoked = true;
    }

    /**
     * This method is provided for security reasons to disallow any future
     * changes to our behaviour. This may be invoked instead of <code>switchTo</code>.
     */
    protected void disallowSwitchTo()
    {
        m_switchToInvoked = true;
    }

    /**
     * Generates a full category name given a prefix and category.  Either may be
     *  null.
     *
     * @param prefix Prefix or parent category.
     * @param category Child category name.
     */
    protected final String getFullCategoryName( String prefix, String category )
    {
        if( ( null == prefix ) || ( prefix.length() == 0 ) )
        {
            if( category == null )
            {
                return "";
            }
            else
            {
                return category;
            }
        }
        else
        {
            if( ( null == category ) || ( category.length() == 0 ) )
            {
                return prefix;
            }
            else
            {
                return prefix + org.apache.log.Logger.CATEGORY_SEPARATOR + category;
            }
        }
    }
}


Here goes LoggerSwitch.java------------------------------------------

/*

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Jakarta", "Avalon", "Excalibur" and "Apache Software Foundation"
    must not be used to endorse or promote products derived from this  software
    without  prior written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation. For more  information on the
 Apache Software Foundation, please see <http://www.apache.org/>.

*/
package org.apache.avalon.excalibur.logger;

import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.logger.NullLogger;

/**
 * A proxy logger that switches between two underlying loggers.
 *
 * @author <a href="mailto:tagunov@motor.ru">Anton Tagunov</a>
 */

public class LoggerSwitch
{
    private final static Logger SHARED_NULL = new NullLogger();

    private final static class BooleanThreadLocal extends ThreadLocal
    {
        public Object initialValue() { return Boolean.FALSE; }
        public boolean value() { return ((Boolean)this.get()).booleanValue(); }
    }

    private static class SwitchingLogger implements Logger
    {
        Logger m_fallback;
        Logger m_preferred;

        BooleanThreadLocal m_recursionOnPreferred = new BooleanThreadLocal();
        BooleanThreadLocal m_recursionOnFallback = new BooleanThreadLocal();

        SwitchingLogger( final Logger fallback, final Logger preferred )
        {
            m_fallback = fallback != null ? fallback : SHARED_NULL;
            m_preferred = preferred;
        }

        void setFallback( final Logger fallback )
        {
            synchronized( this )
            {
                m_fallback = fallback != null ? fallback : SHARED_NULL;
            }
        }

        void setPreferred( final Logger preferred )
        {
            synchronized( this )
            {
                m_preferred = preferred;
            }
        }

        /**
         * Retrieve m_preferred or if that is null m_fallback.
         * Safeguard against recursion. That is possible if
         * try to log something via a Logger that is failing
         * and trying to log its own error via itself.
         */
        private Logger getLogger()
        {
            final Logger fallback;
            final Logger preferred;

            synchronized( this )
            {
                fallback = m_fallback;
                preferred = m_preferred;
            }

            if ( m_recursionOnFallback.value() )
            {
                throw new IllegalStateException( "infinite recursion" );
            }
            else if ( m_recursionOnPreferred.value() || preferred == null )
            {
                m_recursionOnFallback.set( Boolean.TRUE );
                return fallback;
            }
            else
            {
                m_recursionOnPreferred.set( Boolean.TRUE );
                return m_preferred;
            }
        }

        private Logger getLoggerLight()
        {
            synchronized( this )
            {
                return m_preferred != null ? m_preferred : m_fallback;
            }
        }

        private void releaseLogger()
        {
            if ( m_recursionOnFallback.value() )
            {
                m_recursionOnFallback.set( Boolean.FALSE );
            }
            else if ( m_recursionOnPreferred.value() )
            {
                m_recursionOnPreferred.set( Boolean.FALSE );
            }
            else
            {
                throw new IllegalStateException( "no recursion" );
            }
        }

        public void debug( final String message )
        {
            final Logger logger = getLogger();
            logger.debug( message );
            releaseLogger();
        }
    
        public void debug( final String message, final Throwable throwable )
        {
            final Logger logger = getLogger();
            try
            {
                logger.debug( message, throwable );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        /** 
         * This and similar method may probably be optimized in the
         * future by caching the boolean in our instance variables.
         * Each time setPreferred() or setFallback() is called they
         * will be cached. Maybe in the future. :-)
         */
        public boolean isDebugEnabled()
        {
            final Logger logger = getLoggerLight();
            return logger.isDebugEnabled();
        }

    
        public void info( final String message )
        {
            final Logger logger = getLogger();
            try
            {
                logger.info( message );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public void info( final String message, final Throwable throwable )
        {
            final Logger logger = getLogger();
            try
            {
                logger.info( message, throwable );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public boolean isInfoEnabled()
        {
            final Logger logger = getLoggerLight();
            return logger.isInfoEnabled();
        }
    
        public void warn( final String message )
        {
            final Logger logger = getLogger();
            try
            {
                logger.warn( message );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public void warn( final String message, final Throwable throwable )
        {
            final Logger logger = getLogger();
            try
            {
                logger.warn( message, throwable );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public boolean isWarnEnabled()
        {
            final Logger logger = getLoggerLight();
            return logger.isWarnEnabled();
        }
    
        public void error( final String message )
        {
            final Logger logger = getLogger();
            try
            {
                logger.error( message );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public void error( final String message, final Throwable throwable )
        {
            final Logger logger = getLogger();
            try
            {
                logger.error( message, throwable );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public boolean isErrorEnabled()
        {
            final Logger logger = getLoggerLight();
            return logger.isErrorEnabled();
        }
    
        public void fatalError( final String message )
        {
            final Logger logger = getLogger();
            try
            {
                logger.fatalError( message );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public void fatalError( final String message, final Throwable throwable )
        {
            final Logger logger = getLogger();
            try
            {
                logger.fatalError( message, throwable );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public boolean isFatalErrorEnabled()
        {
            final Logger logger = getLoggerLight();
            return logger.isFatalErrorEnabled();
        }
    
        public Logger getChildLogger( final String name ) { return this; }

    }

    private SwitchingLogger m_switch;

    /**
     * We create a logger with no methods for changing
     * m_fallback and m_preferred for security reasons.
     * All the control is done by the parent class
     * that does not implement Logger itself.
     */
    public Logger get()
    {
        return m_switch;
    }

    public LoggerSwitch( final Logger fallback )
    {
        this( fallback, null );
    }

    public LoggerSwitch( final Logger fallback, final Logger preferred )
    {
        m_switch = new SwitchingLogger( fallback, preferred );
    }

    public void setFallback( final Logger fallback )
    {
        m_switch.setFallback( fallback );
    }

    public void setPreferred( final Logger preferred )
    {
        m_switch.setPreferred( preferred );
    }
}


Here goes a diff for LogKitLoggerManager.java

--- LogKitLoggerManager.orig    2003-05-28 10:23:28.000000000 +0400
+++ LogKitLoggerManager.java    2003-06-05 17:01:49.000000000 +0400
@@ -56,164 +56,264 @@
 import java.util.Set;
 import org.apache.avalon.framework.activity.Disposable;
 import org.apache.avalon.framework.configuration.Configurable;
 import org.apache.avalon.framework.configuration.Configuration;
 import org.apache.avalon.framework.configuration.ConfigurationException;
 import org.apache.avalon.framework.container.ContainerUtil;
 import org.apache.avalon.framework.context.Context;
 import org.apache.avalon.framework.context.ContextException;
 import org.apache.avalon.framework.context.Contextualizable;
 import org.apache.avalon.framework.logger.LogEnabled;
 import org.apache.avalon.framework.logger.LogKitLogger;
 import org.apache.avalon.framework.logger.Logger;
+import org.apache.avalon.framework.logger.NullLogger;
 import org.apache.log.Hierarchy;
+import org.apache.log.ErrorHandler;
 import org.apache.log.LogTarget;
 import org.apache.log.Priority;
+import org.apache.log.LogEvent;
 import org.apache.log.util.Closeable;
 
 /**
  * LogKitLoggerManager implementation.  It populates the LoggerManager
  * from a configuration file.
  *
  * @author <a href="mailto:giacomo@apache.org">Giacomo Pati</a>
  * @author <a href="mailto:bloritsch@apache.org">Berin Loritsch</a>
  * @author <a href="mailto:proyal@apache.org">Peter Royal</a>
  * @version CVS $Revision: 1.16 $ $Date: 2003/05/27 07:40:10 $
  * @since 4.0
  */
-public class LogKitLoggerManager
+public class LogKitLoggerManager extends AbstractLoggerManager
     implements LoggerManager, LogEnabled, Contextualizable, Configurable, Disposable
 {
     /** Map for name to logger mapping */
     final private Map m_loggers = new HashMap();
 
     /** Set of log targets */
     final private Set m_targets = new HashSet();
 
     /** The context object */
     private Context m_context;
 
     /** The hierarchy private to LogKitManager */
     private Hierarchy m_hierarchy;
 
-    /** The root logger to configure */
-    private String m_prefix;
+    /** The error handler we will supply to m_hierarchy.*/
+    private OurErrorHandler m_errorHandler;
 
     /** The default logger used for this system */
-    final private Logger m_defaultLogger;
-
-    /** The logger used to log output from the logger manager. */
-    private Logger m_logger;
+    private Logger m_defaultLogger;
 
     /**
-     * Creates a new <code>DefaultLogKitManager</code>. It will use a new <code>Hierarchy</code>.
+     * Creates a new <code>LogKitLoggerManager</code>. It will use a new <code>Hierarchy</code>.
+     * The messages generated by the LogKitLoggerManager itself won't
+     * be logged anywhere.
      */
     public LogKitLoggerManager()
     {
-        this( new Hierarchy() );
+        this( (String)null, (Logger)null, (String)null );
     }
 
     /**
-     * Creates a new <code>DefaultLogKitManager</code> with an existing <code>Hierarchy</code>.
+     * Creates a new <code>LogKitLoggerManager</code> with an existing 
+     * <code>Hierarchy</code>.
+     * If you create a Hierarchy on you own be sure to have clearned out 
+     * the default console log target with f.e.
+     * <code>hierarchy.getDefaultLogger().unsetLogTargets( true );</code>
+     * unless you really want this log target.
+     * Also note that we will replace the error handler on the supplied
+     * hierarchy, so <strong>please</strong> make sure that
+     * this hierarchy is private to this LogKitLoggerManager.
      */
     public LogKitLoggerManager( final Hierarchy hierarchy )
     {
-        this( null, hierarchy );
+        this( (String)null, hierarchy, (Logger)null, (String)null );
     }
 
     /**
-     * Creates a new <code>DefaultLogKitManager</code> using
-     * specified logger name as root logger.
+     * Creates a new <code>LogKitLoggerManager</code> intance.
+     * @param prefix to apply for all <code>getLoggerForCategory()</code> invocations.
      */
     public LogKitLoggerManager( final String prefix )
     {
-        this( prefix, new Hierarchy() );
+        this( prefix, (Logger)null, (String)null );
     }
 
     /**
-     * Creates a new <code>DefaultLogKitManager</code> with an existing <code>Hierarchy</code> using
-     * specified logger name as root logger.
+     * The preferred constructor to creates a <code>LogKitLoggerManager</code> 
+     * intance.
+     * @param prefix to apply for all <code>getLoggerForCategory()</code> invocations.
+     * @param fallbackLogger the logger to log messages generated by the
+     *         <code>LogKitLoggerManager</code> itself. After the end of
+     *         <code>configure()</code> if switchToCategory has been
+     *         specified will log all messages there. In case of recursion
+     *         will however report logging errors via fallbackLogger.
+     * @param switchToCategory if this parameter is not null after the
+     *         <code>LogKitLoggerManager</code> instance has been initialized
+     *         then it will acquire a Logger for this category
+     *         from itself and switch to logging its own messages to this.
+     */
+    public LogKitLoggerManager( final String prefix, final Logger fallbackLogger,
+                                final String switchToCategory )
+    {
+        this( prefix, new Hierarchy(), fallbackLogger, switchToCategory );
+        /**
+         * We have created it, and we shall clean out all the default
+         * console LogTarget. Let the users populate any logtargets
+         * they want.
+         */
+        m_hierarchy.getRootLogger().unsetLogTargets( true );
+    }
+
+    /**
+     * Creates a new <code>LogKitLoggerManager</code> with an existing <code>Hierarchy</code>
+     * using the specified prefix to prepend to all category names
+     * this LogKitLoggerManager is asked to create Loggers for.
+     * If you create a Hierarchy on you own be sure to have clearned out 
+     * the default console log target with f.e.
+     * <code>hierarchy.getDefaultLogger().unsetLogTargets( true );</code>
+     * unless you really want this log target.
+     * Also note that we will replace the error handler on the supplied
+     * hierarchy, so <strong>please</strong> make sure that
+     * this hierarchy is private to this LogKitLoggerManager.
      */
     public LogKitLoggerManager( final String prefix, final Hierarchy hierarchy )
     {
-        this( prefix, hierarchy,
-              new LogKitLogger( hierarchy.getRootLogger() ) );
+        this( prefix, hierarchy, (Logger)null, (String)null );
     }
 
     /**
-     * Creates a new <code>DefaultLogKitManager</code> with an existing <code>Hierarchy</code> using
-     * specified logger name as root logger.
+     * Creates a new <code>LogKitLoggerManager</code> with an existing <code>Hierarchy</code>
+     * using the specified prefix to prepend to all category names
+     * this LogKitLoggerManager is asked to create Loggers for.
+     * Also note that we will replace the error handler on the supplied
+     * hierarchy, so <strong>please</strong> make sure that
+     * this hierarchy is private to this LogKitLoggerManager.
+     * @param fallbackLogger the log messages generated by LogKitLoggerManager
+     *                       itself will be logged using this logger; note
+     *                       that this version of LogKitLoggerManager 
+     *                       <strong>will not</strong>
+     *                       use this logger as the default logger to be returned
+     *                       from <code>getDefaultLogger()</code> as early
+     *                       versions of LogKitLoggerManager did; may be null.
      */
     public LogKitLoggerManager( final String prefix, final Hierarchy hierarchy,
-                                final Logger defaultLogger )
+                                final Logger fallbackLogger )
     {
-        this( prefix, hierarchy, defaultLogger, defaultLogger );
+        this( prefix, hierarchy, fallbackLogger, (String)null );
     }
 
     /**
-     * Creates a new <code>DefaultLogKitManager</code> with an existing <code>Hierarchy</code> using
-     * specified logger name as root logger.
+     * Creates a new <code>LogKitLoggerManager</code> with an existing <code>Hierarchy</code>
+     * using the specified prefix to prepend to all category names
+     * this LogKitLoggerManager is asked to create Loggers for.
+     * Also note that we will replace the error handler on the supplied
+     * hierarchy, so <strong>please</strong> make sure that
+     * this hierarchy is private to this LogKitLoggerManager.
+     * @param defaultLogger this logger will be returned for getDefaultLogger().
+     * @param fallbackLogger the log messages generated by LogKitLoggerManager itself
+     *                       will be logged to this logger
+     *
+     * @deprecated There is really no reason to specify the defaultLogger explicitly.
+     *             The default logger should be configured from the configuration
+     *             instead. Among other things this breaks the contract of
+     *             <code>getDefaultLogger() == getLoggerForCategory( "" )</code>.
+     *             Use other constructors instead.
      */
     public LogKitLoggerManager( final String prefix, final Hierarchy hierarchy,
-                                final Logger defaultLogger, final Logger logger )
+                                final Logger defaultLogger, final Logger fallbackLogger )
     {
-        m_prefix = prefix;
-        m_hierarchy = hierarchy;
+        this( prefix, hierarchy, fallbackLogger, (String)null );
         m_defaultLogger = defaultLogger;
-        m_logger = logger;
     }
 
     /**
-     * Provide a logger.
+     * Creates a new <code>LogKitLoggerManager</code> with an existing <code>Hierarchy</code>
+     * using the specified prefix to prepend to all category names. This is the deepest
+     * constructor invoked by all other constructors.
+     * Note that we replace the error handler on the hierarchy here.
+     * A bad thing, especially if it was not ours hierarchy.
+     * Conclusion: use only a private hierarchy per LogKitLoggerManager.
+     * In particular do not use Hierarchy.getDefaultHierarchy() to power
+     * LogKitLoggerManager.
+     *
+     * @param fallbackLogger the log messages generated by LogKitLoggerManager itself
+     *                       will be logged to this logger. Please <strong>never</strong>
+     *                       supply a logger obtained from the same <code>Hierarchy</code>
+     *                       as used by this <code>LogKitLoggerManager</code> to
+     *                       avoid ininite looping on critical logging errors.
+     *                       It would be a good idea to supply some reliable
+     *                       logger, a ConsoleLogger for instance as this
+     *                       parameter. Please use the switchToCategory
+     *                       parameter if you want messages other then critical
+     *                       error messages to be logged to a logger obtained from
+     *                       this LoggerManager.
+     * @param switchToCategory after the <code>configuration()</code> is complete
+     *                       switch logging of all messages except critical errors
+     *                       from <code>fallbackLogger</code> to a logger obtained
+     *                       from <code>this.getLoggerForCategory( switchToCategory )</code>.
      *
-     * @param logger the logger
-     **/
-    public void enableLogging( final Logger logger )
+     */
+    public LogKitLoggerManager( final String prefix, final Hierarchy hierarchy,
+                                final Logger fallbackLogger, final String switchToCategory )
     {
-        m_logger = logger;
+        super( prefix, fallbackLogger, switchToCategory );
+
+        m_hierarchy = hierarchy;
+        /* we actaully need m_defaultLogger only for the deprecated constructor.*/
+        m_defaultLogger = null;
+        /* let's do a nasty thing.. */
+        m_errorHandler = new OurErrorHandler( getLogger() );
+        m_hierarchy.setErrorHandler( m_errorHandler );
     }
 
     /**
      * Retrieves a Logger from a category name. Usually
      * the category name refers to a configuration attribute name.  If
      * this LogKitManager does not have the match the default Logger will
      * be returned and a warning is issued.
      *
      * @param categoryName  The category name of a configured Logger.
      * @return the Logger.
      */
     public final Logger getLoggerForCategory( final String categoryName )
     {
         final String fullCategoryName = getFullCategoryName( m_prefix, categoryName );
 
         final Logger logger = (Logger)m_loggers.get( fullCategoryName );
 
         if( null != logger )
         {
-            if( m_logger.isDebugEnabled() )
+            if( getLogger().isDebugEnabled() )
             {
-                m_logger.debug( "Logger for category " + fullCategoryName + " returned" );
+                getLogger().debug( "Logger for category " + fullCategoryName + " returned" );
             }
             return logger;
         }
 
-        if( m_logger.isDebugEnabled() )
+        if( getLogger().isDebugEnabled() )
         {
-            m_logger.debug( "Logger for category " + fullCategoryName + " not defined in "
+            getLogger().debug( "Logger for category " + fullCategoryName + " not defined in "
                             + "configuration. New Logger created and returned" );
         }
 
         return new LogKitLogger( m_hierarchy.getLoggerFor( fullCategoryName ) );
     }
 
+    /**
+     * Retruns the logger for category <code>""</code>.
+     * Shouldn't be invoked before <code>configure()</code>,
+     * otherwise may return <code>null</code>.
+     */
     public final Logger getDefaultLogger()
     {
         return m_defaultLogger;
     }
 
     /**
      * Reads a context object that will be supplied to the log target factory manager.
      *
      * @param context The context object.
      * @throws ContextException if the context is malformed
      */
     public final void contextualize( final Context context )
@@ -233,39 +333,42 @@
     {
         final Configuration factories = configuration.getChild( "factories" );
         final LogTargetFactoryManager targetFactoryManager = setupTargetFactoryManager( factories );
 
         final Configuration targets = configuration.getChild( "targets" );
         final LogTargetManager targetManager = setupTargetManager( targets, targetFactoryManager );
 
         final Configuration categories = configuration.getChild( "categories" );
         final Configuration[] category = categories.getChildren( "category" );
         setupLoggers( targetManager,
                       m_prefix,
                       category,
+                      true,
                       categories.getAttributeAsBoolean( "additive", false ) );
+
+        postConfigure();
     }
 
     /**
      * Setup a LogTargetFactoryManager
      *
      * @param configuration  The configuration object.
      * @throws ConfigurationException if the configuration is malformed
      */
     private final LogTargetFactoryManager setupTargetFactoryManager( final Configuration configuration )
         throws ConfigurationException
     {
         final DefaultLogTargetFactoryManager targetFactoryManager = new DefaultLogTargetFactoryManager();
 
-        ContainerUtil.enableLogging( targetFactoryManager, m_logger );
+        ContainerUtil.enableLogging( targetFactoryManager, getLogger() );
 
         try
         {
             ContainerUtil.contextualize( targetFactoryManager, m_context );
         }
         catch( final ContextException ce )
         {
             throw new ConfigurationException( "cannot contextualize default factory manager", ce );
         }
 
         ContainerUtil.configure( targetFactoryManager, configuration );
 
@@ -275,128 +378,203 @@
     /**
      * Setup a LogTargetManager
      *
      * @param configuration  The configuration object.
      * @throws ConfigurationException if the configuration is malformed
      */
     private final LogTargetManager setupTargetManager( final Configuration configuration,
                                                        final LogTargetFactoryManager targetFactoryManager )
         throws ConfigurationException
     {
         final DefaultLogTargetManager targetManager = new DefaultLogTargetManager();
 
-        ContainerUtil.enableLogging( targetManager, m_logger );
+        ContainerUtil.enableLogging( targetManager, getLogger() );
 
         if( targetManager instanceof LogTargetFactoryManageable )
         {
             targetManager.setLogTargetFactoryManager( targetFactoryManager );
         }
 
         ContainerUtil.configure( targetManager, configuration );
 
         return targetManager;
     }
 
     /**
-     * Generates a full category name given a prefix and category.  Either may be
-     *  null.
-     *
-     * @param prefix Prefix or parent category.
-     * @param category Child category name.
-     */
-    private final String getFullCategoryName( String prefix, String category )
-    {
-        if( ( null == prefix ) || ( prefix.length() == 0 ) )
-        {
-            if( category == null )
-            {
-                return "";
-            }
-            else
-            {
-                return category;
-            }
-        }
-        else
-        {
-            if( ( null == category ) || ( category.length() == 0 ) )
-            {
-                return prefix;
-            }
-            else
-            {
-                return prefix + org.apache.log.Logger.CATEGORY_SEPARATOR + category;
-            }
-        }
-    }
-
-    /**
      * Setup Loggers
      *
      * @param categories []  The array object of configurations for categories.
+     * @param root shows if we're processing the root of the configuration
      * @throws ConfigurationException if the configuration is malformed
      */
     private final void setupLoggers( final LogTargetManager targetManager,
                                      final String parentCategory,
                                      final Configuration[] categories,
+                                     final boolean root,
                                      final boolean defaultAdditive )
         throws ConfigurationException
     {
+        boolean rootLoggerAlive = false;
+
         for( int i = 0; i < categories.length; i++ )
         {
             final String category = categories[ i ].getAttribute( "name" );
             final String loglevel = categories[ i ].getAttribute( "log-level" ).toUpperCase();
             final boolean additive = categories[ i ].
                 getAttributeAsBoolean( "additive", defaultAdditive );
 
             final Configuration[] targets = categories[ i ].getChildren( "log-target" );
             final LogTarget[] logTargets = new LogTarget[ targets.length ];
             for( int j = 0; j < targets.length; j++ )
             {
                 final String id = targets[ j ].getAttribute( "id-ref" );
                 logTargets[ j ] = targetManager.getLogTarget( id );
                 if( !m_targets.contains( logTargets[ j ] ) )
                 {
                     m_targets.add( logTargets[ j ] );
                 }
             }
 
-            if( "".equals( category ) && logTargets.length > 0 )
+            if( root && "".equals( category ) && logTargets.length > 0 )
             {
                 m_hierarchy.setDefaultPriority( Priority.getPriorityForName( loglevel ) );
                 m_hierarchy.setDefaultLogTargets( logTargets );
+                rootLoggerAlive = true;
             }
 
             final String fullCategory = getFullCategoryName( parentCategory, category );
 
             final org.apache.log.Logger logger = m_hierarchy.getLoggerFor( fullCategory );
             m_loggers.put( fullCategory, new LogKitLogger( logger ) );
-            if( m_logger.isDebugEnabled() )
+            if( getLogger().isDebugEnabled() )
             {
-                m_logger.debug( "added logger for category " + fullCategory );
+                getLogger().debug( "added logger for category " + fullCategory );
             }
             logger.setPriority( Priority.getPriorityForName( loglevel ) );
             logger.setLogTargets( logTargets );
             logger.setAdditivity( additive );
 
             final Configuration[] subCategories = categories[ i ].getChildren( "category" );
             if( null != subCategories )
             {
-                setupLoggers( targetManager, fullCategory, subCategories, defaultAdditive );
+                setupLoggers( targetManager, fullCategory, subCategories, false, defaultAdditive );
             }
         }
+
+        if ( root && !rootLoggerAlive )
+        {
+            final String message = "No log targets configured for the root logger.";
+
+            throw new ConfigurationException( message );
+        }
+    }
+
+    private void postConfigure() throws ConfigurationException
+    {
+        /**
+         * We'll check if m_defaultLogger has already been set
+         * by the deprecated constructor. If it has we do not
+         * have to do any checkings.
+         */
+        if ( m_defaultLogger == null )
+        {
+            // get "" if m_prefix == null
+            final String normalizedPrefix = getFullCategoryName( m_prefix, null );
+            // see what LogKit logger backs will back our default logger
+            org.apache.log.Logger defaultLogger = m_hierarchy.getLoggerFor( normalizedPrefix );
+            /** save our default logger for future use */
+            m_defaultLogger = getLoggerForCategory( "" );
+            /** 
+             * Make sure we're alive or blow up, won't work if
+             * priority is bellow INFO. A pity. :-(
+             */
+            m_defaultLogger.info( "Logging started" );
+            if ( m_errorHandler.getWasError() )
+            {
+                throw new ConfigurationException( "Our default logger is not alive" );
+            }
+        }
+        /**
+         * If the user has requested to switch our own logging to a logger
+         * configured via the configuration do it.
+         */
+        super.switchOwnLogging();
     }
 
     public void dispose()
     {
         final Iterator iterator = m_targets.iterator();
         while( iterator.hasNext() )
         {
             final LogTarget target = (LogTarget)iterator.next();
             if( target instanceof Closeable )
             {
                 ( (Closeable)target ).close();
             }
 
         }
     }
+
+    private static class OurErrorHandler implements ErrorHandler
+    {
+        /** 
+         * This will be initialized to an instance of LoggerSwitch.SwitchingLogger;
+         * that is really reliable.
+         */
+        private Logger m_reliableLogger;
+        private boolean m_wasError = false;
+        /**
+         * Access to this variable is not synchronized. Justification: it is only
+         * intended to be accessed from postConfigure when only one thread
+         * is assumed to be using the whole LogKitLoggerManager.
+         */
+        public boolean getWasError() { return m_wasError; }
+
+        OurErrorHandler( final Logger reliableLogger )
+        {
+           if ( reliableLogger == null )
+           {
+               throw new NullPointerException( "reliableLogger" );
+           }
+           m_reliableLogger = reliableLogger;
+        }
+
+        public void error( final String message, final Throwable throwable, final LogEvent event )
+        {
+            // let them know if they are interested
+            m_wasError = true;
+            // let them know we're not OK
+            m_reliableLogger.fatalError( message, throwable );
+
+            // transmit the original error
+            final Priority p = event.getPriority();
+            final String nestedMessage = "nested log event: " + event.getMessage();
+
+            if ( p == Priority.DEBUG )
+            {
+                m_reliableLogger.debug( nestedMessage, event.getThrowable() );
+            }
+            else if ( p == Priority.INFO )
+            {
+                m_reliableLogger.info( nestedMessage, event.getThrowable() );
+            }
+            else if ( p == Priority.WARN )
+            {
+                m_reliableLogger.warn( nestedMessage, event.getThrowable() );
+            }
+            else if ( p == Priority.ERROR )
+            {
+                m_reliableLogger.error( nestedMessage, event.getThrowable() );
+            }
+            else if ( p == Priority.FATAL_ERROR)
+            {
+                m_reliableLogger.fatalError( nestedMessage, event.getThrowable() );
+            }
+            else
+            {
+                /** This just plainly can't happen :-)*/
+                m_reliableLogger.error( "unrecognized priority " + nestedMessage, 
+                    event.getThrowable() );
+            }
+        }
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@avalon.apache.org
For additional commands, e-mail: dev-help@avalon.apache.org


Re: [PATCH] [LogKitLoggerManager] cleanup. ( prefix/getDefaultLogger issue, etc)

Posted by Anton Tagunov <at...@mail.cnt.ru>.
1.

While proofreading my own code I found that a few lines were missing
from LoggerSwitch.java, if you're qurious it's

<             logger.debug( message );
---
>             try
>             {
>                 logger.debug( message );
>             }
>             finally
>             {
>                 releaseLogger();
>             }

So, at the bottom of this message goes a fixed version of
LoggerSwitch.java. 

2.

Yes, I have also found some flaws in my changes to the inline docs on
LogKitLoggerManager, but I hope I will patch that later if/when the
overall patch is approved :-)

WBR, Anton

-------LoggerSwitch.java

/*

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Jakarta", "Avalon", "Excalibur" and "Apache Software Foundation"
    must not be used to endorse or promote products derived from this  software
    without  prior written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation. For more  information on the
 Apache Software Foundation, please see <http://www.apache.org/>.

*/
package org.apache.avalon.excalibur.logger;

import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.logger.NullLogger;

/**
 * A proxy logger that switches between two underlying loggers.
 *
 * @author <a href="mailto:tagunov@motor.ru">Anton Tagunov</a>
 */

public class LoggerSwitch
{
    private final static Logger SHARED_NULL = new NullLogger();

    private final static class BooleanThreadLocal extends ThreadLocal
    {
        public Object initialValue() { return Boolean.FALSE; }
        public boolean value() { return ((Boolean)this.get()).booleanValue(); }
    }

    private static class SwitchingLogger implements Logger
    {
        Logger m_fallback;
        Logger m_preferred;

        BooleanThreadLocal m_recursionOnPreferred = new BooleanThreadLocal();
        BooleanThreadLocal m_recursionOnFallback = new BooleanThreadLocal();

        SwitchingLogger( final Logger fallback, final Logger preferred )
        {
            m_fallback = fallback != null ? fallback : SHARED_NULL;
            m_preferred = preferred;
        }

        void setFallback( final Logger fallback )
        {
            synchronized( this )
            {
                m_fallback = fallback != null ? fallback : SHARED_NULL;
            }
        }

        void setPreferred( final Logger preferred )
        {
            synchronized( this )
            {
                m_preferred = preferred;
            }
        }

        /**
         * Retrieve m_preferred or if that is null m_fallback.
         * Safeguard against recursion. That is possible if
         * try to log something via a Logger that is failing
         * and trying to log its own error via itself.
         */
        private Logger getLogger()
        {
            final Logger fallback;
            final Logger preferred;

            synchronized( this )
            {
                fallback = m_fallback;
                preferred = m_preferred;
            }

            if ( m_recursionOnFallback.value() )
            {
                throw new IllegalStateException( "infinite recursion" );
            }
            else if ( m_recursionOnPreferred.value() || preferred == null )
            {
                m_recursionOnFallback.set( Boolean.TRUE );
                return fallback;
            }
            else
            {
                m_recursionOnPreferred.set( Boolean.TRUE );
                return m_preferred;
            }
        }

        private Logger getLoggerLight()
        {
            synchronized( this )
            {
                return m_preferred != null ? m_preferred : m_fallback;
            }
        }

        private void releaseLogger()
        {
            if ( m_recursionOnFallback.value() )
            {
                m_recursionOnFallback.set( Boolean.FALSE );
            }
            else if ( m_recursionOnPreferred.value() )
            {
                m_recursionOnPreferred.set( Boolean.FALSE );
            }
            else
            {
                throw new IllegalStateException( "no recursion" );
            }
        }

        public void debug( final String message )
        {
            final Logger logger = getLogger();
            try
            {
                logger.debug( message );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public void debug( final String message, final Throwable throwable )
        {
            final Logger logger = getLogger();
            try
            {
                logger.debug( message, throwable );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        /** 
         * This and similar method may probably be optimized in the
         * future by caching the boolean in our instance variables.
         * Each time setPreferred() or setFallback() is called they
         * will be cached. Maybe in the future. :-)
         */
        public boolean isDebugEnabled()
        {
            final Logger logger = getLoggerLight();
            return logger.isDebugEnabled();
        }

    
        public void info( final String message )
        {
            final Logger logger = getLogger();
            try
            {
                logger.info( message );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public void info( final String message, final Throwable throwable )
        {
            final Logger logger = getLogger();
            try
            {
                logger.info( message, throwable );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public boolean isInfoEnabled()
        {
            final Logger logger = getLoggerLight();
            return logger.isInfoEnabled();
        }
    
        public void warn( final String message )
        {
            final Logger logger = getLogger();
            try
            {
                logger.warn( message );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public void warn( final String message, final Throwable throwable )
        {
            final Logger logger = getLogger();
            try
            {
                logger.warn( message, throwable );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public boolean isWarnEnabled()
        {
            final Logger logger = getLoggerLight();
            return logger.isWarnEnabled();
        }
    
        public void error( final String message )
        {
            final Logger logger = getLogger();
            try
            {
                logger.error( message );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public void error( final String message, final Throwable throwable )
        {
            final Logger logger = getLogger();
            try
            {
                logger.error( message, throwable );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public boolean isErrorEnabled()
        {
            final Logger logger = getLoggerLight();
            return logger.isErrorEnabled();
        }
    
        public void fatalError( final String message )
        {
            final Logger logger = getLogger();
            try
            {
                logger.fatalError( message );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public void fatalError( final String message, final Throwable throwable )
        {
            final Logger logger = getLogger();
            try
            {
                logger.fatalError( message, throwable );
            }
            finally
            {
                releaseLogger();
            }
        }
    
        public boolean isFatalErrorEnabled()
        {
            final Logger logger = getLoggerLight();
            return logger.isFatalErrorEnabled();
        }
    
        public Logger getChildLogger( final String name ) { return this; }

    }

    private SwitchingLogger m_switch;

    /**
     * We create a logger with no methods for changing
     * m_fallback and m_preferred for security reasons.
     * All the control is done by the parent class
     * that does not implement Logger itself.
     */
    public Logger get()
    {
        return m_switch;
    }

    public LoggerSwitch( final Logger fallback )
    {
        this( fallback, null );
    }

    public LoggerSwitch( final Logger fallback, final Logger preferred )
    {
        m_switch = new SwitchingLogger( fallback, preferred );
    }

    public void setFallback( final Logger fallback )
    {
        m_switch.setFallback( fallback );
    }

    public void setPreferred( final Logger preferred )
    {
        m_switch.setPreferred( preferred );
    }
}


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@avalon.apache.org
For additional commands, e-mail: dev-help@avalon.apache.org