blob: c2fd840506be822581ba01b2dad20af39a2e3b81 [file] [log] [blame]
#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.Collections.Concurrent;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Net;
using log4net.Util;
using log4net.Repository;
using System.Collections.Generic;
namespace log4net.Config;
/// <summary>
/// Configures a <see cref="ILoggerRepository"/> using an XML tree.
/// </summary>
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
public static class XmlConfigurator
{
/// <summary>
/// Automatically configures the <see cref="ILoggerRepository"/> using settings
/// stored in the application's configuration file.
/// </summary>
/// <remarks>
/// <para>
/// 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
/// <c>log4net</c> that contains the configuration data.
/// </para>
/// <para>
/// To use this method to configure log4net you must specify
/// the <see cref="Log4NetConfigurationSectionHandler"/> section
/// handler for the <c>log4net</c> configuration section. See the
/// <see cref="Log4NetConfigurationSectionHandler"/> for an example.
/// </para>
/// </remarks>
/// <param name="repository">The repository to configure.</param>
public static ICollection Configure(ILoggerRepository repository)
{
repository.EnsureNotNull();
List<LogLog> configurationMessages = [];
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
InternalConfigure(repository, () => System.Configuration.ConfigurationManager.GetSection("log4net") as XmlElement);
}
repository.ConfigurationMessages = configurationMessages;
return configurationMessages;
}
private static void InternalConfigure(ILoggerRepository repository, Func<XmlElement?> getConfigSection)
{
LogLog.Debug(_declaringType, $"configuring repository [{repository.Name}] using .config file section");
try
{
LogLog.Debug(_declaringType, $"Application config file is [{SystemInfo.ConfigurationFileLocation}]");
}
catch (Exception e) when (!e.IsFatal())
{
// ignore error
LogLog.Debug(_declaringType, "Application config file location unknown");
}
try
{
if (getConfigSection() is not XmlElement configElement)
{
// Failed to load the xml config using configuration settings handler
LogLog.Error(_declaringType, @$"Failed to find configuration section 'log4net' in the .config file '{SystemInfo.ConfigurationFileLocation}'.
Check your .config file for the <log4net> and <configSections> elements.
The configuration section should look like: <section name=""log4net"" type=""log4net.Config.Log4NetConfigurationSectionHandler,log4net"" />");
}
else
{
// Configure using the xml loaded from the config file
InternalConfigureFromXml(repository, configElement);
}
}
catch (System.Configuration.ConfigurationException confEx)
{
if (confEx.BareMessage.IndexOf("Unrecognized element", StringComparison.Ordinal) >= 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 = "<section name=\"log4net\" type=\"log4net.Config.Log4NetConfigurationSectionHandler," + Assembly.GetExecutingAssembly().FullName + "\" />";
LogLog.Error(_declaringType, "Failed to parse config file. Is the <configSections> specified as: " + configSectionStr, confEx);
}
}
}
/// <summary>
/// Automatically configures the log4net system based on the
/// application's configuration settings.
/// </summary>
/// <remarks>
/// <para>
/// 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
/// <c>log4net</c> that contains the configuration data.
/// </para>
/// <para>
/// To use this method to configure log4net you must specify
/// the <see cref="Log4NetConfigurationSectionHandler"/> section
/// handler for the <c>log4net</c> configuration section. See the
/// <see cref="Log4NetConfigurationSectionHandler"/> for an example.
/// </para>
/// </remarks>
/// <seealso cref="Log4NetConfigurationSectionHandler"/>
public static ICollection Configure()
=> Configure(LogManager.GetRepository(Assembly.GetCallingAssembly()));
/// <summary>
/// Configures log4net using a <c>log4net</c> element
/// </summary>
/// <remarks>
/// <para>
/// Loads the log4net configuration from the XML element
/// supplied as <paramref name="element"/>.
/// </para>
/// </remarks>
/// <param name="element">The element to parse.</param>
public static ICollection Configure(XmlElement element)
{
List<LogLog> configurationMessages = [];
ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly());
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
InternalConfigureFromXml(repository, element);
}
repository.ConfigurationMessages = configurationMessages;
return configurationMessages;
}
/// <summary>
/// Configures log4net using the specified configuration file.
/// </summary>
/// <param name="configFile">The XML file to load the configuration from.</param>
/// <remarks>
/// <para>
/// The configuration file must be valid XML. It must contain
/// at least one element called <c>log4net</c> that holds
/// the log4net configuration data.
/// </para>
/// <para>
/// The log4net configuration file can possibly be specified in the application's
/// configuration file (either <c>MyAppName.exe.config</c> for a
/// normal application on <c>Web.config</c> for an ASP.NET application).
/// </para>
/// <para>
/// The first element matching <c>&lt;configuration&gt;</c> will be read as the
/// configuration. If this file is also a .NET .config file then you must specify
/// a configuration section for the <c>log4net</c> element otherwise .NET will
/// complain. Set the type for the section handler to <see cref="System.Configuration.IgnoreSectionHandler"/>, for example:
/// <code lang="XML" escaped="true">
/// <configSections>
/// <section name="log4net" type="System.Configuration.IgnoreSectionHandler" />
/// </configSections>
/// </code>
/// </para>
/// <example>
/// The following example configures log4net using a configuration file, of which the
/// location is stored in the application's configuration file :
/// </example>
/// <code lang="C#">
/// using log4net.Config;
/// using System.IO;
/// using System.Configuration;
///
/// ...
///
/// XmlConfigurator.Configure(new FileInfo(ConfigurationSettings.AppSettings["log4net-config-file"]));
/// </code>
/// <para>
/// In the <c>.config</c> file, the path to the log4net can be specified like this :
/// </para>
/// <code lang="XML" escaped="true">
/// <configuration>
/// <appSettings>
/// <add key="log4net-config-file" value="log.config"/>
/// </appSettings>
/// </configuration>
/// </code>
/// </remarks>
public static ICollection Configure(FileInfo configFile)
{
List<LogLog> configurationMessages = [];
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
InternalConfigure(LogManager.GetRepository(Assembly.GetCallingAssembly()), configFile);
}
return configurationMessages;
}
/// <summary>
/// Configures log4net using the specified configuration URI.
/// </summary>
/// <param name="configUri">A URI to load the XML configuration from.</param>
/// <remarks>
/// <para>
/// The configuration data must be valid XML. It must contain
/// at least one element called <c>log4net</c> that holds
/// the log4net configuration data.
/// </para>
/// <para>
/// The <see cref="System.Net.WebRequest"/> must support the URI scheme specified.
/// </para>
/// </remarks>
public static ICollection Configure(Uri configUri)
{
List<LogLog> configurationMessages = [];
ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly());
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
InternalConfigure(repository, configUri);
}
repository.ConfigurationMessages = configurationMessages;
return configurationMessages;
}
/// <summary>
/// Configures log4net using the specified configuration data stream.
/// </summary>
/// <param name="configStream">A stream to load the XML configuration from.</param>
/// <remarks>
/// <para>
/// The configuration data must be valid XML. It must contain
/// at least one element called <c>log4net</c> that holds
/// the log4net configuration data.
/// </para>
/// <para>
/// Note that this method will NOT close the stream parameter.
/// </para>
/// </remarks>
public static ICollection Configure(Stream configStream)
{
List<LogLog> configurationMessages = [];
ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly());
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
InternalConfigure(repository, configStream);
}
repository.ConfigurationMessages = configurationMessages;
return configurationMessages;
}
/// <summary>
/// Configures the <see cref="ILoggerRepository"/> using the specified XML
/// element.
/// </summary>
/// <remarks>
/// Loads the log4net configuration from the XML element
/// supplied as <paramref name="element"/>.
/// </remarks>
/// <param name="repository">The repository to configure.</param>
/// <param name="element">The element to parse.</param>
public static ICollection Configure(ILoggerRepository repository, XmlElement element)
{
repository.EnsureNotNull();
List<LogLog> configurationMessages = [];
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
LogLog.Debug(_declaringType, "configuring repository [" + repository.Name + "] using XML element");
InternalConfigureFromXml(repository, element);
}
repository.ConfigurationMessages = configurationMessages;
return configurationMessages;
}
/// <summary>
/// Configures the <see cref="ILoggerRepository"/> using the specified configuration
/// file.
/// </summary>
/// <param name="repository">The repository to configure.</param>
/// <param name="configFile">The XML file to load the configuration from.</param>
/// <remarks>
/// <para>
/// The configuration file must be valid XML. It must contain
/// at least one element called <c>log4net</c> that holds
/// the configuration data.
/// </para>
/// <para>
/// The log4net configuration file can possibly be specified in the application's
/// configuration file (either <c>MyAppName.exe.config</c> for a
/// normal application on <c>Web.config</c> for an ASP.NET application).
/// </para>
/// <para>
/// The first element matching <c>&lt;configuration&gt;</c> will be read as the
/// configuration. If this file is also a .NET .config file then you must specify
/// a configuration section for the <c>log4net</c> element otherwise .NET will
/// complain. Set the type for the section handler to <see cref="System.Configuration.IgnoreSectionHandler"/>, for example:
/// <code lang="XML" escaped="true">
/// <configSections>
/// <section name="log4net" type="System.Configuration.IgnoreSectionHandler" />
/// </configSections>
/// </code>
/// </para>
/// <example>
/// The following example configures log4net using a configuration file, of which the
/// location is stored in the application's configuration file :
/// </example>
/// <code lang="C#">
/// using log4net.Config;
/// using System.IO;
/// using System.Configuration;
///
/// ...
///
/// XmlConfigurator.Configure(new FileInfo(ConfigurationSettings.AppSettings["log4net-config-file"]));
/// </code>
/// <para>
/// In the <c>.config</c> file, the path to the log4net can be specified like this :
/// </para>
/// <code lang="XML" escaped="true">
/// <configuration>
/// <appSettings>
/// <add key="log4net-config-file" value="log.config"/>
/// </appSettings>
/// </configuration>
/// </code>
/// </remarks>
public static ICollection Configure(ILoggerRepository repository, FileInfo configFile)
{
repository.EnsureNotNull();
List<LogLog> configurationMessages = [];
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
InternalConfigure(repository, configFile);
}
repository.ConfigurationMessages = configurationMessages;
return configurationMessages;
}
private static void InternalConfigure(ILoggerRepository repository, FileInfo? configFile)
{
LogLog.Debug(_declaringType, $"configuring repository [{repository.Name}] using file [{configFile}]");
if (configFile is 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;
}
Thread.Sleep(250);
}
}
if (fs is not null)
{
try
{
// Load the configuration from the stream
InternalConfigure(repository, fs);
}
finally
{
// Force the file closed whatever happens
fs.Dispose();
}
}
}
else
{
LogLog.Debug(_declaringType, "config file [" + configFile.FullName + "] not found. Configuration unchanged.");
}
}
}
/// <summary>
/// Configures the <see cref="ILoggerRepository"/> using the specified configuration
/// URI.
/// </summary>
/// <param name="repository">The repository to configure.</param>
/// <param name="configUri">A URI to load the XML configuration from.</param>
/// <remarks>
/// <para>
/// The configuration data must be valid XML. It must contain
/// at least one element called <c>log4net</c> that holds
/// the configuration data.
/// </para>
/// <para>
/// The <see cref="System.Net.WebRequest"/> must support the URI scheme specified.
/// </para>
/// </remarks>
public static ICollection Configure(ILoggerRepository repository, Uri configUri)
{
repository.EnsureNotNull();
List<LogLog> configurationMessages = [];
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
InternalConfigure(repository, configUri);
}
repository.ConfigurationMessages = configurationMessages;
return configurationMessages;
}
private static void InternalConfigure(ILoggerRepository repository, Uri? configUri)
{
LogLog.Debug(_declaringType, $"configuring repository [{repository.Name}] using URI [{configUri}]");
if (configUri is 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 e) when (!e.IsFatal())
{
LogLog.Error(_declaringType, $"Failed to create WebRequest for URI [{configUri}]", e);
}
if (configRequest is not null)
{
// authentication may be required, set client to use default credentials
try
{
configRequest.Credentials = CredentialCache.DefaultCredentials;
}
catch (Exception e) when (!e.IsFatal())
{
// ignore security exception
}
try
{
using WebResponse? response = configRequest.GetResponse();
if (response is not null)
{
using Stream configStream = response.GetResponseStream();
InternalConfigure(repository, configStream);
}
}
catch (Exception e) when (!e.IsFatal())
{
LogLog.Error(_declaringType, $"Failed to request config from URI [{configUri}]", e);
}
}
}
}
}
/// <summary>
/// Configures the <see cref="ILoggerRepository"/> using the specified configuration
/// file.
/// </summary>
/// <param name="repository">The repository to configure.</param>
/// <param name="configStream">The stream to load the XML configuration from.</param>
/// <remarks>
/// <para>
/// The configuration data must be valid XML. It must contain
/// at least one element called <c>log4net</c> that holds
/// the configuration data.
/// </para>
/// <para>
/// Note that this method will NOT close the stream parameter.
/// </para>
/// </remarks>
public static ICollection Configure(ILoggerRepository repository, Stream configStream)
{
repository.EnsureNotNull();
List<LogLog> configurationMessages = [];
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
InternalConfigure(repository, configStream);
}
repository.ConfigurationMessages = configurationMessages;
return configurationMessages;
}
private static void InternalConfigure(ILoggerRepository repository, Stream? configStream)
{
LogLog.Debug(_declaringType, $"configuring repository [{repository.Name}] using stream");
if (configStream is null)
{
LogLog.Error(_declaringType, "Configure called with null 'configStream' parameter");
}
else
{
// Load the config file into a document
XmlDocument? doc = new() { XmlResolver = null };
try
{
// Allow the DTD to specify entity includes
XmlReaderSettings settings = new()
{
// .NET 4.0 warning CS0618: 'System.Xml.XmlReaderSettings.ProhibitDtd'
// is obsolete: 'Use XmlReaderSettings.DtdProcessing property instead.'
DtdProcessing = DtdProcessing.Ignore
};
// Create a reader over the input stream
using XmlReader xmlReader = XmlReader.Create(configStream, settings);
// load the data into the document
doc.Load(xmlReader);
}
catch (Exception e) when (!e.IsFatal())
{
LogLog.Error(_declaringType, "Error while loading XML configuration", e);
// The document is invalid
doc = null;
}
if (doc is not 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 <log4net> element. Configuration Aborted.");
}
else if (configNodeList.Count > 1)
{
LogLog.Error(_declaringType, $"XML configuration contains [{configNodeList.Count}] <log4net> elements. Only one is allowed. Configuration Aborted.");
}
else
{
InternalConfigureFromXml(repository, configNodeList[0] as XmlElement);
}
}
}
}
/// <summary>
/// Configures log4net using the file specified, monitors the file for changes
/// and reloads the configuration if a change is detected.
/// </summary>
/// <param name="configFile">The XML file to load the configuration from.</param>
/// <remarks>
/// <para>
/// The configuration file must be valid XML. It must contain
/// at least one element called <c>log4net</c> that holds
/// the configuration data.
/// </para>
/// <para>
/// The configuration file will be monitored using a <see cref="FileSystemWatcher"/>
/// and depends on the behavior of that class.
/// </para>
/// <para>
/// For more information on how to configure log4net using
/// a separate configuration file, see <see cref="Configure(FileInfo)"/>.
/// </para>
/// </remarks>
/// <seealso cref="Configure(FileInfo)"/>
public static ICollection ConfigureAndWatch(FileInfo configFile)
{
List<LogLog> configurationMessages = [];
ILoggerRepository repository = LogManager.GetRepository(Assembly.GetCallingAssembly());
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
InternalConfigureAndWatch(repository, configFile);
}
repository.ConfigurationMessages = configurationMessages;
return configurationMessages;
}
/// <summary>
/// Configures the <see cref="ILoggerRepository"/> using the file specified,
/// monitors the file for changes and reloads the configuration if a change
/// is detected.
/// </summary>
/// <param name="repository">The repository to configure.</param>
/// <param name="configFile">The XML file to load the configuration from.</param>
/// <remarks>
/// <para>
/// The configuration file must be valid XML. It must contain
/// at least one element called <c>log4net</c> that holds
/// the configuration data.
/// </para>
/// <para>
/// The configuration file will be monitored using a <see cref="FileSystemWatcher"/>
/// and depends on the behavior of that class.
/// </para>
/// <para>
/// For more information on how to configure log4net using
/// a separate configuration file, see <see cref="Configure(FileInfo)"/>.
/// </para>
/// </remarks>
/// <seealso cref="Configure(FileInfo)"/>
public static ICollection ConfigureAndWatch(ILoggerRepository repository, FileInfo configFile)
{
repository.EnsureNotNull();
List<LogLog> configurationMessages = [];
using (new LogLog.LogReceivedAdapter(configurationMessages))
{
InternalConfigureAndWatch(repository, configFile);
}
repository.ConfigurationMessages = configurationMessages;
return configurationMessages;
}
private static void InternalConfigureAndWatch(ILoggerRepository repository, FileInfo? configFile)
{
LogLog.Debug(_declaringType, $"configuring repository [{repository.Name}] using file [{configFile}] watching for file updates");
if (configFile is null)
{
LogLog.Error(_declaringType, "ConfigureAndWatch called with null 'configFile' parameter");
}
else
{
// Configure log4net now
InternalConfigure(repository, configFile);
try
{
// Support multiple repositories each having their own watcher.
// Create and start a watch handler that will reload the
// configuration whenever the config file is modified.
_repositoryName2ConfigAndWatchHandler.AddOrUpdate(
configFile.FullName,
_ => new ConfigureAndWatchHandler(repository, configFile),
(_, handler) =>
{
// Replace the old handler.
handler.Dispose();
return new ConfigureAndWatchHandler(repository, configFile);
});
}
catch (Exception e) when (!e.IsFatal())
{
LogLog.Error(_declaringType, $"Failed to initialize configuration file watcher for file [{configFile.FullName}]", e);
}
}
}
/// <summary>
/// Class used to watch config files.
/// </summary>
/// <remarks>
/// <para>
/// Uses the <see cref="FileSystemWatcher"/> 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 <see cref="TimeoutMillis"/> time before delivering
/// the event notification. If any further <see cref="FileSystemWatcher"/>
/// change notifications arrive while the timer is waiting it
/// is reset and waits again for <see cref="TimeoutMillis"/> to
/// elapse.
/// </para>
/// </remarks>
private sealed class ConfigureAndWatchHandler : IDisposable
{
/// <summary>
/// Holds the FileInfo used to configure the XmlConfigurator
/// </summary>
private readonly FileInfo _configFile;
/// <summary>
/// Holds the repository being configured.
/// </summary>
private readonly ILoggerRepository _repository;
/// <summary>
/// The timer used to compress the notification events.
/// </summary>
private readonly Timer _timer;
/// <summary>
/// The default amount of time to wait after receiving notification
/// before reloading the config file.
/// </summary>
private const int TimeoutMillis = 500;
/// <summary>
/// Watches file for changes. This object should be disposed when no longer
/// needed to free system handles on the watched resources.
/// </summary>
private readonly FileSystemWatcher _watcher;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureAndWatchHandler" /> class to
/// watch a specified config file used to configure a repository.
/// </summary>
/// <param name="repository">The repository to configure.</param>
/// <param name="configFile">The configuration file to watch.</param>
/// <remarks>
/// <para>
/// Initializes a new instance of the <see cref="ConfigureAndWatchHandler" /> class.
/// </para>
/// </remarks>
[System.Security.SecuritySafeCritical]
public ConfigureAndWatchHandler(ILoggerRepository repository, FileInfo configFile)
{
this._repository = repository;
this._configFile = configFile;
// Create a new FileSystemWatcher and set its properties.
_watcher = new FileSystemWatcher()
{
Path = this._configFile.DirectoryName.EnsureNotNull(),
Filter = this._configFile.Name,
NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.FileName,
};
// Add event handlers. OnChanged will do for all event handlers that fire a FileSystemEventArgs
_watcher.Changed += ConfigureAndWatchHandler_OnChanged;
_watcher.Created += ConfigureAndWatchHandler_OnChanged;
_watcher.Deleted += ConfigureAndWatchHandler_OnChanged;
_watcher.Renamed += ConfigureAndWatchHandler_OnRenamed;
// Begin watching.
_watcher.EnableRaisingEvents = true;
// Create the timer that will be used to deliver events. Set as disabled
_timer = new Timer(OnWatchedFileChange, state: null, Timeout.Infinite, Timeout.Infinite);
}
/// <summary>
/// Event handler used by <see cref="ConfigureAndWatchHandler"/>.
/// </summary>
/// <param name="source">The <see cref="FileSystemWatcher"/> firing the event.</param>
/// <param name="e">The argument indicates the file that caused the event to be fired.</param>
/// <remarks>
/// <para>
/// This handler reloads the configuration from the file when the event is fired.
/// </para>
/// </remarks>
private void ConfigureAndWatchHandler_OnChanged(object source, FileSystemEventArgs e)
{
LogLog.Debug(_declaringType, $"ConfigureAndWatchHandler: {e.ChangeType} [{_configFile.FullName}]");
// Deliver the event in TimeoutMillis time
// timer will fire only once
_timer.Change(TimeoutMillis, Timeout.Infinite);
}
/// <summary>
/// Event handler used by <see cref="ConfigureAndWatchHandler"/>.
/// </summary>
/// <param name="source">The <see cref="FileSystemWatcher"/> firing the event.</param>
/// <param name="e">The argument indicates the file that caused the event to be fired.</param>
/// <remarks>
/// <para>
/// This handler reloads the configuration from the file when the event is fired.
/// </para>
/// </remarks>
private void ConfigureAndWatchHandler_OnRenamed(object source, RenamedEventArgs e)
{
LogLog.Debug(_declaringType, $"ConfigureAndWatchHandler: {e.ChangeType} [{_configFile.FullName}]");
// Deliver the event in TimeoutMillis time
// timer will fire only once
_timer.Change(TimeoutMillis, Timeout.Infinite);
}
/// <summary>
/// Called by the timer when the configuration has been updated.
/// </summary>
/// <param name="state">null</param>
private void OnWatchedFileChange(object? state) => InternalConfigure(_repository, _configFile);
/// <summary>
/// Release the handles held by the watcher and timer.
/// </summary>
[System.Security.SecuritySafeCritical]
public void Dispose()
{
_watcher.EnableRaisingEvents = false;
_watcher.Dispose();
_timer.Dispose();
}
}
/// <summary>
/// Configures the specified repository using a <c>log4net</c> element.
/// </summary>
/// <param name="repository">The hierarchy to configure.</param>
/// <param name="element">The element to parse.</param>
/// <remarks>
/// <para>
/// Loads the log4net configuration from the XML element
/// supplied as <paramref name="element"/>.
/// </para>
/// <para>
/// This method is ultimately called by one of the Configure methods
/// to load the configuration from an <see cref="XmlElement"/>.
/// </para>
/// </remarks>
private static void InternalConfigureFromXml(ILoggerRepository? repository, XmlElement? element)
{
if (element is null)
{
LogLog.Error(_declaringType, "ConfigureFromXml called with null 'element' parameter");
}
else if (repository is null)
{
LogLog.Error(_declaringType, "ConfigureFromXml called with null 'repository' parameter");
}
else
{
LogLog.Debug(_declaringType, "Configuring Repository [" + repository.Name + "]");
if (repository is not IXmlRepositoryConfigurator configurableRepository)
{
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() { XmlResolver = null };
XmlElement newElement = newDoc.AppendChild(newDoc.ImportNode(element, true)).EnsureIs<XmlElement>();
// Pass the configurator the config element
configurableRepository.Configure(newElement);
}
}
}
/// <summary>
/// Maps repository names to ConfigAndWatchHandler instances to allow a particular
/// ConfigAndWatchHandler to dispose of its FileSystemWatcher when a repository is
/// reconfigured.
/// </summary>
private static readonly ConcurrentDictionary<string, ConfigureAndWatchHandler> _repositoryName2ConfigAndWatchHandler = new(StringComparer.Ordinal);
/// <summary>
/// The fully qualified type of the XmlConfigurator class.
/// </summary>
/// <remarks>
/// Used by the internal logger to record the Type of the
/// log message.
/// </remarks>
private static readonly Type _declaringType = typeof(XmlConfigurator);
}