#region Copyright & License // // Copyright 2001-2005 The Apache Software Foundation // // Licensed 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 void Configure() { 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 void Configure(ILoggerRepository repository) { LogLog.Debug("XmlConfigurator: configuring repository [" + repository.Name + "] using .config file section"); try { LogLog.Debug("XmlConfigurator: Application config file is [" + SystemInfo.ConfigurationFileLocation + "]"); } catch { // ignore error LogLog.Debug("XmlConfigurator: 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("XmlConfigurator: 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 ConfigureFromXml(repository, configElement); } } catch(System.Configuration.ConfigurationException confEx) { if (confEx.BareMessage.IndexOf("Unrecognized element") >= 0) { // Looks like the XML file is not valid LogLog.Error("XmlConfigurator: 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("XmlConfigurator: 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 void Configure(XmlElement element) { ConfigureFromXml(LogManager.GetRepository(Assembly.GetCallingAssembly()), element); } /// /// 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 void Configure(ILoggerRepository repository, XmlElement element) { LogLog.Debug("XmlConfigurator: configuring repository [" + repository.Name + "] using XML element"); ConfigureFromXml(repository, element); } #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 void Configure(FileInfo configFile) { Configure(LogManager.GetRepository(Assembly.GetCallingAssembly()), configFile); } /// /// 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 void Configure(Uri configUri) { Configure(LogManager.GetRepository(Assembly.GetCallingAssembly()), configUri); } /// /// 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 void Configure(Stream configStream) { Configure(LogManager.GetRepository(Assembly.GetCallingAssembly()), configStream); } #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 void Configure(ILoggerRepository repository, FileInfo configFile) { LogLog.Debug("XmlConfigurator: configuring repository [" + repository.Name + "] using file [" + configFile + "]"); if (configFile == null) { LogLog.Error("XmlConfigurator: 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("XmlConfigurator: 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 Configure(repository, fs); } finally { // Force the file closed whatever happens fs.Close(); } } } else { LogLog.Debug("XmlConfigurator: 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 void Configure(ILoggerRepository repository, Uri configUri) { LogLog.Debug("XmlConfigurator: configuring repository [" + repository.Name + "] using URI ["+configUri+"]"); if (configUri == null) { LogLog.Error("XmlConfigurator: Configure called with null 'configUri' parameter"); } else { if (configUri.IsFile) { // If URI is local file then call Configure with FileInfo Configure(repository, new FileInfo(configUri.LocalPath)); } else { // NETCF dose not support WebClient WebRequest configRequest = null; try { configRequest = WebRequest.Create(configUri); } catch(Exception ex) { LogLog.Error("XmlConfigurator: Failed to create WebRequest for URI ["+configUri+"]", ex); } if (configRequest != null) { #if !NETCF // 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()) { Configure(repository, configStream); } } finally { response.Close(); } } } catch(Exception ex) { LogLog.Error("XmlConfigurator: 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 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 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(); settings.ProhibitDtd = false; // 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("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 XmlNodeList 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); } } } } #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 void ConfigureAndWatch(FileInfo configFile) { ConfigureAndWatch(LogManager.GetRepository(Assembly.GetCallingAssembly()), configFile); } /// /// 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 void ConfigureAndWatch(ILoggerRepository repository, FileInfo configFile) { LogLog.Debug("XmlConfigurator: configuring repository [" + repository.Name + "] using file [" + configFile + "] watching for file updates"); if (configFile == null) { LogLog.Error("XmlConfigurator: ConfigureAndWatch called with null 'configFile' parameter"); } else { // Configure log4net now Configure(repository, configFile); try { // Create a watch handler that will reload the // configuration whenever the config file is modified. ConfigureAndWatchHandler.StartWatching(repository, configFile); } catch(Exception ex) { LogLog.Error("XmlConfigurator: 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 { /// /// Watch a specified config file used to configure a repository /// /// The repository to configure. /// The configuration file to watch. /// /// /// Watch a specified config file used to configure a repository /// /// internal static void StartWatching(ILoggerRepository repository, FileInfo configFile) { new ConfigureAndWatchHandler(repository, configFile); } /// /// 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; /// /// Initializes a new instance of the class. /// /// The repository to configure. /// The configuration file to watch. /// /// /// Initializes a new instance of the class. /// /// private ConfigureAndWatchHandler(ILoggerRepository repository, FileInfo configFile) { m_repository = repository; m_configFile = configFile; // Create a new FileSystemWatcher and set its properties. FileSystemWatcher watcher = new FileSystemWatcher(); watcher.Path = m_configFile.DirectoryName; watcher.Filter = m_configFile.Name; // Set the notification filters watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName; // Add event handlers. OnChanged will do for all event handlers that fire a FileSystemEventArgs watcher.Changed += new FileSystemEventHandler(ConfigureAndWatchHandler_OnChanged); watcher.Created += new FileSystemEventHandler(ConfigureAndWatchHandler_OnChanged); watcher.Deleted += new FileSystemEventHandler(ConfigureAndWatchHandler_OnChanged); watcher.Renamed += new RenamedEventHandler(ConfigureAndWatchHandler_OnRenamed); // Begin watching. 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("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("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.Configure(m_repository, m_configFile); } } #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 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 + "]"); IXmlRepositoryConfigurator configurableRepository = repository as IXmlRepositoryConfigurator; if (configurableRepository == null) { LogLog.Warn("XmlConfigurator: 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 } }