#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. /// /// override public ILogger Exists(string name) { if (name == null) { throw new ArgumentNullException("name"); } 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. /// /// override public 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. 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 override public 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. /// /// override public 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. /// /// override public 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. /// /// override public 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. /// /// override public 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() { 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 readonly static Type declaringType = typeof(Hierarchy); #endregion Private Static Fields } }