#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.Xml; using System.Collections; using System.IO; using System.Reflection; using System.Threading; using System.Net; using log4net.Appender; using log4net.Util; using log4net.Repository; namespace log4net.Config { /// /// Use this class to initialize the log4net environment using an Xml tree. /// /// /// /// Configures a using an Xml tree. /// /// /// Nicko Cadell /// Gert Driesen public sealed class XmlConfigurator { #region Private Instance Constructors /// /// Private constructor /// private XmlConfigurator() { } #endregion Protected Instance Constructors #region Configure static methods #if !NETCF /// /// Automatically configures the log4net system based on the /// application's configuration settings. /// /// /// /// Each application has a configuration file. This has the /// same name as the application with '.config' appended. /// This file is XML and calling this function prompts the /// configurator to look in that file for a section called /// log4net that contains the configuration data. /// /// /// To use this method to configure log4net you must specify /// the section /// handler for the log4net configuration section. See the /// for an example. /// /// /// #else /// /// Automatically configures the log4net system based on the /// application's configuration settings. /// /// /// /// Each application has a configuration file. This has the /// same name as the application with '.config' appended. /// This file is XML and calling this function prompts the /// configurator to look in that file for a section called /// log4net that contains the configuration data. /// /// #endif static public ICollection Configure() { return Configure(LogManager.GetRepository(Assembly.GetCallingAssembly())); } #if !NETCF /// /// Automatically configures the using settings /// stored in the application's configuration file. /// /// /// /// Each application has a configuration file. This has the /// same name as the application with '.config' appended. /// This file is XML and calling this function prompts the /// configurator to look in that file for a section called /// log4net that contains the configuration data. /// /// /// To use this method to configure log4net you must specify /// the section /// handler for the log4net configuration section. See the /// for an example. /// /// /// The repository to configure. #else /// /// Automatically configures the using settings /// stored in the application's configuration file. /// /// /// /// Each application has a configuration file. This has the /// same name as the application with '.config' appended. /// This file is XML and calling this function prompts the /// configurator to look in that file for a section called /// log4net that contains the configuration data. /// /// /// The repository to configure. #endif static public ICollection Configure(ILoggerRepository repository) { ArrayList configurationMessages = new ArrayList(); using (new LogLog.LogReceivedAdapter(configurationMessages)) { InternalConfigure(repository); } repository.ConfigurationMessages = configurationMessages; return configurationMessages; } static private void InternalConfigure(ILoggerRepository repository) { LogLog.Debug(declaringType, "configuring repository [" + repository.Name + "] using .config file section"); try { LogLog.Debug(declaringType, "Application config file is [" + SystemInfo.ConfigurationFileLocation + "]"); } catch { // ignore error LogLog.Debug(declaringType, "Application config file location unknown"); } #if NETCF // No config file reading stuff. Just go straight for the file Configure(repository, new FileInfo(SystemInfo.ConfigurationFileLocation)); #else try { XmlElement configElement = null; #if NET_2_0 configElement = System.Configuration.ConfigurationManager.GetSection("log4net") as XmlElement; #else configElement = System.Configuration.ConfigurationSettings.GetConfig("log4net") as XmlElement; #endif if (configElement == null) { // Failed to load the xml config using configuration settings handler LogLog.Error(declaringType, "Failed to find configuration section 'log4net' in the application's .config file. Check your .config file for the and elements. The configuration section should look like:
"); } else { // Configure using the xml loaded from the config file InternalConfigureFromXml(repository, configElement); } } catch(System.Configuration.ConfigurationException confEx) { if (confEx.BareMessage.IndexOf("Unrecognized element") >= 0) { // Looks like the XML file is not valid LogLog.Error(declaringType, "Failed to parse config file. Check your .config file is well formed XML.", confEx); } else { // This exception is typically due to the assembly name not being correctly specified in the section type. string configSectionStr = "
"; LogLog.Error(declaringType, "Failed to parse config file. Is the specified as: " + configSectionStr, confEx); } } #endif } /// /// Configures log4net using a log4net element /// /// /// /// Loads the log4net configuration from the XML element /// supplied as . /// /// /// The element to parse. static public ICollection Configure(XmlElement element) { ArrayList configurationMessages = new ArrayList(); ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly()); using (new LogLog.LogReceivedAdapter(configurationMessages)) { InternalConfigureFromXml(repository, element); } repository.ConfigurationMessages = configurationMessages; return configurationMessages; } /// /// Configures the using the specified XML /// element. /// /// /// Loads the log4net configuration from the XML element /// supplied as . /// /// The repository to configure. /// The element to parse. static public ICollection Configure(ILoggerRepository repository, XmlElement element) { ArrayList configurationMessages = new ArrayList(); using (new LogLog.LogReceivedAdapter(configurationMessages)) { LogLog.Debug(declaringType, "configuring repository [" + repository.Name + "] using XML element"); InternalConfigureFromXml(repository, element); } repository.ConfigurationMessages = configurationMessages; return configurationMessages; } #if !NETCF /// /// Configures log4net using the specified configuration file. /// /// 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(new FileInfo(ConfigurationSettings.AppSettings["log4net-config-file"])); /// /// /// In the .config file, the path to the log4net can be specified like this : /// /// /// /// /// /// /// /// /// #else /// /// Configures log4net using the specified configuration file. /// /// 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 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(new FileInfo(ConfigurationSettings.AppSettings["log4net-config-file"])); /// /// /// In the .config file, the path to the log4net can be specified like this : /// /// /// /// /// /// /// /// /// #endif static public ICollection Configure(FileInfo configFile) { ArrayList configurationMessages = new ArrayList(); using (new LogLog.LogReceivedAdapter(configurationMessages)) { InternalConfigure(LogManager.GetRepository(Assembly.GetCallingAssembly()), configFile); } return configurationMessages; } /// /// Configures log4net using the specified configuration URI. /// /// A URI 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. /// /// /// The must support the URI scheme specified. /// /// static public ICollection Configure(Uri configUri) { ArrayList configurationMessages = new ArrayList(); ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly()); using (new LogLog.LogReceivedAdapter(configurationMessages)) { InternalConfigure(repository, configUri); } repository.ConfigurationMessages = configurationMessages; return configurationMessages; } /// /// 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. /// /// static public ICollection Configure(Stream configStream) { ArrayList configurationMessages = new ArrayList(); ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly()); using (new LogLog.LogReceivedAdapter(configurationMessages)) { InternalConfigure(repository, configStream); } repository.ConfigurationMessages = configurationMessages; return configurationMessages; } #if !NETCF /// /// Configures the using the specified configuration /// file. /// /// The repository to configure. /// 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(new FileInfo(ConfigurationSettings.AppSettings["log4net-config-file"])); /// /// /// In the .config file, the path to the log4net can be specified like this : /// /// /// /// /// /// /// /// /// #else /// /// Configures the using the specified configuration /// file. /// /// The repository to configure. /// 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 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(new FileInfo(ConfigurationSettings.AppSettings["log4net-config-file"])); /// /// /// In the .config file, the path to the log4net can be specified like this : /// /// /// /// /// /// /// /// /// #endif static public ICollection Configure(ILoggerRepository repository, FileInfo configFile) { ArrayList configurationMessages = new ArrayList(); using (new LogLog.LogReceivedAdapter(configurationMessages)) { InternalConfigure(repository, configFile); } repository.ConfigurationMessages = configurationMessages; return configurationMessages; } static private void InternalConfigure(ILoggerRepository repository, FileInfo configFile) { LogLog.Debug(declaringType, "configuring repository [" + repository.Name + "] using file [" + configFile + "]"); if (configFile == null) { LogLog.Error(declaringType, "Configure called with null 'configFile' parameter"); } else { // Have to use File.Exists() rather than configFile.Exists() // because configFile.Exists() caches the value, not what we want. if (File.Exists(configFile.FullName)) { // Open the file for reading FileStream fs = null; // Try hard to open the file for(int retry = 5; --retry >= 0; ) { try { fs = configFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read); break; } catch(IOException ex) { if (retry == 0) { LogLog.Error(declaringType, "Failed to open XML config file [" + configFile.Name + "]", ex); // The stream cannot be valid fs = null; } System.Threading.Thread.Sleep(250); } } if (fs != null) { try { // Load the configuration from the stream InternalConfigure(repository, fs); } finally { // Force the file closed whatever happens fs.Close(); } } } else { LogLog.Debug(declaringType, "config file [" + configFile.FullName + "] not found. Configuration unchanged."); } } } /// /// Configures the using the specified configuration /// URI. /// /// The repository to configure. /// A URI 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. /// /// /// The must support the URI scheme specified. /// /// static public ICollection Configure(ILoggerRepository repository, Uri configUri) { ArrayList configurationMessages = new ArrayList(); using (new LogLog.LogReceivedAdapter(configurationMessages)) { InternalConfigure(repository, configUri); } repository.ConfigurationMessages = configurationMessages; return configurationMessages; } static private void InternalConfigure(ILoggerRepository repository, Uri configUri) { LogLog.Debug(declaringType, "configuring repository [" + repository.Name + "] using URI ["+configUri+"]"); if (configUri == null) { LogLog.Error(declaringType, "Configure called with null 'configUri' parameter"); } else { if (configUri.IsFile) { // If URI is local file then call Configure with FileInfo InternalConfigure(repository, new FileInfo(configUri.LocalPath)); } else { // NETCF dose not support WebClient WebRequest configRequest = null; try { configRequest = WebRequest.Create(configUri); } catch(Exception ex) { LogLog.Error(declaringType, "Failed to create WebRequest for URI ["+configUri+"]", ex); } if (configRequest != null) { #if !NETCF_1_0 // authentication may be required, set client to use default credentials try { configRequest.Credentials = CredentialCache.DefaultCredentials; } catch { // ignore security exception } #endif try { WebResponse response = configRequest.GetResponse(); if (response != null) { try { // Open stream on config URI using(Stream configStream = response.GetResponseStream()) { InternalConfigure(repository, configStream); } } finally { response.Close(); } } } catch(Exception ex) { LogLog.Error(declaringType, "Failed to request config from URI ["+configUri+"]", ex); } } } } } /// /// 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. /// /// static public ICollection Configure(ILoggerRepository repository, Stream configStream) { ArrayList configurationMessages = new ArrayList(); using (new LogLog.LogReceivedAdapter(configurationMessages)) { InternalConfigure(repository, configStream); } repository.ConfigurationMessages = configurationMessages; return configurationMessages; } static private void InternalConfigure(ILoggerRepository repository, Stream configStream) { LogLog.Debug(declaringType, "configuring repository [" + repository.Name + "] using stream"); if (configStream == null) { LogLog.Error(declaringType, "Configure called with null 'configStream' parameter"); } else { // Load the config file into a document XmlDocument doc = new XmlDocument(); try { #if (NETCF) // Create a text reader for the file stream XmlTextReader xmlReader = new XmlTextReader(configStream); #elif NET_2_0 // Allow the DTD to specify entity includes XmlReaderSettings settings = new XmlReaderSettings(); // .NET 4.0 warning CS0618: 'System.Xml.XmlReaderSettings.ProhibitDtd' // is obsolete: 'Use XmlReaderSettings.DtdProcessing property instead.' #if !NET_4_0 settings.ProhibitDtd = false; #else settings.DtdProcessing = DtdProcessing.Parse; #endif // Create a reader over the input stream XmlReader xmlReader = XmlReader.Create(configStream, settings); #else // Create a validating reader around a text reader for the file stream XmlValidatingReader xmlReader = new XmlValidatingReader(new XmlTextReader(configStream)); // Specify that the reader should not perform validation, but that it should // expand entity refs. xmlReader.ValidationType = ValidationType.None; xmlReader.EntityHandling = EntityHandling.ExpandEntities; #endif // load the data into the document doc.Load(xmlReader); } catch(Exception ex) { LogLog.Error(declaringType, "Error while loading XML configuration", ex); // The document is invalid doc = null; } if (doc != null) { LogLog.Debug(declaringType, "loading XML configuration"); // Configure using the 'log4net' element XmlNodeList configNodeList = doc.GetElementsByTagName("log4net"); if (configNodeList.Count == 0) { LogLog.Debug(declaringType, "XML configuration does not contain a element. Configuration Aborted."); } else if (configNodeList.Count > 1) { LogLog.Error(declaringType, "XML configuration contains [" + configNodeList.Count + "] elements. Only one is allowed. Configuration Aborted."); } else { InternalConfigureFromXml(repository, configNodeList[0] as XmlElement); } } } } #endregion Configure static methods #region ConfigureAndWatch static methods #if (!NETCF && !SSCLI) /// /// Configures log4net using the file specified, monitors the file for changes /// and reloads the configuration if a change is detected. /// /// 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 configuration file will be monitored using a /// and depends on the behavior of that class. /// /// /// For more information on how to configure log4net using /// a separate configuration file, see . /// /// /// static public ICollection ConfigureAndWatch(FileInfo configFile) { ArrayList configurationMessages = new ArrayList(); ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly()); using (new LogLog.LogReceivedAdapter(configurationMessages)) { InternalConfigureAndWatch(repository, configFile); } repository.ConfigurationMessages = configurationMessages; return configurationMessages; } /// /// Configures the using the file specified, /// monitors the file for changes and reloads the configuration if a change /// is detected. /// /// The repository to configure. /// 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 configuration file will be monitored using a /// and depends on the behavior of that class. /// /// /// For more information on how to configure log4net using /// a separate configuration file, see . /// /// /// static public ICollection ConfigureAndWatch(ILoggerRepository repository, FileInfo configFile) { ArrayList configurationMessages = new ArrayList(); using (new LogLog.LogReceivedAdapter(configurationMessages)) { InternalConfigureAndWatch(repository, configFile); } repository.ConfigurationMessages = configurationMessages; return configurationMessages; } static private void InternalConfigureAndWatch(ILoggerRepository repository, FileInfo configFile) { LogLog.Debug(declaringType, "configuring repository [" + repository.Name + "] using file [" + configFile + "] watching for file updates"); if (configFile == null) { LogLog.Error(declaringType, "ConfigureAndWatch called with null 'configFile' parameter"); } else { // Configure log4net now InternalConfigure(repository, configFile); try { lock (m_repositoryName2ConfigAndWatchHandler) { // support multiple repositories each having their own watcher ConfigureAndWatchHandler handler = (ConfigureAndWatchHandler)m_repositoryName2ConfigAndWatchHandler[configFile.FullName]; if (handler != null) { m_repositoryName2ConfigAndWatchHandler.Remove(configFile.FullName); handler.Dispose(); } // Create and start a watch handler that will reload the // configuration whenever the config file is modified. handler = new ConfigureAndWatchHandler(repository, configFile); m_repositoryName2ConfigAndWatchHandler[configFile.FullName] = handler; } } catch(Exception ex) { LogLog.Error(declaringType, "Failed to initialize configuration file watcher for file ["+configFile.FullName+"]", ex); } } } #endif #endregion ConfigureAndWatch static methods #region ConfigureAndWatchHandler #if (!NETCF && !SSCLI) /// /// Class used to watch config files. /// /// /// /// Uses the to monitor /// changes to a specified file. Because multiple change notifications /// may be raised when the file is modified, a timer is used to /// compress the notifications into a single event. The timer /// waits for time before delivering /// the event notification. If any further /// change notifications arrive while the timer is waiting it /// is reset and waits again for to /// elapse. /// /// private sealed class ConfigureAndWatchHandler : IDisposable { /// /// Holds the FileInfo used to configure the XmlConfigurator /// private FileInfo m_configFile; /// /// Holds the repository being configured. /// private ILoggerRepository m_repository; /// /// The timer used to compress the notification events. /// private Timer m_timer; /// /// The default amount of time to wait after receiving notification /// before reloading the config file. /// private const int TimeoutMillis = 500; /// /// Watches file for changes. This object should be disposed when no longer /// needed to free system handles on the watched resources. /// private FileSystemWatcher m_watcher; /// /// Initializes a new instance of the class to /// watch a specified config file used to configure a repository. /// /// The repository to configure. /// The configuration file to watch. /// /// /// Initializes a new instance of the class. /// /// #if NET_4_0 [System.Security.SecuritySafeCritical] #endif public ConfigureAndWatchHandler(ILoggerRepository repository, FileInfo configFile) { m_repository = repository; m_configFile = configFile; // Create a new FileSystemWatcher and set its properties. m_watcher = new FileSystemWatcher(); m_watcher.Path = m_configFile.DirectoryName; m_watcher.Filter = m_configFile.Name; // Set the notification filters m_watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName; // Add event handlers. OnChanged will do for all event handlers that fire a FileSystemEventArgs m_watcher.Changed += new FileSystemEventHandler(ConfigureAndWatchHandler_OnChanged); m_watcher.Created += new FileSystemEventHandler(ConfigureAndWatchHandler_OnChanged); m_watcher.Deleted += new FileSystemEventHandler(ConfigureAndWatchHandler_OnChanged); m_watcher.Renamed += new RenamedEventHandler(ConfigureAndWatchHandler_OnRenamed); // Begin watching. m_watcher.EnableRaisingEvents = true; // Create the timer that will be used to deliver events. Set as disabled m_timer = new Timer(new TimerCallback(OnWatchedFileChange), null, Timeout.Infinite, Timeout.Infinite); } /// /// Event handler used by . /// /// The firing the event. /// The argument indicates the file that caused the event to be fired. /// /// /// This handler reloads the configuration from the file when the event is fired. /// /// private void ConfigureAndWatchHandler_OnChanged(object source, FileSystemEventArgs e) { LogLog.Debug(declaringType, "ConfigureAndWatchHandler: "+e.ChangeType+" [" + m_configFile.FullName + "]"); // Deliver the event in TimeoutMillis time // timer will fire only once m_timer.Change(TimeoutMillis, Timeout.Infinite); } /// /// Event handler used by . /// /// The firing the event. /// The argument indicates the file that caused the event to be fired. /// /// /// This handler reloads the configuration from the file when the event is fired. /// /// private void ConfigureAndWatchHandler_OnRenamed(object source, RenamedEventArgs e) { LogLog.Debug(declaringType, "ConfigureAndWatchHandler: " + e.ChangeType + " [" + m_configFile.FullName + "]"); // Deliver the event in TimeoutMillis time // timer will fire only once m_timer.Change(TimeoutMillis, Timeout.Infinite); } /// /// Called by the timer when the configuration has been updated. /// /// null private void OnWatchedFileChange(object state) { XmlConfigurator.InternalConfigure(m_repository, m_configFile); } /// /// Release the handles held by the watcher and timer. /// #if NET_4_0 [System.Security.SecuritySafeCritical] #endif public void Dispose() { m_watcher.EnableRaisingEvents = false; m_watcher.Dispose(); m_timer.Dispose(); } } #endif #endregion ConfigureAndWatchHandler #region Private Static Methods /// /// 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 . /// /// static private void InternalConfigureFromXml(ILoggerRepository repository, XmlElement element) { if (element == null) { LogLog.Error(declaringType, "ConfigureFromXml called with null 'element' parameter"); } else if (repository == null) { LogLog.Error(declaringType, "ConfigureFromXml called with null 'repository' parameter"); } else { LogLog.Debug(declaringType, "Configuring Repository [" + repository.Name + "]"); IXmlRepositoryConfigurator configurableRepository = repository as IXmlRepositoryConfigurator; if (configurableRepository == null) { LogLog.Warn(declaringType, "Repository [" + repository + "] does not support the XmlConfigurator"); } else { // Copy the xml data into the root of a new document // this isolates the xml config data from the rest of // the document XmlDocument newDoc = new XmlDocument(); XmlElement newElement = (XmlElement)newDoc.AppendChild(newDoc.ImportNode(element, true)); // Pass the configurator the config element configurableRepository.Configure(newElement); } } } #endregion Private Static Methods #region Private Static Fields /// /// Maps repository names to ConfigAndWatchHandler instances to allow a particular /// ConfigAndWatchHandler to dispose of its FileSystemWatcher when a repository is /// reconfigured. /// private readonly static Hashtable m_repositoryName2ConfigAndWatchHandler = new Hashtable(); /// /// The fully qualified type of the XmlConfigurator class. /// /// /// Used by the internal logger to record the Type of the /// log message. /// private readonly static Type declaringType = typeof(XmlConfigurator); #endregion Private Static Fields } }