From 342fc55a228e94700b25414a502cd5394b9efbc8 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Mon, 9 May 2011 13:51:59 -0700 Subject: [PATCH] #17774: Rolling files when locked. Removing unecessary custom XMLConfigurators, reconfiguring and monitoring files in the logger factory only under full trust. --HG-- branch : 1.x --- .../Logging/OrchardFileAppenderTests.cs | 55 ++ .../Orchard.Framework.Tests.csproj | 1 + src/Orchard.Web/Config/log4net.config | 6 +- src/Orchard/Logging/OrchardFileAppender.cs | 62 +- src/Orchard/Logging/OrchardLog4netFactory.cs | 42 +- src/Orchard/Logging/OrchardXmlConfigurator.cs | 308 ------- .../OrchardXmlHierarchyConfigurator.cs | 858 ------------------ src/Orchard/Orchard.Framework.csproj | 2 - 8 files changed, 109 insertions(+), 1225 deletions(-) create mode 100644 src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs delete mode 100644 src/Orchard/Logging/OrchardXmlConfigurator.cs delete mode 100644 src/Orchard/Logging/OrchardXmlHierarchyConfigurator.cs diff --git a/src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs b/src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs new file mode 100644 index 000000000..1e429e208 --- /dev/null +++ b/src/Orchard.Tests/Logging/OrchardFileAppenderTests.cs @@ -0,0 +1,55 @@ +using System; +using Moq; +using Moq.Protected; +using NUnit.Framework; +using Orchard.Logging; + +namespace Orchard.Tests.Logging { + [TestFixture] + public class OrchardFileAppenderTests { + [Test] + public void AddSuffixTest() { + const string filename = "Orchard-debug"; + const string filenameAlternative1 = "Orchard-debug-1"; + const string filenameAlternative2 = "Orchard-debug-2"; + + string filenameUsed = string.Empty; + + Mock firstOrchardFileAppenderMock = new Mock(); + StubOrchardFileAppender firstOrchardFileAppender = firstOrchardFileAppenderMock.Object; + + // Regular filename should be used if nothing is locked + firstOrchardFileAppenderMock.Protected().Setup("BaseOpenFile", ItExpr.Is(file => file.Equals(filename)), ItExpr.IsAny()).Callback( + (file, append) => filenameUsed = file); + + firstOrchardFileAppender.OpenFileStub(filename, true); + + Assert.That(filenameUsed, Is.EqualTo(filename)); + + // Alternative 1 should be used if regular filename is locked + + firstOrchardFileAppenderMock.Protected().Setup("BaseOpenFile", ItExpr.Is(file => file.Equals(filename)), ItExpr.IsAny()).Throws(new Exception()); + firstOrchardFileAppenderMock.Protected().Setup("BaseOpenFile", ItExpr.Is(file => file.Equals(filenameAlternative1)), ItExpr.IsAny()).Callback( + (file, append) => filenameUsed = file); + + firstOrchardFileAppender.OpenFileStub(filename, true); + + Assert.That(filenameUsed, Is.EqualTo(filenameAlternative1)); + + // make alternative 1 also throw exception to make sure alternative 2 is used. + firstOrchardFileAppenderMock.Protected().Setup("BaseOpenFile", ItExpr.Is(file => file.Equals(filenameAlternative1)), ItExpr.IsAny()).Throws(new Exception()); + firstOrchardFileAppenderMock.Protected().Setup("BaseOpenFile", ItExpr.Is(file => file.Equals(filenameAlternative2)), ItExpr.IsAny()).Callback( + (file, append) => filenameUsed = file); + + firstOrchardFileAppender.OpenFileStub(filename, true); + + Assert.That(filenameUsed, Is.EqualTo(filenameAlternative2)); + } + + public class StubOrchardFileAppender : OrchardFileAppender { + public void OpenFileStub(string fileName, bool append) { + base.OpenFile(fileName, append); + } + } + } +} diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index 4027f9a8b..5acf3f855 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -255,6 +255,7 @@ + diff --git a/src/Orchard.Web/Config/log4net.config b/src/Orchard.Web/Config/log4net.config index 29afd8b06..8b57d7da7 100644 --- a/src/Orchard.Web/Config/log4net.config +++ b/src/Orchard.Web/Config/log4net.config @@ -53,8 +53,7 @@ - - + @@ -72,8 +71,7 @@ - - + diff --git a/src/Orchard/Logging/OrchardFileAppender.cs b/src/Orchard/Logging/OrchardFileAppender.cs index eb1429fa4..ddde96623 100644 --- a/src/Orchard/Logging/OrchardFileAppender.cs +++ b/src/Orchard/Logging/OrchardFileAppender.cs @@ -1,27 +1,51 @@ -using log4net.Appender; -using log4net.Util; +using System.Collections.Generic; +using log4net.Appender; namespace Orchard.Logging { public class OrchardFileAppender : RollingFileAppender { - public enum RollingStyleFrequencyMode { - Once, - Continuous + /// + /// Dictionary of already known suffixes (based on previous attempts) for a given filename. + /// + private static readonly Dictionary _suffixes = new Dictionary(); + + private const int Retries = 50; + + /// + /// Opens the log file adding an incremental suffix to the filename if required due to an openning failure (usually, locking). + /// + /// The filename as specified in the configuration file. + /// Boolean flag indicating weather the log file should be appended if it already exists. + protected override void OpenFile(string fileName, bool append) { + lock (this) { + bool fileOpened = false; + string currentFilename = fileName; + + if (!_suffixes.ContainsKey(fileName)) { + _suffixes[fileName] = 0; + } + + int newSuffix = _suffixes[fileName]; + + for (int i = 1; !fileOpened && i <= Retries; i++) { + try { + if (newSuffix > 0) { + currentFilename = string.Format("{0}-{1}", fileName, newSuffix); + } + + BaseOpenFile(currentFilename, append); + + fileOpened = true; + } catch { + newSuffix = _suffixes[fileName] + i; + } + } + + _suffixes[fileName] = newSuffix; + } } - public RollingStyleFrequencyMode RollingStyleFrequency { get; set; } - - public bool RollSize { - get { return (RollingStyle == RollingMode.Composite || RollingStyle == RollingMode.Size); } - } - - protected override void AdjustFileBeforeAppend() { - if (RollingStyle == RollingMode.Size || - RollingStyleFrequency == RollingStyleFrequencyMode.Continuous) { - base.AdjustFileBeforeAppend(); - } - else if (RollSize && ((File != null) && (((CountingQuietTextWriter) QuietWriter).Count >= MaxFileSize))) { - RollOverSize(); - } + protected virtual void BaseOpenFile(string fileName, bool append) { + base.OpenFile(fileName, append); } } } diff --git a/src/Orchard/Logging/OrchardLog4netFactory.cs b/src/Orchard/Logging/OrchardLog4netFactory.cs index 4a5dd45d4..043651b00 100644 --- a/src/Orchard/Logging/OrchardLog4netFactory.cs +++ b/src/Orchard/Logging/OrchardLog4netFactory.cs @@ -1,48 +1,22 @@ using System; using System.Configuration; -using System.IO; -using System.Web; -using System.Web.Hosting; - using Castle.Core.Logging; - using log4net; +using log4net.Config; +using Orchard.Environment; namespace Orchard.Logging { public class OrchardLog4netFactory : AbstractLoggerFactory { - public OrchardLog4netFactory() - : this(ConfigurationManager.AppSettings["log4net.Config"]) { - } + public OrchardLog4netFactory(IHostEnvironment hostEnvironment) + : this(ConfigurationManager.AppSettings["log4net.Config"], hostEnvironment) { } - public OrchardLog4netFactory(String configFilename) { - if (!String.IsNullOrWhiteSpace(configFilename)) { - var mappedConfigFilename = configFilename; - - if (HostingEnvironment.IsHosted) { - if (!VirtualPathUtility.IsAppRelative(mappedConfigFilename)) { - if (!mappedConfigFilename.StartsWith("/")) { - mappedConfigFilename = "~/" + mappedConfigFilename; - } - else { - mappedConfigFilename = "~" + mappedConfigFilename; - } - } - - mappedConfigFilename = HostingEnvironment.MapPath(mappedConfigFilename); - } - - OrchardXmlConfigurator.Configure(mappedConfigFilename); + public OrchardLog4netFactory(string configFilename, IHostEnvironment hostEnvironment) { + if (hostEnvironment.IsFullTrust) { + // Only monitor configuration file in full trust + XmlConfigurator.ConfigureAndWatch(GetConfigFile(configFilename)); } } - /// - /// Configures log4net with a stream containing XML. - /// - /// - public OrchardLog4netFactory(Stream config) { - OrchardXmlConfigurator.Configure(config); - } - public override Castle.Core.Logging.ILogger Create(string name, LoggerLevel level) { throw new NotSupportedException("Logger levels cannot be set at runtime. Please review your configuration file."); } diff --git a/src/Orchard/Logging/OrchardXmlConfigurator.cs b/src/Orchard/Logging/OrchardXmlConfigurator.cs deleted file mode 100644 index 965f389f9..000000000 --- a/src/Orchard/Logging/OrchardXmlConfigurator.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -using System.Xml; - -using log4net; -using log4net.Repository; -using log4net.Repository.Hierarchy; -using log4net.Util; - -namespace Orchard.Logging { - public class OrchardXmlConfigurator { - - /// - /// Private constructor - /// - private OrchardXmlConfigurator() { - } - - /// - /// Configures log4net using the specified configuration file. - /// - /// The name of the XML file to load the configuration from. - /// - /// - /// The configuration file must be valid XML. It must contain - /// at least one element called log4net that holds - /// the log4net configuration data. - /// - /// - /// The log4net configuration file can possible be specified in the application's - /// configuration file (either MyAppName.exe.config for a - /// normal application on Web.config for an ASP.NET application). - /// - /// - /// The first element matching <configuration> will be read as the - /// configuration. If this file is also a .NET .config file then you must specify - /// a configuration section for the log4net element otherwise .NET will - /// complain. Set the type for the section handler to , for example: - /// - /// - ///
- /// - /// - /// - /// - /// The following example configures log4net using a configuration file, of which the - /// location is stored in the application's configuration file : - /// - /// - /// using log4net.Config; - /// using System.IO; - /// using System.Configuration; - /// - /// ... - /// - /// XmlConfigurator.Configure(ConfigurationSettings.AppSettings["log4net-config-file"]); - /// - /// - /// In the .config file, the path to the log4net can be specified like this : - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static void Configure(string configFilename) { - Configure(LogManager.GetRepository(Assembly.GetCallingAssembly()), configFilename); - } - - /// - /// Configures log4net using the specified configuration data stream. - /// - /// A stream to load the XML configuration from. - /// - /// - /// The configuration data must be valid XML. It must contain - /// at least one element called log4net that holds - /// the log4net configuration data. - /// - /// - /// Note that this method will NOT close the stream parameter. - /// - /// - public static void Configure(Stream configStream) { - Configure(LogManager.GetRepository(Assembly.GetCallingAssembly()), configStream); - } - - /// - /// Configures the using the specified configuration - /// file. - /// - /// The repository to configure. - /// The name of the XML file to load the configuration from. - /// - /// - /// The configuration file must be valid XML. It must contain - /// at least one element called log4net that holds - /// the configuration data. - /// - /// - /// The log4net configuration file can possible be specified in the application's - /// configuration file (either MyAppName.exe.config for a - /// normal application on Web.config for an ASP.NET application). - /// - /// - /// The first element matching <configuration> will be read as the - /// configuration. If this file is also a .NET .config file then you must specify - /// a configuration section for the log4net element otherwise .NET will - /// complain. Set the type for the section handler to , for example: - /// - /// - ///
- /// - /// - /// - /// - /// The following example configures log4net using a configuration file, of which the - /// location is stored in the application's configuration file : - /// - /// - /// using log4net.Config; - /// using System.IO; - /// using System.Configuration; - /// - /// ... - /// - /// XmlConfigurator.Configure(ConfigurationSettings.AppSettings["log4net-config-file"]); - /// - /// - /// In the .config file, the path to the log4net can be specified like this : - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static void Configure(ILoggerRepository repository, string configFilename) { - LogLog.Debug("XmlConfigurator: configuring repository [" + repository.Name + "] using file [" + configFilename + "]"); - - if (String.IsNullOrWhiteSpace(configFilename)) { - LogLog.Error("XmlConfigurator: Configure called with null 'configFilename' parameter"); - } - else { - // Have to use File.Exists() rather than configFile.Exists() - // because configFile.Exists() caches the value, not what we want. - if (File.Exists(configFilename)) { - // Open the file for reading - FileStream fs = null; - - // Try hard to open the file - for (var retry = 5; --retry >= 0; ) { - try { - fs = new FileStream(configFilename, FileMode.Open, FileAccess.Read, FileShare.Read); - break; - } - catch (IOException ex) { - if (retry == 0) { - LogLog.Error("XmlConfigurator: Failed to open XML config file [" + configFilename + "]", ex); - - // The stream cannot be valid - fs = null; - } - - System.Threading.Thread.Sleep(250); - } - } - - if (fs != null) { - try { - // Load the configuration from the stream - Configure(repository, fs); - } - finally { - // Force the file closed whatever happens - fs.Close(); - } - } - } - else { - LogLog.Debug("XmlConfigurator: config file [" + configFilename + "] not found. Configuration unchanged."); - } - } - } - - /// - /// Configures the using the specified configuration - /// file. - /// - /// The repository to configure. - /// The stream to load the XML configuration from. - /// - /// - /// The configuration data must be valid XML. It must contain - /// at least one element called log4net that holds - /// the configuration data. - /// - /// - /// Note that this method will NOT close the stream parameter. - /// - /// - public static void Configure(ILoggerRepository repository, Stream configStream) { - LogLog.Debug("XmlConfigurator: configuring repository [" + repository.Name + "] using stream"); - - if (configStream == null) { - LogLog.Error("XmlConfigurator: Configure called with null 'configStream' parameter"); - } - else { - // Load the config file into a document - var doc = new XmlDocument(); - try { - // Create a text reader for the file stream - var xmlReader = new XmlTextReader(configStream) { DtdProcessing = DtdProcessing.Parse }; - - // Specify that the reader should not perform validation - var settings = new XmlReaderSettings { ValidationType = ValidationType.None }; - - // load the data into the document - doc.Load(xmlReader); - } - catch (Exception ex) { - LogLog.Error("XmlConfigurator: Error while loading XML configuration", ex); - - // The document is invalid - doc = null; - } - - if (doc != null) { - LogLog.Debug("XmlConfigurator: loading XML configuration"); - - // Configure using the 'log4net' element - var configNodeList = doc.GetElementsByTagName("log4net"); - if (configNodeList.Count == 0) { - LogLog.Debug("XmlConfigurator: XML configuration does not contain a element. Configuration Aborted."); - } - else if (configNodeList.Count > 1) { - LogLog.Error("XmlConfigurator: XML configuration contains [" + configNodeList.Count + "] elements. Only one is allowed. Configuration Aborted."); - } - else { - ConfigureFromXml(repository, configNodeList[0] as XmlElement); - } - } - } - } - - /// - /// Configures the specified repository using a log4net element. - /// - /// The hierarchy to configure. - /// The element to parse. - /// - /// - /// Loads the log4net configuration from the XML element - /// supplied as . - /// - /// - /// This method is ultimately called by one of the Configure methods - /// to load the configuration from an . - /// - /// - private static void ConfigureFromXml(ILoggerRepository repository, XmlElement element) { - if (element == null) { - LogLog.Error("XmlConfigurator: ConfigureFromXml called with null 'element' parameter"); - } - else if (repository == null) { - LogLog.Error("XmlConfigurator: ConfigureFromXml called with null 'repository' parameter"); - } - else { - LogLog.Debug("XmlConfigurator: Configuring Repository [" + repository.Name + "]"); - - // - // Since we're not reinventing the whole Hierarchy class from log4net to add out optimizations to XmlRepositoryConfigurator - // we've to check the neccessary casts here to be able to complete the configuration. - // - - // Needed to fire configuration changed event - var repositorySkeleton = repository as LoggerRepositorySkeleton; - - // Needed to XmlHierarchyConfigurator - var hierarchy = repository as Hierarchy; - - if (repositorySkeleton == null || hierarchy == null) { - LogLog.Warn("XmlConfigurator: Repository [" + repository + "] does not support the XmlConfigurator"); - } - else { - var configurator = new OrchardXmlHierarchyConfigurator(hierarchy); - - // Copy the xml data into the root of a new document - // this isolates the xml config data from the rest of - // the document - var newDoc = new XmlDocument(); - var newElement = (XmlElement)newDoc.AppendChild(newDoc.ImportNode(element, true)); - - // Pass the configurator the config element - configurator.Configure(newElement); - - repositorySkeleton.RaiseConfigurationChanged(EventArgs.Empty); - } - } - } - } -} diff --git a/src/Orchard/Logging/OrchardXmlHierarchyConfigurator.cs b/src/Orchard/Logging/OrchardXmlHierarchyConfigurator.cs deleted file mode 100644 index 8b4370432..000000000 --- a/src/Orchard/Logging/OrchardXmlHierarchyConfigurator.cs +++ /dev/null @@ -1,858 +0,0 @@ -using System; -using System.Collections; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Xml; -using log4net.Appender; -using log4net.Core; -using log4net.ObjectRenderer; -using log4net.Repository.Hierarchy; -using log4net.Util; - -namespace Orchard.Logging { - /// - /// Initializes the log4net environment using an XML DOM. - /// - /// - /// - /// Configures a using an XML DOM. - /// - /// - /// Nicko Cadell - /// Gert Driesen - public class OrchardXmlHierarchyConfigurator { - private enum ConfigUpdateMode { - Merge, - Overwrite - } - - // 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 ConfigUpdateModeAttr = "update"; - private const string RenderingTypeAttr = "renderingClass"; - private const string RenderedTypeAttr = "renderedClass"; - - // flag used on the level element - private const string Inherited = "inherited"; - - /// - /// key: appenderName, value: appender. - /// - private Hashtable _appenderBag; - - /// - /// The Hierarchy being configured. - /// - private readonly Hierarchy _hierarchy; - - /// - /// The snapshot of the environment variables at configuration time, or null if an error has occured during querying them. - /// - private IDictionary _environmentVariables; - - /// - /// Construct the configurator for a hierarchy - /// - /// The hierarchy to build. - /// - /// - /// Initializes a new instance of the class - /// with the specified . - /// - /// - public OrchardXmlHierarchyConfigurator(Hierarchy hierarchy) { - _hierarchy = hierarchy; - _appenderBag = new Hashtable(); - } - - /// - /// Configure the hierarchy by parsing a DOM tree of XML elements. - /// - /// The root element to parse. - /// - /// - /// Configure the hierarchy by parsing a DOM tree of XML elements. - /// - /// - public void Configure(XmlElement element) { - if (element == null || _hierarchy == null) { - return; - } - - var rootElementName = element.LocalName; - - if (rootElementName != ConfigurationTag) { - LogLog.Error("XmlHierarchyConfigurator: Xml element is - not a <" + ConfigurationTag + "> element."); - return; - } - - if (!LogLog.InternalDebugging) { - // Look for a debug attribute to enable internal debug - var debugAttribute = element.GetAttribute(InternalDebugAttr); - LogLog.Debug("XmlHierarchyConfigurator: " + InternalDebugAttr + " attribute [" + debugAttribute + "]."); - - if (debugAttribute.Length > 0 && debugAttribute != "null") { - LogLog.InternalDebugging = OptionConverter.ToBoolean(debugAttribute, true); - } - else { - LogLog.Debug("XmlHierarchyConfigurator: Ignoring " + InternalDebugAttr + " attribute."); - } - - var confDebug = element.GetAttribute(ConfigDebugAttr); - if (confDebug.Length > 0 && confDebug != "null") { - LogLog.Warn("XmlHierarchyConfigurator: The \"" + ConfigDebugAttr + "\" attribute is deprecated."); - LogLog.Warn("XmlHierarchyConfigurator: Use the \"" + InternalDebugAttr + "\" attribute instead."); - LogLog.InternalDebugging = OptionConverter.ToBoolean(confDebug, true); - } - } - - // Default mode is merge - var configUpdateMode = ConfigUpdateMode.Merge; - - // Look for the config update attribute - var configUpdateModeAttribute = element.GetAttribute(ConfigUpdateModeAttr); - if (!String.IsNullOrEmpty(configUpdateModeAttribute)) { - // Parse the attribute - try { - configUpdateMode = (ConfigUpdateMode)OptionConverter.ConvertStringTo(typeof(ConfigUpdateMode), configUpdateModeAttribute); - } - catch { - LogLog.Error("XmlHierarchyConfigurator: Invalid " + ConfigUpdateModeAttr + " attribute value [" + configUpdateModeAttribute + "]"); - } - } - - // IMPL: The IFormatProvider argument to Enum.ToString() is deprecated in .NET 2.0 - LogLog.Debug("XmlHierarchyConfigurator: Configuration update mode [" + configUpdateMode + "]."); - - // Only reset configuration if overwrite flag specified - if (configUpdateMode == ConfigUpdateMode.Overwrite) { - // Reset to original unset configuration - _hierarchy.ResetConfiguration(); - LogLog.Debug("XmlHierarchyConfigurator: Configuration reset before reading config."); - } - - // The Log4netFactory/OrchardXmlConfigurator/OrchardXmlHierarchyConfigurator can be refactored later - // so we call HostEnvironment.IsFullTrust - if (AppDomain.CurrentDomain.IsHomogenous && AppDomain.CurrentDomain.IsFullyTrusted) - _environmentVariables = System.Environment.GetEnvironmentVariables(); - else { - _environmentVariables = null; - } - - /* 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) { - var 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("XmlHierarchyConfigurator: Hierarchy Threshold [" + thresholdStr + "]"); - if (thresholdStr.Length > 0 && thresholdStr != "null") { - var thresholdLevel = (Level)ConvertStringTo(typeof(Level), thresholdStr); - if (thresholdLevel != null) { - _hierarchy.Threshold = thresholdLevel; - } - else { - LogLog.Warn("XmlHierarchyConfigurator: Unable to set hierarchy threshold using value [" + thresholdStr + "] (with acceptable conversion types)"); - } - } - - // Done reading config - } - - /// - /// Parse appenders by IDREF. - /// - /// The appender ref element. - /// The instance of the appender that the ref refers to. - /// - /// - /// Parse an XML element that represents an appender and return - /// the appender. - /// - /// - protected IAppender FindAppenderByReference(XmlElement appenderRef) { - var appenderName = appenderRef.GetAttribute(RefAttr); - - IAppender appender = (IAppender)_appenderBag[appenderName]; - if (appender != null) { - return appender; - } - // Find the element with that id - XmlElement element = null; - - if (!String.IsNullOrEmpty(appenderName)) { - foreach (XmlElement curAppenderElement in appenderRef.OwnerDocument.GetElementsByTagName(AppenderTag)) { - if (curAppenderElement.GetAttribute("name") != appenderName) { - continue; - } - element = curAppenderElement; - break; - } - } - - if (element == null) { - LogLog.Error("XmlHierarchyConfigurator: No appender named [" + appenderName + "] could be found."); - return null; - } - appender = ParseAppender(element); - if (appender != null) { - _appenderBag[appenderName] = appender; - } - return appender; - } - - /// - /// Parses an appender element. - /// - /// The appender element. - /// The appender instance or null when parsing failed. - /// - /// - /// Parse an XML element that represents an appender and return - /// the appender instance. - /// - /// - protected IAppender ParseAppender(XmlElement appenderElement) { - var appenderName = appenderElement.GetAttribute(NameAttr); - var typeName = appenderElement.GetAttribute(TypeAttr); - - LogLog.Debug("XmlHierarchyConfigurator: Loading Appender [" + appenderName + "] type: [" + typeName + "]"); - try { - var appender = (IAppender)Activator.CreateInstance(SystemInfo.GetTypeFromString(typeName, true, true)); - appender.Name = appenderName; - - foreach (XmlNode currentNode in appenderElement.ChildNodes) { - /* We're only interested in Elements */ - if (currentNode.NodeType == XmlNodeType.Element) { - var currentElement = (XmlElement)currentNode; - - // Look for the appender ref tag - if (currentElement.LocalName == AppenderRefTag) { - var refName = currentElement.GetAttribute(RefAttr); - - var appenderContainer = appender as IAppenderAttachable; - if (appenderContainer != null) { - LogLog.Debug("XmlHierarchyConfigurator: Attaching appender named [" + refName + "] to appender named [" + appender.Name + "]."); - - var referencedAppender = FindAppenderByReference(currentElement); - if (referencedAppender != null) { - appenderContainer.AddAppender(referencedAppender); - } - } - else { - LogLog.Error("XmlHierarchyConfigurator: 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); - } - } - } - - var optionHandler = appender as IOptionHandler; - if (optionHandler != null) { - optionHandler.ActivateOptions(); - } - - LogLog.Debug("XmlHierarchyConfigurator: Created Appender [" + appenderName + "]"); - return appender; - } - catch (Exception ex) { - // Yes, it's ugly. But all exceptions point to the same problem: we can't create an Appender - - LogLog.Error("XmlHierarchyConfigurator: Could not create Appender [" + appenderName + "] of type [" + typeName + "]. Reported error follows.", ex); - return null; - } - } - - /// - /// Parses a logger element. - /// - /// The logger element. - /// - /// - /// Parse an XML element that represents a logger. - /// - /// - protected void ParseLogger(XmlElement loggerElement) { - // Create a new log4net.Logger object from the element. - var loggerName = loggerElement.GetAttribute(NameAttr); - - LogLog.Debug("XmlHierarchyConfigurator: Retrieving an instance of log4net.Repository.Logger for logger [" + loggerName + "]."); - var log = _hierarchy.GetLogger(loggerName) as Logger; - - // Setting up a logger needs to be an atomic operation, in order - // to protect potential log operations while logger - // configuration is in progress. - if (log == null) { - return; - } - lock (log) { - var additivity = OptionConverter.ToBoolean(loggerElement.GetAttribute(AdditivityAttr), true); - - LogLog.Debug("XmlHierarchyConfigurator: Setting [" + log.Name + "] additivity to [" + additivity + "]."); - log.Additivity = additivity; - ParseChildrenOfLoggerElement(loggerElement, log, false); - } - } - - /// - /// Parses the root logger element. - /// - /// The root element. - /// - /// - /// Parse an XML element that represents the root logger. - /// - /// - protected void ParseRoot(XmlElement rootElement) { - var root = _hierarchy.Root; - // logger configuration needs to be atomic - lock (root) { - ParseChildrenOfLoggerElement(rootElement, root, true); - } - } - - /// - /// Parses the children of a logger element. - /// - /// The category element. - /// The logger instance. - /// Flag to indicate if the logger is the root logger. - /// - /// - /// Parse the child elements of a <logger> element. - /// - /// - protected void ParseChildrenOfLoggerElement(XmlElement catElement, Logger log, bool isRoot) { - // Remove all existing appenders from log. They will be - // reconstructed if need be. - log.RemoveAllAppenders(); - - foreach (XmlNode currentNode in catElement.ChildNodes) { - if (currentNode.NodeType == XmlNodeType.Element) { - var currentElement = (XmlElement)currentNode; - - if (currentElement.LocalName == AppenderRefTag) { - var appender = FindAppenderByReference(currentElement); - var refName = currentElement.GetAttribute(RefAttr); - if (appender != null) { - LogLog.Debug("XmlHierarchyConfigurator: Adding appender named [" + refName + "] to logger [" + log.Name + "]."); - log.AddAppender(appender); - } - else { - LogLog.Error("XmlHierarchyConfigurator: Appender named [" + refName + "] not found."); - } - } - else if (currentElement.LocalName == LevelTag || currentElement.LocalName == PriorityTag) { - ParseLevel(currentElement, log, isRoot); - } - else { - SetParameter(currentElement, log); - } - } - } - - var optionHandler = log as IOptionHandler; - if (optionHandler != null) { - optionHandler.ActivateOptions(); - } - } - - /// - /// Parses an object renderer. - /// - /// The renderer element. - /// - /// - /// Parse an XML element that represents a renderer. - /// - /// - protected void ParseRenderer(XmlElement element) { - var renderingClassName = element.GetAttribute(RenderingTypeAttr); - var renderedClassName = element.GetAttribute(RenderedTypeAttr); - - LogLog.Debug("XmlHierarchyConfigurator: Rendering class [" + renderingClassName + "], Rendered class [" + renderedClassName + "]."); - var renderer = (IObjectRenderer)OptionConverter.InstantiateByClassName(renderingClassName, typeof(IObjectRenderer), null); - if (renderer == null) { - LogLog.Error("XmlHierarchyConfigurator: Could not instantiate renderer [" + renderingClassName + "]."); - return; - } - try { - _hierarchy.RendererMap.Put(SystemInfo.GetTypeFromString(renderedClassName, true, true), renderer); - } - catch (Exception e) { - LogLog.Error("XmlHierarchyConfigurator: Could not find class [" + renderedClassName + "].", e); - } - } - - /// - /// Parses a level element. - /// - /// The level element. - /// The logger object to set the level on. - /// Flag to indicate if the logger is the root logger. - /// - /// - /// Parse an XML element that represents a level. - /// - /// - protected void ParseLevel(XmlElement element, Logger log, bool isRoot) { - var loggerName = log.Name; - if (isRoot) { - loggerName = "root"; - } - - var levelStr = element.GetAttribute(ValueAttr); - LogLog.Debug("XmlHierarchyConfigurator: Logger [" + loggerName + "] Level string is [" + levelStr + "]."); - - if (Inherited == levelStr) { - if (isRoot) { - LogLog.Error("XmlHierarchyConfigurator: Root level cannot be inherited. Ignoring directive."); - } - else { - LogLog.Debug("XmlHierarchyConfigurator: Logger [" + loggerName + "] level set to inherit from parent."); - log.Level = null; - } - } - else { - log.Level = log.Hierarchy.LevelMap[levelStr]; - if (log.Level == null) { - LogLog.Error("XmlHierarchyConfigurator: Undefined level [" + levelStr + "] on Logger [" + loggerName + "]."); - } - else { - LogLog.Debug("XmlHierarchyConfigurator: Logger [" + loggerName + "] level set to [name=\"" + log.Level.Name + "\",value=" + log.Level.Value + "]."); - } - } - } - - /// - /// Sets a parameter on an object. - /// - /// The parameter element. - /// The object to set the parameter on. - /// - /// 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 Parse 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. - /// - protected void SetParameter(XmlElement element, object target) { - // Get the property name - var name = element.GetAttribute(NameAttr); - - // If the name attribute does not exist then use the name of the element - if (element.LocalName != ParamTag || String.IsNullOrEmpty(name)) { - name = element.LocalName; - } - - // Look for the property on the target object - var targetType = target.GetType(); - Type propertyType = null; - - MethodInfo methInfo = null; - - // Try to find a writable property - var propInfo = targetType.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase); - if (propInfo != null && propInfo.CanWrite) { - // found a property - propertyType = propInfo.PropertyType; - } - else { - propInfo = null; - - // look for a method with the signature Add(type) - methInfo = FindMethodInfo(targetType, name); - - if (methInfo != null) { - propertyType = methInfo.GetParameters()[0].ParameterType; - } - } - - if (propertyType == null) { - LogLog.Error("XmlHierarchyConfigurator: Cannot find Property [" + name + "] to set object on [" + target + "]"); - } - else { - string propertyValue = null; - - if (element.GetAttributeNode(ValueAttr) != 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 == XmlNodeType.CDATA || childNode.NodeType == XmlNodeType.Text) { - if (propertyValue == null) { - propertyValue = childNode.InnerText; - } - else { - propertyValue += childNode.InnerText; - } - } - } - } - - if (propertyValue != null) { - if (_environmentVariables != null) { - // Expand environment variables in the string. - propertyValue = OptionConverter.SubstituteVariables(propertyValue, _environmentVariables); - } - - Type parsedObjectConversionTargetType = null; - - // Check if a specific subtype is specified on the element using the 'type' attribute - var subTypeString = element.GetAttribute(TypeAttr); - if (!String.IsNullOrEmpty(subTypeString)) { - // Read the explicit subtype - try { - var subType = SystemInfo.GetTypeFromString(subTypeString, true, true); - - LogLog.Debug("XmlHierarchyConfigurator: 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 sub type as intermediary type - propertyType = subType; - } - else { - LogLog.Error("XmlHierarchyConfigurator: 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 ex) { - LogLog.Error("XmlHierarchyConfigurator: Failed to find type [" + subTypeString + "] set on [" + name + "]", ex); - } - } - - // Now try to convert the string value to an acceptable type - // to pass to this property. - - var convertedValue = ConvertStringTo(propertyType, propertyValue); - - // Check if we need to do an additional conversion - if (convertedValue != null && parsedObjectConversionTargetType != null) { - LogLog.Debug("XmlHierarchyConfigurator: Performing additional conversion of value from [" + convertedValue.GetType().Name + "] to [" + parsedObjectConversionTargetType.Name + "]"); - convertedValue = OptionConverter.ConvertTypeTo(convertedValue, parsedObjectConversionTargetType); - } - - if (convertedValue != null) { - if (propInfo != null) { - // Got a converted result - LogLog.Debug("XmlHierarchyConfigurator: 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("XmlHierarchyConfigurator: Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException); - } - } - else if (methInfo != null) { - // Got a converted result - LogLog.Debug("XmlHierarchyConfigurator: Setting Collection Property [" + methInfo.Name + "] to " + convertedValue.GetType().Name + " value [" + convertedValue + "]"); - - try { - // Pass to the property - methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new[] { convertedValue }, CultureInfo.InvariantCulture); - } - catch (TargetInvocationException targetInvocationEx) { - LogLog.Error("XmlHierarchyConfigurator: Failed to set parameter [" + name + "] on object [" + target + "] using value [" + convertedValue + "]", targetInvocationEx.InnerException); - } - } - } - else { - LogLog.Warn("XmlHierarchyConfigurator: 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 = ""; - } - else { - // No value specified - Type defaultObjectType = null; - if (IsTypeConstructible(propertyType)) { - defaultObjectType = propertyType; - } - - createdObject = CreateObjectFromXml(element, defaultObjectType, propertyType); - } - - if (createdObject == null) { - LogLog.Error("XmlHierarchyConfigurator: Failed to create object to set param: " + name); - } - else { - if (propInfo != null) { - // Got a converted result - LogLog.Debug("XmlHierarchyConfigurator: 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("XmlHierarchyConfigurator: Failed to set parameter [" + propInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException); - } - } - else if (methInfo != null) { - // Got a converted result - LogLog.Debug("XmlHierarchyConfigurator: Setting Collection Property [" + methInfo.Name + "] to object [" + createdObject + "]"); - - try { - // Pass to the property - methInfo.Invoke(target, BindingFlags.InvokeMethod, null, new[] { createdObject }, CultureInfo.InvariantCulture); - } - catch (TargetInvocationException targetInvocationEx) { - LogLog.Error("XmlHierarchyConfigurator: Failed to set parameter [" + methInfo.Name + "] on object [" + target + "] using value [" + createdObject + "]", targetInvocationEx.InnerException); - } - } - } - } - } - } - - /// - /// Test if an element has no attributes or child elements - /// - /// the element to inspect - /// true if the element has any attributes or child elements, false otherwise - private static bool HasAttributesOrElements(XmlElement element) { - return element.ChildNodes.Cast().Any(node => node.NodeType == XmlNodeType.Attribute || node.NodeType == XmlNodeType.Element); - } - - /// - /// Test if a is constructible with Activator.CreateInstance. - /// - /// the type to inspect - /// true if the type is creatable using a default constructor, false otherwise - private static bool IsTypeConstructible(Type type) { - if (type.IsClass && !type.IsAbstract) { - var defaultConstructor = type.GetConstructor(new Type[0]); - if (defaultConstructor != null && !defaultConstructor.IsAbstract && !defaultConstructor.IsPrivate) { - return true; - } - } - return false; - } - - /// - /// Look for a method on the that matches the supplied - /// - /// the type that has the method - /// the name of the method - /// the method info found - /// - /// - /// The method must be a public instance method on the . - /// The method must be named or "Add" followed by . - /// The method must take a single parameter. - /// - /// - private static MethodInfo FindMethodInfo(Type targetType, string name) { - var requiredMethodNameA = name; - var requiredMethodNameB = "Add" + name; - - var methods = targetType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - - foreach (var methInfo in methods) { - if (!methInfo.IsStatic) { - if (string.Compare(methInfo.Name, requiredMethodNameA, true, CultureInfo.InvariantCulture) == 0 || - string.Compare(methInfo.Name, requiredMethodNameB, true, CultureInfo.InvariantCulture) == 0) { - // Found matching method name - - // Look for version with one arg only - var methParams = methInfo.GetParameters(); - if (methParams.Length == 1) { - return methInfo; - } - } - } - } - return null; - } - - /// - /// Converts a string value to a target type. - /// - /// The type of object to convert the string to. - /// The string value to use as the value of the object. - /// - /// - /// An object of type with value or - /// null when the conversion could not be performed. - /// - /// - protected object ConvertStringTo(Type type, string value) { - // Hack to allow use of Level in property - if (typeof(Level) == type) { - // Property wants a level - var levelValue = _hierarchy.LevelMap[value]; - - if (levelValue == null) { - LogLog.Error("XmlHierarchyConfigurator: Unknown Level Specified [" + value + "]"); - } - - return levelValue; - } - return OptionConverter.ConvertStringTo(type, value); - } - - /// - /// Creates an object as specified in XML. - /// - /// The XML element that contains the definition of the object. - /// The object type to use if not explicitly specified. - /// The type that the returned object must be or must inherit from. - /// The object or null - /// - /// - /// Parse an XML element and create an object instance based on the configuration - /// data. - /// - /// - /// The type of the instance may be specified in the XML. If not - /// specified then the is used - /// as the type. However the type is specified it must support the - /// type. - /// - /// - protected object CreateObjectFromXml(XmlElement element, Type defaultTargetType, Type typeConstraint) { - Type objectType; - - // Get the object type - var objectTypeString = element.GetAttribute(TypeAttr); - if (String.IsNullOrEmpty(objectTypeString)) { - if (defaultTargetType == null) { - LogLog.Error("XmlHierarchyConfigurator: 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 ex) { - LogLog.Error("XmlHierarchyConfigurator: Failed to find type [" + objectTypeString + "]", ex); - return null; - } - } - - var requiresConversion = false; - - // Got the object type. Check that it meets the typeConstraint - if (typeConstraint != null) { - if (!typeConstraint.IsAssignableFrom(objectType)) { - // Check if there is an appropriate type converter - if (OptionConverter.CanConvertTypeTo(objectType, typeConstraint)) { - requiresConversion = true; - } - else { - LogLog.Error("XmlHierarchyConfigurator: 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 = null; - try { - createdObject = Activator.CreateInstance(objectType); - } - catch (Exception createInstanceEx) { - LogLog.Error("XmlHierarchyConfigurator: Failed to construct object of type [" + objectType.FullName + "] Exception: " + createInstanceEx); - } - - // Set any params on object - foreach (var currentNode in element.ChildNodes.Cast().Where(currentNode => currentNode.NodeType == XmlNodeType.Element)) { - SetParameter((XmlElement)currentNode, createdObject); - } - - // Check if we need to call ActivateOptions - var optionHandler = createdObject as IOptionHandler; - if (optionHandler != null) { - optionHandler.ActivateOptions(); - } - - // Ok object should be initialized - - return requiresConversion ? OptionConverter.ConvertTypeTo(createdObject, typeConstraint) : createdObject; - } - } -} diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index b05405759..35b429556 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -200,8 +200,6 @@ - -