in src/log4net/Repository/Hierarchy/XmlHierarchyConfigurator.cs [41:1040]
public class XmlHierarchyConfigurator(Hierarchy hierarchy)
{
private enum ConfigUpdateMode
{
Merge,
Overwrite
}
/// <summary>
/// Configure the hierarchy by parsing a DOM tree of XML elements.
/// </summary>
/// <param name="element">The root element to parse.</param>
/// <remarks>
/// <para>
/// Configure the hierarchy by parsing a DOM tree of XML elements.
/// </para>
/// </remarks>
public void Configure(XmlElement? element)
{
if (element is null)
{
return;
}
string rootElementName = element.LocalName;
if (rootElementName != ConfigurationTag)
{
LogLog.Error(_declaringType, $"Xml element is - not a <{ConfigurationTag}> element.");
return;
}
if (!LogLog.EmitInternalMessages)
{
// Look for a emitDebug attribute to enable internal debug
string emitDebugAttribute = element.GetAttribute(EmitInternalDebugAttr);
LogLog.Debug(_declaringType, $"{EmitInternalDebugAttr} attribute [{emitDebugAttribute}].");
if (emitDebugAttribute.Length > 0 && emitDebugAttribute != "null")
{
LogLog.EmitInternalMessages = OptionConverter.ToBoolean(emitDebugAttribute, true);
}
else
{
LogLog.Debug(_declaringType, $"Ignoring {EmitInternalDebugAttr} attribute.");
}
}
if (!LogLog.InternalDebugging)
{
// Look for a debug attribute to enable internal debug
string debugAttribute = element.GetAttribute(InternalDebugAttr);
LogLog.Debug(_declaringType, $"{InternalDebugAttr} attribute [{debugAttribute}].");
if (debugAttribute.Length > 0 && debugAttribute != "null")
{
LogLog.InternalDebugging = OptionConverter.ToBoolean(debugAttribute, true);
}
else
{
LogLog.Debug(_declaringType, $"Ignoring {InternalDebugAttr} attribute.");
}
string confDebug = element.GetAttribute(ConfigDebugAttr);
if (confDebug.Length > 0 && confDebug != "null")
{
LogLog.Warn(_declaringType, $"The \"{ConfigDebugAttr}\" attribute is deprecated.");
LogLog.Warn(_declaringType, $"Use the \"{InternalDebugAttr}\" attribute instead.");
LogLog.InternalDebugging = OptionConverter.ToBoolean(confDebug, true);
}
}
// Default mode is merge
ConfigUpdateMode? configUpdateMode = ConfigUpdateMode.Merge;
// Look for the config update attribute
string? configUpdateModeAttribute = element.GetAttribute(ConfigUpdateModeAttr);
if (!string.IsNullOrEmpty(configUpdateModeAttribute))
{
// Parse the attribute
try
{
if (OptionConverter.ConvertStringTo(typeof(ConfigUpdateMode), configUpdateModeAttribute) is ConfigUpdateMode mode)
{
configUpdateMode = mode;
}
else
{
LogLog.Error(_declaringType, $"Invalid {ConfigUpdateModeAttr} attribute value [{configUpdateModeAttribute}]");
}
}
catch (Exception e) when (!e.IsFatal())
{
LogLog.Error(_declaringType, $"Invalid {ConfigUpdateModeAttr} attribute value [{configUpdateModeAttribute}]", e);
}
}
// IMPL: The IFormatProvider argument to Enum.ToString() is deprecated in .NET 2.0
LogLog.Debug(_declaringType, $"Configuration update mode [{configUpdateMode}].");
// Only reset configuration if overwrite flag specified
if (configUpdateMode == ConfigUpdateMode.Overwrite)
{
// Reset to original unset configuration
hierarchy.ResetConfiguration();
LogLog.Debug(_declaringType, "Configuration reset before reading config.");
}
/* Building Appender objects, placing them in a local namespace
for future reference */
/* Process all the top level elements */
foreach (XmlNode currentNode in element.ChildNodes)
{
if (currentNode.NodeType == XmlNodeType.Element)
{
XmlElement currentElement = (XmlElement)currentNode;
if (currentElement.LocalName == LoggerTag)
{
ParseLogger(currentElement);
}
else if (currentElement.LocalName == CategoryTag)
{
// TODO: deprecated use of category
ParseLogger(currentElement);
}
else if (currentElement.LocalName == RootTag)
{
ParseRoot(currentElement);
}
else if (currentElement.LocalName == RendererTag)
{
ParseRenderer(currentElement);
}
else if (currentElement.LocalName == AppenderTag)
{
// We ignore appenders in this pass. They will
// be found and loaded if they are referenced.
}
else
{
// Read the param tags and set properties on the hierarchy
SetParameter(currentElement, hierarchy);
}
}
}
// Lastly set the hierarchy threshold
string thresholdStr = element.GetAttribute(ThresholdAttr);
LogLog.Debug(_declaringType, $"Hierarchy Threshold [{thresholdStr}]");
if (thresholdStr.Length > 0 && thresholdStr != "null")
{
if (ConvertStringTo(typeof(Level), thresholdStr) is Level thresholdLevel)
{
hierarchy.Threshold = thresholdLevel;
}
else
{
LogLog.Warn(_declaringType, $"Unable to set hierarchy threshold using value [{thresholdStr}] (with acceptable conversion types)");
}
}
// Done reading config
}
/// <summary>
/// Parse appenders by IDREF.
/// </summary>
/// <param name="appenderRef">The appender ref element.</param>
/// <returns>The instance of the appender that the ref refers to.</returns>
/// <remarks>
/// <para>
/// Parse an XML element that represents an appender and return
/// the appender.
/// </para>
/// </remarks>
protected IAppender? FindAppenderByReference(XmlElement appenderRef)
{
string? appenderName = appenderRef.EnsureNotNull().GetAttribute(RefAttr);
if (_appenderBag.TryGetValue(appenderName, out IAppender? appender))
{
return appender;
}
// Find the element with that id
XmlElement? element = null;
if (!string.IsNullOrEmpty(appenderName) && appenderRef.OwnerDocument is not null)
{
foreach (XmlElement curAppenderElement in appenderRef.OwnerDocument.GetElementsByTagName(AppenderTag))
{
if (curAppenderElement.GetAttribute("name") == appenderName)
{
element = curAppenderElement;
break;
}
}
}
if (element is null)
{
LogLog.Error(_declaringType, $"XmlHierarchyConfigurator: No appender named [{appenderName}] could be found.");
return null;
}
appender = ParseAppender(element);
if (appender is not null)
{
_appenderBag[appenderName] = appender;
}
return appender;
}
/// <summary>
/// Parses an appender element.
/// </summary>
/// <param name="appenderElement">The appender element.</param>
/// <returns>The appender instance or <c>null</c> when parsing failed.</returns>
/// <remarks>
/// <para>
/// Parse an XML element that represents an appender and return
/// the appender instance.
/// </para>
/// </remarks>
protected IAppender? ParseAppender(XmlElement appenderElement)
{
string appenderName = appenderElement.EnsureNotNull().GetAttribute(NameAttr);
string typeName = appenderElement.GetAttribute(TypeAttr);
LogLog.Debug(_declaringType, $"Loading Appender [{appenderName}] type: [{typeName}]");
try
{
IAppender appender = Activator.CreateInstance(
SystemInfo.GetTypeFromString(typeName, true, true).EnsureNotNull()).EnsureIs<IAppender>();
appender.Name = appenderName;
foreach (XmlNode currentNode in appenderElement.ChildNodes)
{
// We're only interested in Elements
if (currentNode.NodeType == XmlNodeType.Element)
{
XmlElement currentElement = (XmlElement)currentNode;
// Look for the appender ref tag
if (currentElement.LocalName == AppenderRefTag)
{
string refName = currentElement.GetAttribute(RefAttr);
if (appender is IAppenderAttachable appenderContainer)
{
LogLog.Debug(_declaringType, $"Attaching appender named [{refName}] to appender named [{appender.Name}].");
if (FindAppenderByReference(currentElement) is IAppender referencedAppender)
{
appenderContainer.AddAppender(referencedAppender);
}
}
else
{
LogLog.Error(_declaringType, $"Requesting attachment of appender named [{refName}] to appender named [{appender.Name}] which does not implement log4net.Core.IAppenderAttachable.");
}
}
else
{
// For all other tags we use standard set param method
SetParameter(currentElement, appender);
}
}
}
if (appender is IOptionHandler optionHandler)
{
optionHandler.ActivateOptions();
}
LogLog.Debug(_declaringType, $"Created Appender [{appenderName}]");
return appender;
}
catch (Exception e) when (!e.IsFatal())
{
// Yes, it's ugly. But all exceptions point to the same problem: we can't create an Appender
LogLog.Error(_declaringType, $"Could not create Appender [{appenderName}] of type [{typeName}]. Reported error follows.", e);
return null;
}
}
/// <summary>
/// Parses a logger element.
/// </summary>
/// <param name="loggerElement">The logger element.</param>
/// <remarks>
/// <para>
/// Parse an XML element that represents a logger.
/// </para>
/// </remarks>
protected void ParseLogger(XmlElement loggerElement)
{
// Create a new log4net.Logger object from the <logger> element.
string loggerName = loggerElement.EnsureNotNull().GetAttribute(NameAttr);
LogLog.Debug(_declaringType, $"Retrieving an instance of log4net.Repository.Logger for logger [{loggerName}].");
// Setting up a logger needs to be an atomic operation, in order
// to protect potential log operations while logger
// configuration is in progress.
if (hierarchy.GetLogger(loggerName) is Logger log)
{
lock (log)
{
bool additivity = OptionConverter.ToBoolean(loggerElement.GetAttribute(AdditivityAttr), true);
LogLog.Debug(_declaringType, $"Setting [{log.Name}] additivity to [{additivity}].");
log.Additivity = additivity;
ParseChildrenOfLoggerElement(loggerElement, log, false);
}
}
}
/// <summary>
/// Parses the root logger element.
/// </summary>
/// <param name="rootElement">The root element.</param>
/// <remarks>
/// <para>
/// Parse an XML element that represents the root logger.
/// </para>
/// </remarks>
protected void ParseRoot(XmlElement rootElement)
{
Logger root = hierarchy.Root;
// logger configuration needs to be atomic
lock (root)
{
ParseChildrenOfLoggerElement(rootElement, root, true);
}
}
/// <summary>
/// Parses the children of a logger element.
/// </summary>
/// <param name="catElement">The category element.</param>
/// <param name="log">The logger instance.</param>
/// <param name="isRoot">Flag to indicate if the logger is the root logger.</param>
/// <remarks>
/// <para>
/// Parse the child elements of a <logger> element.
/// </para>
/// </remarks>
protected void ParseChildrenOfLoggerElement(XmlElement catElement, Logger log, bool isRoot)
{
// Remove all existing appenders from log. They will be
// reconstructed if need be.
log.EnsureNotNull().RemoveAllAppenders();
foreach (XmlNode currentNode in catElement.EnsureNotNull().ChildNodes)
{
if (currentNode.NodeType == XmlNodeType.Element)
{
XmlElement currentElement = (XmlElement)currentNode;
if (currentElement.LocalName == AppenderRefTag)
{
string refName = currentElement.GetAttribute(RefAttr);
if (FindAppenderByReference(currentElement) is IAppender appender)
{
LogLog.Debug(_declaringType, $"Adding appender named [{refName}] to logger [{log.Name}].");
log.AddAppender(appender);
}
else
{
LogLog.Error(_declaringType, $"Appender named [{refName}] not found.");
}
}
else if (currentElement.LocalName is LevelTag or PriorityTag)
{
ParseLevel(currentElement, log, isRoot);
}
else
{
SetParameter(currentElement, log);
}
}
}
if (log is IOptionHandler optionHandler)
{
optionHandler.ActivateOptions();
}
}
/// <summary>
/// Parses an object renderer.
/// </summary>
/// <param name="element">The renderer element.</param>
/// <remarks>
/// <para>
/// Parse an XML element that represents a renderer.
/// </para>
/// </remarks>
protected void ParseRenderer(XmlElement element)
{
string renderingClassName = element.EnsureNotNull().GetAttribute(RenderingTypeAttr);
string renderedClassName = element.GetAttribute(RenderedTypeAttr);
LogLog.Debug(_declaringType, $"Rendering class [{renderingClassName}], Rendered class [{renderedClassName}].");
if (OptionConverter.InstantiateByClassName(renderingClassName, typeof(IObjectRenderer), null) is not IObjectRenderer renderer)
{
LogLog.Error(_declaringType, $"Could not instantiate renderer [{renderingClassName}].");
return;
}
try
{
hierarchy.RendererMap.Put(SystemInfo.GetTypeFromString(renderedClassName, true, true)!, renderer);
}
catch (Exception e) when (!e.IsFatal())
{
LogLog.Error(_declaringType, $"Could not find class [{renderedClassName}].", e);
}
}
/// <summary>
/// Parses a level element.
/// </summary>
/// <param name="element">The level element.</param>
/// <param name="log">The logger object to set the level on.</param>
/// <param name="isRoot">Flag to indicate if the logger is the root logger.</param>
/// <remarks>
/// <para>
/// Parse an XML element that represents a level.
/// </para>
/// </remarks>
protected static void ParseLevel(XmlElement element, Logger log, bool isRoot)
{
string loggerName = log.EnsureNotNull().Name;
if (isRoot)
{
loggerName = "root";
}
string levelStr = element.EnsureNotNull().GetAttribute(ValueAttr);
LogLog.Debug(_declaringType, $"Logger [{loggerName}] Level string is [{levelStr}].");
if (Inherited == levelStr)
{
if (isRoot)
{
LogLog.Error(_declaringType, "Root level cannot be inherited. Ignoring directive.");
}
else
{
LogLog.Debug(_declaringType, $"Logger [{loggerName}] level set to inherit from parent.");
log.Level = null;
}
}
else
{
log.Level = log.Hierarchy?.LevelMap[levelStr];
if (log.Level is null)
{
LogLog.Error(_declaringType, $"Undefined level [{levelStr}] on Logger [{loggerName}].");
}
else
{
LogLog.Debug(_declaringType, $"Logger [{loggerName}] level set to [name=\"{log.Level.Name}\",value={log.Level.Value}].");
}
}
}
/// <summary>
/// Sets a parameter on an object.
/// </summary>
/// <param name="element">The parameter element.</param>
/// <param name="target">The object to set the parameter on.</param>
/// <remarks>
/// The parameter name must correspond to a writable property
/// on the object. The value of the parameter is a string,
/// therefore this function will attempt to set a string
/// property first. If unable to set a string property it
/// will inspect the property and its argument type. It will
/// attempt to call a static method called <c>Parse</c> on the
/// type of the property. This method will take a single
/// string argument and return a value that can be used to
/// set the property.
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
protected void SetParameter(XmlElement element, object target)
{
// Get the property name
string name = element.EnsureNotNull().GetAttribute(NameAttr);
// If the name attribute does not exist then use the name of the element
if (element.LocalName != ParamTag || name.Length == 0)
{
name = element.LocalName;
}
// Look for the property on the target object
Type targetType = target.EnsureNotNull().GetType();
Type? propertyType = null;
MethodInfo? methInfo = null;
// Try to find a writable property
PropertyInfo? propInfo = targetType.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
if (propInfo is not null && propInfo.CanWrite)
{
// found a property
propertyType = propInfo.PropertyType;
}
else
{
propInfo = null;
// look for a method with the signature Add<property>(type)
methInfo = FindMethodInfo(targetType, name);
if (methInfo is not null)
{
propertyType = methInfo.GetParameters()[0].ParameterType;
}
}
if (propertyType is null)
{
LogLog.Error(_declaringType, $"XmlHierarchyConfigurator: Cannot find Property [{name}] to set object on [{target}]");
}
else
{
string? propertyValue = null;
if (element.GetAttributeNode(ValueAttr) is not null)
{
propertyValue = element.GetAttribute(ValueAttr);
}
else if (element.HasChildNodes)
{
// Concatenate the CDATA and Text nodes together
foreach (XmlNode childNode in element.ChildNodes)
{
if (childNode.NodeType is XmlNodeType.CDATA or XmlNodeType.Text)
{
if (propertyValue is null)
{
propertyValue = childNode.InnerText;
}
else
{
propertyValue += childNode.InnerText;
}
}
}
}
if (propertyValue is not null)
{
try
{
// Expand environment variables in the string.
IDictionary environmentVariables = Environment.GetEnvironmentVariables();
if (HasCaseInsensitiveEnvironment)
{
environmentVariables = CreateCaseInsensitiveWrapper(environmentVariables);
}
propertyValue = OptionConverter.SubstituteVariables(propertyValue, environmentVariables);
}
catch (System.Security.SecurityException)
{
// This security exception will occur if the caller does not have
// unrestricted environment permission. If this occurs the expansion
// will be skipped with the following warning message.
LogLog.Debug(_declaringType, "Security exception while trying to expand environment variables. Error Ignored. No Expansion.");
}
Type? parsedObjectConversionTargetType = null;
// Check if a specific subtype is specified on the element using the 'type' attribute
string subTypeString = element.GetAttribute(TypeAttr);
if (subTypeString.Length > 0)
{
// Read the explicit subtype
try
{
Type subType = SystemInfo.GetTypeFromString(subTypeString, true, true)!;
LogLog.Debug(_declaringType, $"Parameter [{name}] specified subtype [{subType.FullName}]");
if (!propertyType.IsAssignableFrom(subType))
{
// Check if there is an appropriate type converter
if (OptionConverter.CanConvertTypeTo(subType, propertyType))
{
// Must re-convert to the real property type
parsedObjectConversionTargetType = propertyType;
// Use subtype as intermediary type
propertyType = subType;
}
else
{
LogLog.Error(_declaringType, $"subtype [{subType.FullName}] set on [{name}] is not a subclass of property type [{propertyType.FullName}] and there are no acceptable type conversions.");
}
}
else
{
// The subtype specified is found and is actually a subtype of the property
// type, therefore we can switch to using this type.
propertyType = subType;
}
}
catch (Exception e) when (!e.IsFatal())
{
LogLog.Error(_declaringType, $"Failed to find type [{subTypeString}] set on [{name}]", e);
}
}
// Now try to convert the string value to an acceptable type
// to pass to this property.
object? convertedValue = ConvertStringTo(propertyType, propertyValue);
// Check if we need to do an additional conversion
if (convertedValue is not null && parsedObjectConversionTargetType is not null)
{
LogLog.Debug(_declaringType, $"Performing additional conversion of value from [{convertedValue.GetType().Name}] to [{parsedObjectConversionTargetType.Name}]");
convertedValue = OptionConverter.ConvertTypeTo(convertedValue, parsedObjectConversionTargetType);
}
if (convertedValue is not null)
{
if (propInfo is not null)
{
// Got a converted result
LogLog.Debug(_declaringType, $"Setting Property [{propInfo.Name}] to {convertedValue.GetType().Name} value [{convertedValue}]");
try
{
// Pass to the property
propInfo.SetValue(target, convertedValue, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
}
catch (TargetInvocationException targetInvocationEx)
{
LogLog.Error(_declaringType, $"Failed to set parameter [{propInfo.Name}] on object [{target}] using value [{convertedValue}]", targetInvocationEx.InnerException);
}
}
else
{
// Got a converted result
LogLog.Debug(_declaringType, $"Setting Collection Property [{methInfo!.Name}] to {convertedValue.GetType().Name} value [{convertedValue}]");
try
{
// Pass to the property
methInfo.Invoke(target, BindingFlags.InvokeMethod, null, [convertedValue], CultureInfo.InvariantCulture);
}
catch (TargetInvocationException targetInvocationEx)
{
LogLog.Error(_declaringType, $"Failed to set parameter [{name}] on object [{target}] using value [{convertedValue}]", targetInvocationEx.InnerException);
}
}
}
else
{
LogLog.Warn(_declaringType, $"Unable to set property [{name}] on object [{target}] using value [{propertyValue}] (with acceptable conversion types)");
}
}
else
{
object? createdObject;
if (propertyType == typeof(string) && !HasAttributesOrElements(element))
{
// If the property is a string and the element is empty (no attributes
// or child elements) then we special case the object value to an empty string.
// This is necessary because while the String is a class it does not have
// a default constructor that creates an empty string, which is the behavior
// we are trying to simulate and would be expected from CreateObjectFromXml
createdObject = string.Empty;
}
else
{
// No value specified
Type? defaultObjectType = null;
if (IsTypeConstructible(propertyType))
{
defaultObjectType = propertyType;
}
createdObject = CreateObjectFromXml(element, defaultObjectType, propertyType);
}
if (createdObject is null)
{
LogLog.Error(_declaringType, $"Failed to create object to set param: {name}");
}
else
{
if (propInfo is not null)
{
// Got a converted result
LogLog.Debug(_declaringType, $"Setting Property [{propInfo.Name}] to object [{createdObject}]");
try
{
// Pass to the property
propInfo.SetValue(target, createdObject, BindingFlags.SetProperty, null, null, CultureInfo.InvariantCulture);
}
catch (TargetInvocationException targetInvocationEx)
{
LogLog.Error(_declaringType, $"Failed to set parameter [{propInfo.Name}] on object [{target}] using value [{createdObject}]", targetInvocationEx.InnerException);
}
}
else
{
// Got a converted result
LogLog.Debug(_declaringType, $"Setting Collection Property [{methInfo!.Name}] to object [{createdObject}]");
try
{
// Pass to the property
methInfo.Invoke(target, BindingFlags.InvokeMethod, null, [createdObject], CultureInfo.InvariantCulture);
}
catch (TargetInvocationException targetInvocationEx)
{
LogLog.Error(_declaringType, $"Failed to set parameter [{methInfo.Name}] on object [{target}] using value [{createdObject}]", targetInvocationEx.InnerException);
}
}
}
}
}
}
/// <summary>
/// Test if an element has no attributes or child elements
/// </summary>
/// <param name="element">the element to inspect</param>
/// <returns><c>true</c> if the element has any attributes or child elements, <c>false</c> otherwise</returns>
private static bool HasAttributesOrElements(XmlElement element)
=> element.ChildNodes.OfType<XmlNode>().Any(node => node.NodeType is XmlNodeType.Attribute or XmlNodeType.Element);
/// <summary>
/// Test if a <see cref="Type"/> is constructible with <c>Activator.CreateInstance</c>.
/// </summary>
/// <param name="type">the type to inspect</param>
/// <returns><c>true</c> if the type is creatable using a default constructor, <c>false</c> otherwise</returns>
private static bool IsTypeConstructible(Type type)
{
return type.IsClass && !type.IsAbstract
&& type.GetConstructor(Type.EmptyTypes) is ConstructorInfo defaultConstructor
&& !defaultConstructor.IsAbstract && !defaultConstructor.IsPrivate;
}
/// <summary>
/// Look for a method on the <paramref name="targetType"/> that matches the <paramref name="name"/> supplied
/// </summary>
/// <param name="targetType">the type that has the method</param>
/// <param name="name">the name of the method</param>
/// <returns>the method info found</returns>
/// <remarks>
/// <para>
/// The method must be a public instance method on the <paramref name="targetType"/>.
/// The method must be named <paramref name="name"/> or "Add" followed by <paramref name="name"/>.
/// The method must take a single parameter.
/// </para>
/// </remarks>
private static MethodInfo? FindMethodInfo(Type targetType, string name)
{
string requiredMethodNameA = name;
string requiredMethodNameB = "Add" + name;
MethodInfo[] methods = targetType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (MethodInfo methInfo in methods)
{
if (!methInfo.IsStatic)
{
string methodInfoName = methInfo.Name;
if (SystemInfo.EqualsIgnoringCase(methodInfoName, requiredMethodNameA) ||
SystemInfo.EqualsIgnoringCase(methodInfoName, requiredMethodNameB))
{
// Found matching method name
// Look for version with one arg only
ParameterInfo[] methParams = methInfo.GetParameters();
if (methParams.Length == 1)
{
return methInfo;
}
}
}
}
return null;
}
/// <summary>
/// Converts a string value to a target type.
/// </summary>
/// <param name="type">The type of object to convert the string to.</param>
/// <param name="value">The string value to use as the value of the object.</param>
/// <returns>
/// <para>
/// An object of type <paramref name="type"/> with value <paramref name="value"/> or
/// <c>null</c> when the conversion could not be performed.
/// </para>
/// </returns>
protected object? ConvertStringTo(Type type, string value)
{
// Hack to allow use of Level in property
if (typeof(Level) == type)
{
// Property wants a level
Level? levelValue = hierarchy.LevelMap[value];
if (levelValue is null)
{
LogLog.Error(_declaringType, $"XmlHierarchyConfigurator: Unknown Level Specified [{value}]");
}
return levelValue;
}
return OptionConverter.ConvertStringTo(type, value);
}
/// <summary>
/// Creates an object as specified in XML.
/// </summary>
/// <param name="element">The XML element that contains the definition of the object.</param>
/// <param name="defaultTargetType">The object type to use if not explicitly specified.</param>
/// <param name="typeConstraint">The type that the returned object must be or must inherit from.</param>
/// <returns>The object or <c>null</c></returns>
/// <remarks>
/// <para>
/// Parse an XML element and create an object instance based on the configuration
/// data.
/// </para>
/// <para>
/// The type of the instance may be specified in the XML. If not
/// specified then the <paramref name="defaultTargetType"/> is used
/// as the type. However the type is specified it must support the
/// <paramref name="typeConstraint"/> type.
/// </para>
/// </remarks>
protected object? CreateObjectFromXml(XmlElement element, Type? defaultTargetType, Type? typeConstraint)
{
Type? objectType;
// Get the object type
string objectTypeString = element.EnsureNotNull().GetAttribute(TypeAttr);
if (objectTypeString.Length == 0)
{
if (defaultTargetType is null)
{
LogLog.Error(_declaringType, $"Object type not specified. Cannot create object of type [{typeConstraint?.FullName}]. Missing Value or Type.");
return null;
}
// Use the default object type
objectType = defaultTargetType;
}
else
{
// Read the explicit object type
try
{
objectType = SystemInfo.GetTypeFromString(objectTypeString, true, true);
}
catch (Exception e) when (!e.IsFatal())
{
LogLog.Error(_declaringType, $"Failed to find type [{objectTypeString}]", e);
return null;
}
}
bool requiresConversion = false;
// Got the object type. Check that it meets the typeConstraint
if (typeConstraint is not null)
{
if (!typeConstraint.IsAssignableFrom(objectType))
{
// Check if there is an appropriate type converter
if (OptionConverter.CanConvertTypeTo(objectType, typeConstraint))
{
requiresConversion = true;
}
else
{
LogLog.Error(_declaringType, $"Object type [{objectType?.FullName}] is not assignable to type [{typeConstraint.FullName}]. There are no acceptable type conversions.");
return null;
}
}
}
// Create using the default constructor
object? createdObject;
try
{
createdObject = Activator.CreateInstance(objectType!).EnsureNotNull();
}
catch (Exception e) when (!e.IsFatal())
{
LogLog.Error(_declaringType, $"XmlHierarchyConfigurator: Failed to construct object of type [{objectType?.FullName}]", e);
return null;
}
// Set any params on object
foreach (XmlNode currentNode in element.ChildNodes)
{
if (currentNode.NodeType == XmlNodeType.Element)
{
SetParameter((XmlElement)currentNode, createdObject);
}
}
// Check if we need to call ActivateOptions
if (createdObject is IOptionHandler optionHandler)
{
optionHandler.ActivateOptions();
}
// Ok object should be initialized
if (requiresConversion)
{
// Convert the object type
return OptionConverter.ConvertTypeTo(createdObject, typeConstraint!);
}
// The object is of the correct type
return createdObject;
}
private static bool HasCaseInsensitiveEnvironment
{
get
{
PlatformID platform = Environment.OSVersion.Platform;
return platform is not PlatformID.Unix and not PlatformID.MacOSX;
}
}
private static Hashtable CreateCaseInsensitiveWrapper(IDictionary dict)
{
Hashtable hash = SystemInfo.CreateCaseInsensitiveHashtable();
foreach (DictionaryEntry entry in dict)
{
hash[entry.Key] = entry.Value;
}
return hash;
}
// String constants used while parsing the XML data
private const string ConfigurationTag = "log4net";
private const string RendererTag = "renderer";
private const string AppenderTag = "appender";
private const string AppenderRefTag = "appender-ref";
private const string ParamTag = "param";
// TODO: Deprecate use of category tags
private const string CategoryTag = "category";
// TODO: Deprecate use of priority tag
private const string PriorityTag = "priority";
private const string LoggerTag = "logger";
private const string NameAttr = "name";
private const string TypeAttr = "type";
private const string ValueAttr = "value";
private const string RootTag = "root";
private const string LevelTag = "level";
private const string RefAttr = "ref";
private const string AdditivityAttr = "additivity";
private const string ThresholdAttr = "threshold";
private const string ConfigDebugAttr = "configDebug";
private const string InternalDebugAttr = "debug";
private const string EmitInternalDebugAttr = "emitDebug";
private const string ConfigUpdateModeAttr = "update";
private const string RenderingTypeAttr = "renderingClass";
private const string RenderedTypeAttr = "renderedClass";
// flag used on the level element
private const string Inherited = "inherited";
/// <summary>
/// key: appenderName, value: appender.
/// </summary>
private readonly Dictionary<string, IAppender> _appenderBag = new(StringComparer.Ordinal);
/// <summary>
/// The fully qualified type of the XmlHierarchyConfigurator class.
/// </summary>
/// <remarks>
/// Used by the internal logger to record the Type of the
/// log message.
/// </remarks>
private static readonly Type _declaringType = typeof(XmlHierarchyConfigurator);
}