in src/log4net/Repository/Hierarchy/Hierarchy.cs [88:778]
public class Hierarchy(PropertiesDictionary properties, ILoggerFactory loggerFactory)
: LoggerRepositorySkeleton(properties), IBasicRepositoryConfigurator, IXmlRepositoryConfigurator
{
private readonly ConcurrentDictionary<LoggerKey, object> _loggers = new(LoggerKey.ComparerInstance);
private ILoggerFactory _defaultFactory = loggerFactory.EnsureNotNull();
private Logger? _rootLogger;
/// <summary>
/// The fully qualified type of the Hierarchy class.
/// </summary>
/// <remarks>
/// Used by the internal logger to record the type of the log message.
/// </remarks>
private static readonly Type _declaringType = typeof(Hierarchy);
/// <summary>
/// Event used to notify that a logger has been created.
/// </summary>
public event LoggerCreationEventHandler? LoggerCreatedEvent;
/// <summary>
/// Default constructor
/// </summary>
public Hierarchy()
: this(new DefaultLoggerFactory())
{ }
/// <summary>
/// Construct with properties
/// </summary>
/// <param name="properties">The properties to pass to this repository.</param>
public Hierarchy(PropertiesDictionary properties)
: this(properties, new DefaultLoggerFactory())
{ }
/// <summary>
/// Construct with a logger factory
/// </summary>
/// <param name="loggerFactory">The factory to use to create new logger instances.</param>
public Hierarchy(ILoggerFactory loggerFactory) : this([], loggerFactory)
{ }
/// <summary>
/// Has no appender warning been emitted
/// </summary>
/// <remarks>
/// Flag to indicate if we have already issued a warning about not having an appender warning.
/// </remarks>
internal bool EmittedNoAppenderWarning { get; set; }
/// <summary>
/// Get the root of this hierarchy
/// </summary>
public Logger Root
{
get
{
if (_rootLogger is null)
{
Logger root = _defaultFactory.CreateLogger(this, null);
root.Hierarchy = this;
Interlocked.CompareExchange(ref _rootLogger, root, null);
}
return _rootLogger;
}
}
/// <summary>
/// Gets or sets the default <see cref="ILoggerFactory" /> instance.
/// </summary>
/// <remarks>
/// <para>
/// The logger factory is used to create logger instances.
/// </para>
/// </remarks>
public ILoggerFactory LoggerFactory
{
get => _defaultFactory;
set => _defaultFactory = value.EnsureNotNull();
}
/// <summary>
/// Test if a logger exists
/// </summary>
/// <param name="name">The name of the logger to lookup</param>
/// <returns>The Logger object with the name specified</returns>
/// <remarks>
/// <para>
/// Check if the named logger exists in the hierarchy. If so return
/// its reference, otherwise returns <see langword="null"/>.
/// </para>
/// </remarks>
public override ILogger? Exists(string name)
{
_loggers.TryGetValue(new(name.EnsureNotNull()), out object? o);
return o as Logger;
}
/// <summary>
/// Returns all the currently defined loggers in the hierarchy as an Array
/// </summary>
/// <returns>All the defined loggers</returns>
/// <remarks>
/// <para>
/// Returns all the currently defined loggers in the hierarchy as an Array.
/// The root logger is <b>not</b> included in the returned
/// enumeration.
/// </para>
/// </remarks>
public override ILogger[] GetCurrentLoggers() =>
// The accumulation in loggers is necessary because not all elements in
// loggers are Logger objects as there might be some ProvisionNodes as well.
_loggers.Select(logger => logger.Value).OfType<ILogger>().ToArray();
/// <summary>
/// Return a new logger instance named as the first parameter using
/// the default factory.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="name">The name of the logger to retrieve</param>
/// <returns>The logger object with the name specified</returns>
public override ILogger GetLogger(string name)
=> GetLogger(name.EnsureNotNull(), _defaultFactory);
/// <summary>
/// Shutting down a hierarchy will <i>safely</i> close and remove
/// all appenders in all loggers including the root logger.
/// </summary>
/// <remarks>
/// <para>
/// Shutting down a hierarchy will <i>safely</i> close and remove
/// all appenders in all loggers including the root logger.
/// </para>
/// <para>
/// Some appenders need to be closed before the
/// application exists. Otherwise, pending logging events might be
/// lost.
/// </para>
/// <para>
/// The <see cref="Shutdown"/> method is careful to close nested
/// appenders before closing regular appenders. This allows
/// configurations where a regular appender is attached to a logger
/// and again to a nested appender.
/// </para>
/// </remarks>
public override void Shutdown()
{
LogLog.Debug(_declaringType, $"Shutdown called on Hierarchy [{Name}]");
// begin by closing nested appenders
Root.CloseNestedAppenders();
ILogger[] currentLoggers = GetCurrentLoggers();
foreach (Logger logger in currentLoggers.OfType<Logger>())
{
logger.CloseNestedAppenders();
}
// then, remove all appenders
Root.RemoveAllAppenders();
foreach (Logger logger in currentLoggers.OfType<Logger>())
{
logger.RemoveAllAppenders();
}
base.Shutdown();
}
/// <summary>
/// Reset all values contained in this hierarchy instance to their default.
/// </summary>
/// <remarks>
/// <para>
/// 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 <see langword="null"/>,
/// sets their additivity flag to <see langword="true"/> and sets the level
/// of the root logger to <see cref="Level.Debug"/>. Moreover,
/// message disabling is set its default "off" value.
/// </para>
/// <para>
/// Existing loggers are not removed. They are just reset.
/// </para>
/// <para>
/// This method should be used sparingly and with care as it will
/// block all logging until it is completed.
/// </para>
/// </remarks>
public override void ResetConfiguration()
{
Root.Level = LevelMap.LookupWithDefault(Level.Debug);
Threshold = LevelMap.LookupWithDefault(Level.All);
Shutdown(); // nested locks are OK
foreach (Logger logger in GetCurrentLoggers().OfType<Logger>())
{
logger.Level = null;
logger.Additivity = true;
}
base.ResetConfiguration();
// Notify listeners
OnConfigurationChanged(null);
}
/// <summary>
/// Log the logEvent through this hierarchy.
/// </summary>
/// <param name="logEvent">the event to log</param>
/// <remarks>
/// <para>
/// This method should not normally be used to log.
/// The <see cref="ILog"/> interface should be used
/// for routine logging. This interface can be obtained
/// using the <see cref="LogManager.GetLogger(string)"/> method.
/// </para>
/// <para>
/// The <paramref name="logEvent" /> is delivered to the appropriate logger and
/// that logger is then responsible for logging the event.
/// </para>
/// </remarks>
public override void Log(LoggingEvent logEvent)
{
logEvent.EnsureNotNull();
if (logEvent.LoggerName is not null)
{
GetLogger(logEvent.LoggerName, _defaultFactory).Log(logEvent);
}
}
/// <summary>
/// Returns all the Appenders that are currently configured
/// </summary>
/// <returns>An array containing all the currently configured appenders</returns>
/// <remarks>
/// <para>
/// Returns all the <see cref="IAppender"/> 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.
/// </para>
/// <para>
/// The list returned is unordered but does not contain duplicates.
/// </para>
/// </remarks>
[SuppressMessage("Style", "IDE0305:Simplify collection initialization")]
public override IAppender[] GetAppenders()
{
HashSet<IAppender> appenderList = [];
CollectAppenders(appenderList, Root);
foreach (Logger logger in GetCurrentLoggers().OfType<Logger>())
{
CollectAppenders(appenderList, logger);
}
return appenderList.ToArray();
}
/// <summary>
/// Collect the appenders from an <see cref="IAppenderAttachable"/>.
/// The appender may also be a container.
/// </summary>
private static void CollectAppender(HashSet<IAppender> appenderList, IAppender appender)
{
if (appenderList.Add(appender) && appender is IAppenderAttachable container)
{
CollectAppenders(appenderList, container);
}
}
/// <summary>
/// Collect the appenders from an <see cref="IAppenderAttachable"/> container
/// </summary>
private static void CollectAppenders(HashSet<IAppender> appenderList, IAppenderAttachable container)
{
foreach (IAppender appender in container.Appenders)
{
CollectAppender(appenderList, appender);
}
}
/// <summary>
/// Initialize the log4net system using the specified appender
/// </summary>
/// <param name="appender">the appender to use to log all logging events</param>
void IBasicRepositoryConfigurator.Configure(IAppender appender)
=> BasicRepositoryConfigure(appender);
/// <summary>
/// Initialize the log4net system using the specified appenders
/// </summary>
/// <param name="appenders">the appenders to use to log all logging events</param>
void IBasicRepositoryConfigurator.Configure(params IAppender[] appenders)
=> BasicRepositoryConfigure(appenders);
/// <summary>
/// Initialize the log4net system using the specified appenders
/// </summary>
/// <param name="appenders">the appenders to use to log all logging events</param>
/// <remarks>
/// <para>
/// This method provides the same functionality as the
/// <see cref="IBasicRepositoryConfigurator.Configure(IAppender)"/> method implemented
/// on this object, but it is protected and therefore can be called by subclasses.
/// </para>
/// </remarks>
protected void BasicRepositoryConfigure(params IAppender[] appenders)
{
appenders.EnsureNotNull();
List<LogLog> configurationMessages = [];
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
foreach (IAppender appender in appenders)
{
Root.AddAppender(appender);
}
}
Configured = true;
ConfigurationMessages = configurationMessages;
// Notify listeners
OnConfigurationChanged(new ConfigurationChangedEventArgs(configurationMessages));
}
/// <summary>
/// Initialize the log4net system using the specified config
/// </summary>
/// <param name="element">the element containing the root of the config</param>
void IXmlRepositoryConfigurator.Configure(System.Xml.XmlElement element) => XmlRepositoryConfigure(element);
/// <summary>
/// Initialize the log4net system using the specified config
/// </summary>
/// <param name="element">the element containing the root of the config</param>
/// <remarks>
/// <para>
/// This method provides the same functionality as the
/// <see cref="IBasicRepositoryConfigurator.Configure(IAppender)"/> method implemented
/// on this object, but it is protected and therefore can be called by subclasses.
/// </para>
/// </remarks>
protected void XmlRepositoryConfigure(System.Xml.XmlElement element)
{
List<LogLog> configurationMessages = [];
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
new XmlHierarchyConfigurator(this).Configure(element);
}
Configured = true;
ConfigurationMessages = configurationMessages;
// Notify listeners
OnConfigurationChanged(new ConfigurationChangedEventArgs(configurationMessages));
}
/// <summary>
/// Test if this hierarchy is disabled for the specified <see cref="Level"/>.
/// </summary>
/// <param name="level">The level to check against.</param>
/// <returns>
/// <see langword="true"/> if the repository is disabled for the level argument, <see langword="false"/> otherwise.
/// </returns>
/// <remarks>
/// If this hierarchy has not been configured then this method will always return <see langword="true"/>.
/// See also the <see cref="ILoggerRepository.Threshold"/> property.
/// </remarks>
public bool IsDisabled(Level level)
{
if (Configured)
{
return Threshold > level.EnsureNotNull();
}
// If not configured the hierarchy is effectively disabled
return true;
}
/// <summary>
/// Clear all logger definitions from the internal hashtable
/// </summary>
/// <remarks>
/// <para>
/// This call will clear all logger definitions from the internal
/// hashtable. Invoking this method will irrevocably mess up the
/// logger hierarchy.
/// </para>
/// <para>
/// You should <b>really</b> know what you are doing before invoking this method.
/// </para>
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
public void Clear() => _loggers.Clear();
/// <summary>
/// Returns a new logger instance named as the first parameter using
/// <paramref name="factory"/>.
/// </summary>
/// <param name="name">The name of the logger to retrieve</param>
/// <param name="factory">The factory that will make the new logger instance</param>
/// <returns>The logger object with the name specified</returns>
/// <remarks>
/// <para>
/// If a logger of that name already exists, then it will be
/// returned. Otherwise, a new logger will be instantiated by the
/// <paramref name="factory"/> parameter and linked with its existing
/// ancestors as well as children.
/// </para>
/// </remarks>
public Logger GetLogger(string name, ILoggerFactory factory)
{
name.EnsureNotNull();
factory.EnsureNotNull();
LoggerKey key = new(name);
const int maxRetries = 5;
for (int i = 0; i < maxRetries; i++)
{
if (TryCreateLogger(key, factory) is Logger result)
{
return result;
}
}
throw new LogException(
$"GetLogger failed, because possibly too many threads are messing with creating the logger {name}!");
}
private Logger? TryCreateLogger(LoggerKey key, ILoggerFactory factory)
{
if (!_loggers.TryGetValue(key, out object? node))
{
Logger newLogger = CreateLogger(key.Name);
node = _loggers.GetOrAdd(key, newLogger);
if (node == newLogger)
{
RegisterLogger(newLogger);
}
}
if (node is Logger logger)
{
return logger;
}
if (node is ProvisionNode provisionNode)
{
Logger newLogger = CreateLogger(key.Name);
if (_loggers.TryUpdate(key, newLogger, node))
{
UpdateChildren(provisionNode, newLogger);
RegisterLogger(newLogger);
return newLogger;
}
return null;
}
// It should be impossible to arrive here but let's keep the compiler happy.
throw new LogException("TryCreateLogger failed, because a node is neither a Logger nor a ProvisionNode!");
Logger CreateLogger(string name)
{
Logger result = factory.CreateLogger(this, name);
result.Hierarchy = this;
return result;
}
void RegisterLogger(Logger logger)
{
UpdateParents(logger);
OnLoggerCreationEvent(logger);
}
}
/// <summary>
/// Sends a logger creation event to all registered listeners
/// </summary>
/// <param name="logger">The newly created logger</param>
/// <remarks>
/// Raises the logger creation event.
/// </remarks>
protected virtual void OnLoggerCreationEvent(Logger logger)
=> LoggerCreatedEvent?.Invoke(this, new(logger));
/// <summary>
/// Updates all the parents of the specified logger
/// </summary>
/// <param name="log">The logger to update the parents for</param>
/// <remarks>
/// <para>
/// This method loops through all the <i>potential</i> parents of
/// <paramref name="log"/>. There 3 possible cases:
/// </para>
/// <list type="number">
/// <item>
/// <term>No entry for the potential parent of <paramref name="log"/> exists</term>
/// <description>
/// We create a ProvisionNode for this potential
/// parent and insert <paramref name="log"/> in that provision node.
/// </description>
/// </item>
/// <item>
/// <term>The entry is of type Logger for the potential parent.</term>
/// <description>
/// The entry is <paramref name="log"/>'s nearest existing parent. We
/// update <paramref name="log"/>'s parent field with this entry. We also break from
/// the loop because updating our parent's parent is our parent's
/// responsibility.
/// </description>
/// </item>
/// <item>
/// <term>The entry is of type ProvisionNode for this potential parent.</term>
/// <description>
/// We add <paramref name="log"/> to the list of children for this potential parent.
/// </description>
/// </item>
/// </list>
/// </remarks>
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(substr);
_loggers.TryGetValue(key, out object? node);
// Create a provision node for a future parent.
if (node is null)
{
_loggers[key] = new ProvisionNode(log);
}
else
{
if (node is Logger nodeLogger)
{
parentFound = true;
log.Parent = nodeLogger;
break; // no need to update the ancestors of the closest ancestor
}
if (node is ProvisionNode nodeProvisionNode)
{
nodeProvisionNode.Add(log);
}
else
{
LogLog.Error(_declaringType, $"Unexpected object type [{node.GetType()}] in loggers.", 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 = Root;
}
}
/// <summary>
/// Replace a <see cref="ProvisionNode"/> with a <see cref="Logger"/> in the hierarchy.
/// </summary>
/// <remarks>
/// <para>
/// 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'.
/// </para>
/// <para>
/// We loop on all the children 'c' in 'pn'.
/// </para>
/// <para>
/// If the child 'c' has been already linked to a child of
/// 'log' then there is no need to update 'c'.
/// </para>
/// <para>
/// Otherwise, we set log's parent field to c's parent and set
/// c's parent field to log.
/// </para>
/// </remarks>
private static void UpdateChildren(ProvisionNode provisionNode, Logger log)
{
provisionNode.ForEach(Update, log);
static void Update(Logger childLogger, Logger log)
{
// 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 is not null && !childLogger.Parent.Name.StartsWith(log.Name, StringComparison.Ordinal))
{
log.Parent = childLogger.Parent;
childLogger.Parent = log;
}
}
}
/// <summary>
/// Define or redefine a Level using the values in the <see cref="LevelEntry"/> argument
/// </summary>
/// <param name="levelEntry">the level values</param>
/// <remarks>
/// Supports setting levels via the configuration file.
/// </remarks>
internal void AddLevel(LevelEntry levelEntry)
{
levelEntry.EnsureNotNull();
levelEntry.Name.EnsureNotNull();
// Lookup replacement value
if (levelEntry.Value == -1)
{
if (LevelMap[levelEntry.Name] is Level previousLevel)
{
levelEntry.Value = previousLevel.Value;
}
else
{
throw new InvalidOperationException($"Cannot redefine level [{levelEntry.Name}] because it is not defined in the LevelMap. To define the level supply the level value.");
}
}
LevelMap.Add(levelEntry.Name, levelEntry.Value, levelEntry.DisplayName);
}
/// <summary>
/// A class to hold the value, name and display name for a level
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Reflection")]
internal sealed class LevelEntry
{
/// <summary>
/// Value of the level
/// </summary>
/// <remarks>
/// If the value is not set (defaults to -1) the value will be looked
/// up for the current level with the same name.
/// </remarks>
public int Value { get; set; } = -1;
/// <summary>
/// Name of the level
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Display name for the level
/// </summary>
public string? DisplayName { get; set; }
/// <summary>
/// Override <c>Object.ToString</c> to return sensible debug info
/// </summary>
/// <returns>string info about this object</returns>
public override string ToString()
=> $"LevelEntry(Value={Value}, Name={Name}, DisplayName={DisplayName})";
}
/// <summary>
/// Set a Property using the values in the <see cref="LevelEntry"/> argument
/// </summary>
/// <param name="propertyEntry">the property value</param>
/// <remarks>
/// Supports setting property values via the configuration file.
/// </remarks>
internal void AddProperty(PropertyEntry propertyEntry)
=> Properties[propertyEntry.Key.EnsureNotNull()] = propertyEntry.EnsureNotNull().Value;
}