src/log4net/Repository/Hierarchy/XmlHierarchyConfigurator.cs (670 lines of code) (raw):

#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 System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Xml; using log4net.Appender; using log4net.Util; using log4net.Core; using log4net.ObjectRenderer; using System.Linq; namespace log4net.Repository.Hierarchy; /// <summary> /// Initializes the log4net environment using an XML DOM. /// </summary> /// <param name="hierarchy">The hierarchy to build.</param> /// <author>Nicko Cadell</author> /// <author>Gert Driesen</author> 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 &lt;logger&gt; 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); }