org.apache.avalon.excalibur.logger.LogKitLoggerManager Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.avalon.excalibur.logger;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.avalon.excalibur.logger.util.LoggerUtil;
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.LogKitLogger;
import org.apache.avalon.framework.logger.Logger;
import org.apache.log.ErrorHandler;
import org.apache.log.Hierarchy;
import org.apache.log.LogEvent;
import org.apache.log.LogTarget;
import org.apache.log.Priority;
import org.apache.log.util.Closeable;
/**
* LogKitLoggerManager implementation. It populates the LoggerManager
* from a configuration file.
*
* @author Avalon Development Team
* @version SVN $Id$
* @since 4.0
*/
public class LogKitLoggerManager extends AbstractLoggerManager
implements LoggerManager, Contextualizable, Configurable, Disposable
{
/** Set of log targets */
final private Set m_targets = new HashSet();
/** The context object */
private Context m_context;
/** The hierarchy private to LogKitManager */
private final Hierarchy m_hierarchy;
/**
* Creates a new LogKitLoggerManager
;
* one of the preferred constructors.
* Please also invoke enableLogging()
* to supply a fallback logger.
*/
public LogKitLoggerManager()
{
this( (String)null, (Hierarchy)null, (String)null, (Logger)null, (Logger)null );
}
/**
* Creates a new LogKitLoggerManager
;
* one of the preferred constructors.
* Please also invoke enableLogging()
* to supply a fallback logger.
*
* @param prefix to prepended to category name on each
* invocation of getLoggerForCategory()
.
*/
public LogKitLoggerManager( final String prefix )
{
this( prefix, (Hierarchy)null, (String)null, (Logger)null, (Logger)null );
}
/**
* Creates a new LogKitLoggerManager
;
* one of the preferred constructors,
* intended for the widest usage.
*
* Please also invoke enableLogging()
* to supply a fallback logger.
*
* Example:
*
* LogKitLoggerManager l = new LogKitLoggerManager( "fortress", "system.logkit" );
* l.enableLogging( bootstrapLogger );
* l.configure( loggerManagerConfiguration );
*
*
* @param prefix to prepended to category name on each
* invocation of getLoggerForCategory()
.
* @param switchToCategory if this parameter is not null
* after start()
* LogKitLoggerManager
will start
* to log its own debug and error messages to
* a logger obtained via
* this.getLoggerForCategory( switchToCategory )
.
* Note that prefix will be prepended to
* the value of switchToCategory
also.
*/
public LogKitLoggerManager( final String prefix, final String switchToCategory )
{
this( prefix, (Hierarchy)null, switchToCategory, (Logger)null, (Logger)null );
}
/**
* Creates a new LogKitLoggerManager
* with an existing Hierarchy
;
* use with caution.
*
* Please also invoke enableLogging()
* to supply a fallback logger.
* See comments on the root constructor
* for details on why constructors supplying an existing hierarchy
* should be used with caution.
*
* @param prefix to prepended to category name on each
* invocation of getLoggerForCategory()
.
* @param switchToCategory if this parameter is not null
* after start()
* LogKitLoggerManager
will start
* to log its own debug and error messages to
* a logger obtained via
* this.getLoggerForCategory( switchToCategory )
.
* Note that prefix will be prepended to
* the value of switchToCategory
also.
*/
public LogKitLoggerManager( final String prefix, final Hierarchy hierarchy,
final String switchToCategory )
{
this( prefix, hierarchy, switchToCategory, (Logger)null, (Logger)null );
}
/**
* Creates a new LogKitLoggerManager
* with an existing Hierarchy
;
* use with caution.
*
* Please also invoke enableLogging()
* to supply a fallback logger.
* See comments on the root constructor
* for details on why constructors supplying an existing hierarchy
* should be used with caution.
*/
public LogKitLoggerManager( final Hierarchy hierarchy )
{
this( (String)null, hierarchy, (String)null, (Logger)null, (Logger)null );
}
/**
* Creates a new LogKitLoggerManager
* with an existing Hierarchy
;
* use with caution.
*
* Please also invoke enableLogging()
* to supply a fallback logger.
* See comments on the root constructor
* for details on why constructors supplying an existing hierarchy
* should be used with caution.
*
* @param prefix to prepended to category name on each
* invocation of getLoggerForCategory()
.
*/
public LogKitLoggerManager( final String prefix, final Hierarchy hierarchy )
{
this( prefix, hierarchy, (String)null, (Logger)null, (Logger)null );
}
/**
* Creates a new LogKitLoggerManager
* with an existing Hierarchy
using
* specified logger name as a fallback logger and to
* forcibly override the root logger;
* compatibility constructor.
*
* The configuration for the ""
* category in the configuration will supply the defaults for
* all other categories, but getDefaultLogger()
* and getLoggerForCategory()
will still
* use logger supplied by this constructor.
*
*
* See comments on the root constructor
* for details on why constructors supplying an existing hierarchy
* should be used with caution.
*
*
* As this constructor provides a logger to be used as a fallback
* a subsequent enableLogging()
stage is unnecessary.
* Moreover, it will fail.
*
* @param prefix to prepended to category name on each
* invocation of getLoggerForCategory()
.
* @param defaultOverrideAndFallback the logger used to
* a) forcibly
* override the root logger that will further be obtained from
* the configuration and b) as the fallback logger. Note that
* specifying a logger as this parameter crucially differs from
* supplying it via enableLogging()
. The logger
* supplied via enableLogging
will only be used
* as the fallback logger and to log messages during initialization
* while this constructor argument also has the described
* override semantics.
*/
public LogKitLoggerManager( final String prefix, final Hierarchy hierarchy,
final Logger defaultOverrideAndFallback )
{
this( prefix, hierarchy, (String)null,
defaultOverrideAndFallback, defaultOverrideAndFallback );
}
/**
* Creates a new LogKitLoggerManager
* with an existing Hierarchy
using
* specified loggers to forcibly override
* the default logger and to provide a fallback logger;
* compatibility constructor.
*
* See comments on the root constructor
* for details on why constructors supplying an existing hierarchy
* should be used with caution.
*
* As this constructor provides a logger to be used as a fallback
* a subsequent enableLogging()
stage is unnecessary.
* Moreover, it will fail.
*
* @param prefix to prepended to category name on each
* invocation of getLoggerForCategory()
.
* @param defaultLoggerOverride the logger to be used to forcibly
* override the root logger that will further be obtained from
* the configuration
* @param fallbackLogger the logger to as a fallback logger
* (passing non-null as this argument eliminates the need
* to invoke enableLogging()
)
*/
public LogKitLoggerManager( final String prefix, final Hierarchy hierarchy,
final Logger defaultLoggerOverride,
final Logger fallbackLogger )
{
this( prefix, hierarchy, (String)null, defaultLoggerOverride, fallbackLogger );
}
/**
*
*
* Creates a new LogKitLoggerManager
;
* "root" constructor invoked by all other constructors.
*
*
* If the hierarchy
parameter is not null
* this instructs this constructor to use an existing hierarchy
* instead of creating a new one. This also disables removing
* the default log target configured by the Hierarchy()
* constructor (this target logs to System.out
) and
* installing our own ErrorHandler
for our
* Hierarchy
(the default ErrorHandler
* writes to System.err
).
*
* The configuration of the resulting Hierarchy
* is a combination of the original configuraiton that
* existed when this Hierarchy
was handled to us
* and the configuration supplied to use via configure()
.
* LogTarget
s for those categories for which a
* configuration node has been supplied in the configuration
* supplied via configure()
are replaced during
* the configure()
process. LogTargets
* for those categories for which configuration nodes have
* not been supplied are left as they were. A special case
* is when a node in configuration for a category exists but
* it does not enlist log targets. In this case the original
* targets if any are left as they were.
*
*
* Generally it is preferrable to
*
* - have the
Hierarchy
be configured
* from top to bottom via the configuration
* - have our custom
ErrorHandler
reporting
* errors via the fallback logger (supplied either as
* the fallbackLogger
to this constructor or
* via the enableLogging()
method) installed
*
* That's why it is preferrable to pass null
* for the hierarchy
parameter of this constructor
* or, which is easier to read but has the same effect, to
* invoke a constructor which does not accept a Hierarchy
* argument.
*
*
* The defaultLoggerOverride
and fallbackLogger
* are a special case too. defaultLoggerOverride
* forcibly overrides
* the root logger configured via configure()
.
* As there is little reason to take away users's freedom to configure
* whatever he likes as the default logger, it is preferrable to pass
* null
for this parameter or better still to invoke
* a constructor without Logger
parameters at all.
*
* There is nothing wrong with passing fallbackLogger
* via this constructor, but as this constructor is not convinient to
* be invoked (too many arguments, some of them likely to be null) and the
* {@link #LogKitLoggerManager(String,Hierarchy,Logger)}
* constructor is broken
* in using its Logger
argument both as
* fallbackLogger
(which is okay) and as
* a defaultLoggerOverride
(which is probably not
* desired for the reasons given above) it is preferrable not to
* specify a logger
* as a constructor argument but rather supply it via
* enableLogging()
call, like this happens with all
* other normal Avalon components after all.
*
* @param prefix to prepended to category name on each
* invocation of getLoggerForCategory()
.
* @param switchToCategory if this parameter is not null
* after start()
* LogKitLoggerManager
will start
* to log its own debug and error messages to
* a logger obtained via
* this.getLoggerForCategory( switchToCategory )
.
* Note that prefix will be prepended to
* the value of switchToCategory
also.
* @param defaultLoggerOverride the logger to be used to
* forcibly override
* the root logger that would further be obtained from
* the configuration
* @param fallbackLogger the logger to as a fallback logger
* (passing non-null as this argument eliminates the need
* to invoke enableLogging()
)
*/
public LogKitLoggerManager( final String prefix, final Hierarchy hierarchy,
final String switchToCategory,
final Logger defaultLoggerOverride,
final Logger fallbackLogger )
{
super( prefix, switchToCategory, defaultLoggerOverride );
m_prefix = prefix;
if ( hierarchy == null )
{
m_hierarchy = new Hierarchy();
m_hierarchy.getRootLogger().unsetLogTargets( true );
final ErrorHandler errorHandler = new OurErrorHandler( getLogger() );
m_hierarchy.setErrorHandler( errorHandler );
}
else
{
m_hierarchy = hierarchy;
}
if ( fallbackLogger != null )
{
this.enableLogging( fallbackLogger );
}
}
/**
* 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 )
throws ContextException
{
m_context = context;
}
/**
* Actually create a logger for the given category.
* The result will be cached by
* AbstractLoggerManager.getLoggerForCategory()
.
*/
protected Logger doGetLoggerForCategory( final String fullCategoryName )
{
return new LogKitLogger( m_hierarchy.getLoggerFor( fullCategoryName ) );
}
/**
* Reads a configuration object and creates the category mapping.
*
* @param configuration The configuration object.
* @throws ConfigurationException if the configuration is malformed
*/
public void configure( final Configuration configuration )
throws ConfigurationException
{
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 ) );
}
/**
* 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, getLogger() );
try
{
ContainerUtil.contextualize( targetFactoryManager, m_context );
}
catch( final ContextException ce )
{
throw new ConfigurationException( "cannot contextualize default factory manager", ce );
}
ContainerUtil.configure( targetFactoryManager, configuration );
return targetFactoryManager;
}
/**
* 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, getLogger() );
targetManager.setLogTargetFactoryManager( targetFactoryManager );
ContainerUtil.configure( targetManager, configuration );
return targetManager;
}
/**
* 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,
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 fullCategory =
LoggerUtil.getFullCategoryName( parentCategory, category );
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 (logTargets[ j ] == null)
{
throw new ConfigurationException("Category <" + fullCategory + ">: " +
"Target <" + id + "> is not defined.");
}
if( !m_targets.contains( logTargets[ j ] ) )
{
m_targets.add( logTargets[ j ] );
}
}
if( root && "".equals( category ) && logTargets.length > 0 )
{
m_hierarchy.setDefaultPriority( Priority.getPriorityForName( loglevel ) );
m_hierarchy.setDefaultLogTargets( logTargets );
rootLoggerAlive = true;
}
final org.apache.log.Logger logger = m_hierarchy.getLoggerFor( fullCategory );
m_loggers.put( fullCategory, new LogKitLogger( logger ) );
if( getLogger().isDebugEnabled() )
{
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, false, defaultAdditive );
}
}
if ( root && !rootLoggerAlive )
{
final String message = "No log targets configured for the root logger.";
throw new ConfigurationException( message );
}
}
/**
* Closes all our LogTargets.
*/
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;
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 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() );
}
}
}
}