#region Apache License
//
// 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.
//
#endregion
using System;
using System.Collections;
using log4net.Appender;
using log4net.Core;
using log4net.Repository;
using log4net.Util;
namespace log4net.Repository.Hierarchy
{
#region LoggerCreationEvent
///
/// Delegate used to handle logger creation event notifications.
///
/// The in which the has been created.
/// The event args that hold the instance that has been created.
///
///
/// Delegate used to handle logger creation event notifications.
///
///
public delegate void LoggerCreationEventHandler(object sender, LoggerCreationEventArgs e);
///
/// Provides data for the event.
///
///
///
/// A event is raised every time a
/// is created.
///
///
public class LoggerCreationEventArgs : EventArgs
{
///
/// The created
///
private Logger m_log;
///
/// Constructor
///
/// The that has been created.
///
///
/// Initializes a new instance of the event argument
/// class,with the specified .
///
///
public LoggerCreationEventArgs(Logger log)
{
m_log = log;
}
///
/// Gets the that has been created.
///
///
/// The that has been created.
///
///
///
/// The that has been created.
///
///
public Logger Logger
{
get { return m_log; }
}
}
#endregion LoggerCreationEvent
///
/// Hierarchical organization of loggers
///
///
///
/// The casual user should not have to deal with this class
/// directly.
///
///
/// This class is specialized in retrieving loggers by name and
/// also maintaining the logger hierarchy. Implements the
/// interface.
///
///
/// The structure of the logger hierarchy is maintained by the
/// method. The hierarchy is such that children
/// link to their parent but parents do not have any references to their
/// children. Moreover, loggers can be instantiated in any order, in
/// particular descendant before ancestor.
///
///
/// In case a descendant is created before a particular ancestor,
/// then it creates a provision node for the ancestor and adds itself
/// to the provision node. Other descendants of the same ancestor add
/// themselves to the previously created provision node.
///
///
/// Nicko Cadell
/// Gert Driesen
public class Hierarchy : LoggerRepositorySkeleton, IBasicRepositoryConfigurator, IXmlRepositoryConfigurator
{
#region Public Events
///
/// Event used to notify that a logger has been created.
///
///
///
/// Event raised when a logger is created.
///
///
public event LoggerCreationEventHandler LoggerCreatedEvent
{
add { m_loggerCreatedEvent += value; }
remove { m_loggerCreatedEvent -= value; }
}
#endregion Public Events
#region Public Instance Constructors
///
/// Default constructor
///
///
///
/// Initializes a new instance of the class.
///
///
public Hierarchy() : this(new DefaultLoggerFactory())
{
}
///
/// Construct with properties
///
/// The properties to pass to this repository.
///
///
/// Initializes a new instance of the class.
///
///
public Hierarchy(PropertiesDictionary properties) : this(properties, new DefaultLoggerFactory())
{
}
///
/// Construct with a logger factory
///
/// The factory to use to create new logger instances.
///
///
/// Initializes a new instance of the class with
/// the specified .
///
///
public Hierarchy(ILoggerFactory loggerFactory) : this(new PropertiesDictionary(), loggerFactory)
{
}
///
/// Construct with properties and a logger factory
///
/// The properties to pass to this repository.
/// The factory to use to create new logger instances.
///
///
/// Initializes a new instance of the class with
/// the specified .
///
///
public Hierarchy(PropertiesDictionary properties, ILoggerFactory loggerFactory) : base(properties)
{
if (loggerFactory == null)
{
throw new ArgumentNullException("loggerFactory");
}
m_defaultFactory = loggerFactory;
m_ht = System.Collections.Hashtable.Synchronized(new System.Collections.Hashtable());
}
#endregion Public Instance Constructors
#region Public Instance Properties
///
/// Has no appender warning been emitted
///
///
///
/// Flag to indicate if we have already issued a warning
/// about not having an appender warning.
///
///
public bool EmittedNoAppenderWarning
{
get { return m_emittedNoAppenderWarning; }
set { m_emittedNoAppenderWarning = value; }
}
///
/// Get the root of this hierarchy
///
///
///
/// Get the root of this hierarchy.
///
///
public Logger Root
{
get
{
if (m_root == null)
{
lock(this)
{
if (m_root == null)
{
// Create the root logger
Logger root = m_defaultFactory.CreateLogger(this, null);
root.Hierarchy = this;
// Store root
m_root = root;
}
}
}
return m_root;
}
}
///
/// Gets or sets the default instance.
///
/// The default
///
///
/// The logger factory is used to create logger instances.
///
///
public ILoggerFactory LoggerFactory
{
get { return m_defaultFactory; }
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
m_defaultFactory = value;
}
}
#endregion Public Instance Properties
#region Override Implementation of LoggerRepositorySkeleton
///
/// Test if a logger exists
///
/// The name of the logger to lookup
/// The Logger object with the name specified
///
///
/// Check if the named logger exists in the hierarchy. If so return
/// its reference, otherwise returns null.
///
///
public override ILogger Exists(string name)
{
if (name == null)
{
throw new ArgumentNullException("name");
}
lock(m_ht)
{
return m_ht[new LoggerKey(name)] as Logger;
}
}
///
/// Returns all the currently defined loggers in the hierarchy as an Array
///
/// All the defined loggers
///
///
/// Returns all the currently defined loggers in the hierarchy as an Array.
/// The root logger is not included in the returned
/// enumeration.
///
///
public override ILogger[] GetCurrentLoggers()
{
// The accumulation in loggers is necessary because not all elements in
// ht are Logger objects as there might be some ProvisionNodes
// as well.
lock(m_ht)
{
System.Collections.ArrayList loggers = new System.Collections.ArrayList(m_ht.Count);
// Iterate through m_ht values
foreach(object node in m_ht.Values)
{
if (node is Logger)
{
loggers.Add(node);
}
}
return (Logger[])loggers.ToArray(typeof(Logger));
}
}
///
/// Return a new logger instance named as the first parameter using
/// the default factory.
///
///
///
/// Return a new logger instance named as the first parameter using
/// the default factory.
///
///
/// If a logger of that name already exists, then it will be
/// returned. Otherwise, a new logger will be instantiated and
/// then linked with its existing ancestors as well as children.
///
///
/// The name of the logger to retrieve
/// The logger object with the name specified
public override ILogger GetLogger(string name)
{
if (name == null)
{
throw new ArgumentNullException("name");
}
return GetLogger(name, m_defaultFactory);
}
///
/// Shutting down a hierarchy will safely close and remove
/// all appenders in all loggers including the root logger.
///
///
///
/// Shutting down a hierarchy will safely close and remove
/// all appenders in all loggers including the root logger.
///
///
/// Some appenders need to be closed before the
/// application exists. Otherwise, pending logging events might be
/// lost.
///
///
/// The Shutdown method is careful to close nested
/// appenders before closing regular appenders. This is allows
/// configurations where a regular appender is attached to a logger
/// and again to a nested appender.
///
///
public override void Shutdown()
{
LogLog.Debug(declaringType, "Shutdown called on Hierarchy ["+this.Name+"]");
// begin by closing nested appenders
Root.CloseNestedAppenders();
lock(m_ht)
{
ILogger[] currentLoggers = this.GetCurrentLoggers();
foreach(Logger logger in currentLoggers)
{
logger.CloseNestedAppenders();
}
// then, remove all appenders
Root.RemoveAllAppenders();
foreach(Logger logger in currentLoggers)
{
logger.RemoveAllAppenders();
}
}
base.Shutdown();
}
///
/// Reset all values contained in this hierarchy instance to their default.
///
///
///
/// Reset all values contained in this hierarchy instance to their
/// default. This removes all appenders from all loggers, sets
/// the level of all non-root loggers to null,
/// sets their additivity flag to true and sets the level
/// of the root logger to . Moreover,
/// message disabling is set its default "off" value.
///
///
/// Existing loggers are not removed. They are just reset.
///
///
/// This method should be used sparingly and with care as it will
/// block all logging until it is completed.
///
///
public override void ResetConfiguration()
{
Root.Level = LevelMap.LookupWithDefault(Level.Debug);
Threshold = LevelMap.LookupWithDefault(Level.All);
// the synchronization is needed to prevent hashtable surprises
lock(m_ht)
{
Shutdown(); // nested locks are OK
foreach(Logger l in this.GetCurrentLoggers())
{
l.Level = null;
l.Additivity = true;
}
}
base.ResetConfiguration();
// Notify listeners
OnConfigurationChanged(null);
}
///
/// Log the logEvent through this hierarchy.
///
/// the event to log
///
///
/// This method should not normally be used to log.
/// The interface should be used
/// for routine logging. This interface can be obtained
/// using the method.
///
///
/// The logEvent is delivered to the appropriate logger and
/// that logger is then responsible for logging the event.
///
///
public override void Log(LoggingEvent logEvent)
{
if (logEvent == null)
{
throw new ArgumentNullException("logEvent");
}
this.GetLogger(logEvent.LoggerName, m_defaultFactory).Log(logEvent);
}
///
/// Returns all the Appenders that are currently configured
///
/// An array containing all the currently configured appenders
///
///
/// Returns all the instances that are currently configured.
/// All the loggers are searched for appenders. The appenders may also be containers
/// for appenders and these are also searched for additional loggers.
///
///
/// The list returned is unordered but does not contain duplicates.
///
///
public override Appender.IAppender[] GetAppenders()
{
System.Collections.ArrayList appenderList = new System.Collections.ArrayList();
CollectAppenders(appenderList, Root);
foreach(Logger logger in GetCurrentLoggers())
{
CollectAppenders(appenderList, logger);
}
return (Appender.IAppender[])appenderList.ToArray(typeof(Appender.IAppender));
}
#endregion Override Implementation of LoggerRepositorySkeleton
#region Private Static Methods
///
/// Collect the appenders from an .
/// The appender may also be a container.
///
///
///
private static void CollectAppender(System.Collections.ArrayList appenderList, Appender.IAppender appender)
{
if (!appenderList.Contains(appender))
{
appenderList.Add(appender);
IAppenderAttachable container = appender as IAppenderAttachable;
if (container != null)
{
CollectAppenders(appenderList, container);
}
}
}
///
/// Collect the appenders from an container
///
///
///
private static void CollectAppenders(System.Collections.ArrayList appenderList, IAppenderAttachable container)
{
foreach(Appender.IAppender appender in container.Appenders)
{
CollectAppender(appenderList, appender);
}
}
#endregion
#region Implementation of IBasicRepositoryConfigurator
///
/// Initialize the log4net system using the specified appender
///
/// the appender to use to log all logging events
void IBasicRepositoryConfigurator.Configure(IAppender appender)
{
BasicRepositoryConfigure(appender);
}
///
/// Initialize the log4net system using the specified appenders
///
/// the appenders to use to log all logging events
void IBasicRepositoryConfigurator.Configure(params IAppender[] appenders)
{
BasicRepositoryConfigure(appenders);
}
///
/// Initialize the log4net system using the specified appenders
///
/// the appenders to use to log all logging events
///
///
/// This method provides the same functionality as the
/// method implemented
/// on this object, but it is protected and therefore can be called by subclasses.
///
///
protected void BasicRepositoryConfigure(params IAppender[] appenders)
{
ArrayList configurationMessages = new ArrayList();
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
foreach (IAppender appender in appenders)
{
Root.AddAppender(appender);
}
}
Configured = true;
ConfigurationMessages = configurationMessages;
// Notify listeners
OnConfigurationChanged(new ConfigurationChangedEventArgs(configurationMessages));
}
#endregion Implementation of IBasicRepositoryConfigurator
#region Implementation of IXmlRepositoryConfigurator
///
/// Initialize the log4net system using the specified config
///
/// the element containing the root of the config
void IXmlRepositoryConfigurator.Configure(System.Xml.XmlElement element)
{
XmlRepositoryConfigure(element);
}
///
/// Initialize the log4net system using the specified config
///
/// the element containing the root of the config
///
///
/// This method provides the same functionality as the
/// method implemented
/// on this object, but it is protected and therefore can be called by subclasses.
///
///
protected void XmlRepositoryConfigure(System.Xml.XmlElement element)
{
ArrayList configurationMessages = new ArrayList();
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
XmlHierarchyConfigurator config = new XmlHierarchyConfigurator(this);
config.Configure(element);
}
Configured = true;
ConfigurationMessages = configurationMessages;
// Notify listeners
OnConfigurationChanged(new ConfigurationChangedEventArgs(configurationMessages));
}
#endregion Implementation of IXmlRepositoryConfigurator
#region Public Instance Methods
///
/// Test if this hierarchy is disabled for the specified .
///
/// The level to check against.
///
/// true if the repository is disabled for the level argument, false otherwise.
///
///
///
/// If this hierarchy has not been configured then this method will
/// always return true.
///
///
/// This method will return true if this repository is
/// disabled for level object passed as parameter and
/// false otherwise.
///
///
/// See also the property.
///
///
public bool IsDisabled(Level level)
{
// Cast level to object for performance
if ((object)level == null)
{
throw new ArgumentNullException("level");
}
if (Configured)
{
return Threshold > level;
}
else
{
// If not configured the hierarchy is effectively disabled
return true;
}
}
///
/// Clear all logger definitions from the internal hashtable
///
///
///
/// This call will clear all logger definitions from the internal
/// hashtable. Invoking this method will irrevocably mess up the
/// logger hierarchy.
///
///
/// You should really know what you are doing before
/// invoking this method.
///
///
public void Clear()
{
lock(m_ht)
{
m_ht.Clear();
}
}
///
/// Return a new logger instance named as the first parameter using
/// .
///
/// The name of the logger to retrieve
/// The factory that will make the new logger instance
/// The logger object with the name specified
///
///
/// If a logger of that name already exists, then it will be
/// returned. Otherwise, a new logger will be instantiated by the
/// parameter and linked with its existing
/// ancestors as well as children.
///
///
public Logger GetLogger(string name, ILoggerFactory factory)
{
if (name == null)
{
throw new ArgumentNullException("name");
}
if (factory == null)
{
throw new ArgumentNullException("factory");
}
LoggerKey key = new LoggerKey(name);
// Synchronize to prevent write conflicts. Read conflicts (in
// GetEffectiveLevel() method) are possible only if variable
// assignments are non-atomic.
lock(m_ht)
{
Logger logger = null;
Object node = m_ht[key];
if (node == null)
{
logger = factory.CreateLogger(this, name);
logger.Hierarchy = this;
m_ht[key] = logger;
UpdateParents(logger);
OnLoggerCreationEvent(logger);
return logger;
}
Logger nodeLogger = node as Logger;
if (nodeLogger != null)
{
return nodeLogger;
}
ProvisionNode nodeProvisionNode = node as ProvisionNode;
if (nodeProvisionNode != null)
{
logger = factory.CreateLogger(this, name);
logger.Hierarchy = this;
m_ht[key] = logger;
UpdateChildren(nodeProvisionNode, logger);
UpdateParents(logger);
OnLoggerCreationEvent(logger);
return logger;
}
// It should be impossible to arrive here but let's keep the compiler happy.
return null;
}
}
#endregion Public Instance Methods
#region Protected Instance Methods
///
/// Sends a logger creation event to all registered listeners
///
/// The newly created logger
///
/// Raises the logger creation event.
///
protected virtual void OnLoggerCreationEvent(Logger logger)
{
LoggerCreationEventHandler handler = m_loggerCreatedEvent;
if (handler != null)
{
handler(this, new LoggerCreationEventArgs(logger));
}
}
#endregion Protected Instance Methods
#region Private Instance Methods
///
/// Updates all the parents of the specified logger
///
/// The logger to update the parents for
///
///
/// This method loops through all the potential parents of
/// . There 3 possible cases:
///
///
/// -
/// No entry for the potential parent of exists
///
/// We create a ProvisionNode for this potential
/// parent and insert in that provision node.
///
///
/// -
/// The entry is of type Logger for the potential parent.
///
/// The entry is 's nearest existing parent. We
/// update 's parent field with this entry. We also break from
/// he loop because updating our parent's parent is our parent's
/// responsibility.
///
///
/// -
/// The entry is of type ProvisionNode for this potential parent.
///
/// We add to the list of children for this
/// potential parent.
///
///
///
///
private void UpdateParents(Logger log)
{
string name = log.Name;
int length = name.Length;
bool parentFound = false;
// if name = "w.x.y.z", loop through "w.x.y", "w.x" and "w", but not "w.x.y.z"
for(int i = name.LastIndexOf('.', length-1); i >= 0; i = name.LastIndexOf('.', i-1))
{
string substr = name.Substring(0, i);
LoggerKey key = new LoggerKey(substr); // simple constructor
Object node = m_ht[key];
// Create a provision node for a future parent.
if (node == null)
{
ProvisionNode pn = new ProvisionNode(log);
m_ht[key] = pn;
}
else
{
Logger nodeLogger = node as Logger;
if (nodeLogger != null)
{
parentFound = true;
log.Parent = nodeLogger;
break; // no need to update the ancestors of the closest ancestor
}
else
{
ProvisionNode nodeProvisionNode = node as ProvisionNode;
if (nodeProvisionNode != null)
{
nodeProvisionNode.Add(log);
}
else
{
LogLog.Error(declaringType, "Unexpected object type ["+node.GetType()+"] in ht.", new LogException());
}
}
}
if (i == 0) {
// logger name starts with a dot
// and we've hit the start
break;
}
}
// If we could not find any existing parents, then link with root.
if (!parentFound)
{
log.Parent = this.Root;
}
}
///
/// Replace a with a in the hierarchy.
///
///
///
///
///
/// We update the links for all the children that placed themselves
/// in the provision node 'pn'. The second argument 'log' is a
/// reference for the newly created Logger, parent of all the
/// children in 'pn'.
///
///
/// We loop on all the children 'c' in 'pn'.
///
///
/// If the child 'c' has been already linked to a child of
/// 'log' then there is no need to update 'c'.
///
///
/// Otherwise, we set log's parent field to c's parent and set
/// c's parent field to log.
///
///
private static void UpdateChildren(ProvisionNode pn, Logger log)
{
for(int i = 0; i < pn.Count; i++)
{
Logger childLogger = (Logger)pn[i];
// Unless this child already points to a correct (lower) parent,
// make log.Parent point to childLogger.Parent and childLogger.Parent to log.
if (!childLogger.Parent.Name.StartsWith(log.Name))
{
log.Parent = childLogger.Parent;
childLogger.Parent = log;
}
}
}
///
/// Define or redefine a Level using the values in the argument
///
/// the level values
///
///
/// Define or redefine a Level using the values in the argument
///
///
/// Supports setting levels via the configuration file.
///
///
internal void AddLevel(LevelEntry levelEntry)
{
if (levelEntry == null) throw new ArgumentNullException("levelEntry");
if (levelEntry.Name == null) throw new ArgumentNullException("levelEntry.Name");
// Lookup replacement value
if (levelEntry.Value == -1)
{
Level previousLevel = LevelMap[levelEntry.Name];
if (previousLevel == null)
{
throw new InvalidOperationException("Cannot redefine level ["+levelEntry.Name+"] because it is not defined in the LevelMap. To define the level supply the level value.");
}
levelEntry.Value = previousLevel.Value;
}
LevelMap.Add(levelEntry.Name, levelEntry.Value, levelEntry.DisplayName);
}
///
/// A class to hold the value, name and display name for a level
///
///
///
/// A class to hold the value, name and display name for a level
///
///
internal class LevelEntry
{
private int m_levelValue = -1;
private string m_levelName = null;
private string m_levelDisplayName = null;
///
/// Value of the level
///
///
///
/// If the value is not set (defaults to -1) the value will be looked
/// up for the current level with the same name.
///
///
public int Value
{
get { return m_levelValue; }
set { m_levelValue = value; }
}
///
/// Name of the level
///
///
/// The name of the level
///
///
///
/// The name of the level.
///
///
public string Name
{
get { return m_levelName; }
set { m_levelName = value; }
}
///
/// Display name for the level
///
///
/// The display name of the level
///
///
///
/// The display name of the level.
///
///
public string DisplayName
{
get { return m_levelDisplayName; }
set { m_levelDisplayName = value; }
}
///
/// Override Object.ToString to return sensible debug info
///
/// string info about this object
public override string ToString()
{
return "LevelEntry(Value="+m_levelValue+", Name="+m_levelName+", DisplayName="+m_levelDisplayName+")";
}
}
///
/// Set a Property using the values in the argument
///
/// the property value
///
///
/// Set a Property using the values in the argument.
///
///
/// Supports setting property values via the configuration file.
///
///
internal void AddProperty(PropertyEntry propertyEntry)
{
if (propertyEntry == null) throw new ArgumentNullException("propertyEntry");
if (propertyEntry.Key == null) throw new ArgumentNullException("propertyEntry.Key");
Properties[propertyEntry.Key] = propertyEntry.Value;
}
#endregion Private Instance Methods
#region Private Instance Fields
private ILoggerFactory m_defaultFactory;
private System.Collections.Hashtable m_ht;
private Logger m_root;
private bool m_emittedNoAppenderWarning = false;
private event LoggerCreationEventHandler m_loggerCreatedEvent;
#endregion Private Instance Fields
#region Private Static Fields
///
/// The fully qualified type of the Hierarchy class.
///
///
/// Used by the internal logger to record the Type of the
/// log message.
///
private static readonly Type declaringType = typeof(Hierarchy);
#endregion Private Static Fields
}
}