#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 // .NET Compact Framework 1.0 has no support for reading assembly attributes // and uses the CompactRepositorySelector instead #if !NETCF using System; using System.Collections; #if !NETSTANDARD1_3 using System.Configuration; #else using System.Linq; #endif using System.IO; using System.Reflection; using log4net.Config; using log4net.Util; using log4net.Repository; namespace log4net.Core { /// /// The default implementation of the interface. /// /// /// /// Uses attributes defined on the calling assembly to determine how to /// configure the hierarchy for the repository. /// /// /// Nicko Cadell /// Gert Driesen public class DefaultRepositorySelector : IRepositorySelector { #region Public Events /// /// Event to notify that a logger repository has been created. /// /// /// Event to notify that a logger repository has been created. /// /// /// /// Event raised when a new repository is created. /// The event source will be this selector. The event args will /// be a which /// holds the newly created . /// /// public event LoggerRepositoryCreationEventHandler LoggerRepositoryCreatedEvent { add { m_loggerRepositoryCreatedEvent += value; } remove { m_loggerRepositoryCreatedEvent -= value; } } #endregion Public Events #region Public Instance Constructors /// /// Creates a new repository selector. /// /// The type of the repositories to create, must implement /// /// /// Create an new repository selector. /// The default type for repositories must be specified, /// an appropriate value would be . /// /// /// is . /// does not implement . public DefaultRepositorySelector(Type defaultRepositoryType) { if (defaultRepositoryType == null) { throw new ArgumentNullException("defaultRepositoryType"); } // Check that the type is a repository if (! (typeof(ILoggerRepository).IsAssignableFrom(defaultRepositoryType)) ) { throw log4net.Util.SystemInfo.CreateArgumentOutOfRangeException("defaultRepositoryType", defaultRepositoryType, "Parameter: defaultRepositoryType, Value: [" + defaultRepositoryType + "] out of range. Argument must implement the ILoggerRepository interface"); } m_defaultRepositoryType = defaultRepositoryType; LogLog.Debug(declaringType, "defaultRepositoryType [" + m_defaultRepositoryType + "]"); } #endregion Public Instance Constructors #region Implementation of IRepositorySelector /// /// Gets the for the specified assembly. /// /// The assembly use to lookup the . /// /// /// The type of the created and the repository /// to create can be overridden by specifying the /// attribute on the . /// /// /// The default values are to use the /// implementation of the interface and to use the /// as the name of the repository. /// /// /// The created will be automatically configured using /// any attributes defined on /// the . /// /// /// The for the assembly /// is . public ILoggerRepository GetRepository(Assembly repositoryAssembly) { if (repositoryAssembly == null) { throw new ArgumentNullException("repositoryAssembly"); } return CreateRepository(repositoryAssembly, m_defaultRepositoryType); } /// /// Gets the for the specified repository. /// /// The repository to use to lookup the . /// The for the specified repository. /// /// /// Returns the named repository. If is null /// a is thrown. If the repository /// does not exist a is thrown. /// /// /// Use to create a repository. /// /// /// is . /// does not exist. public ILoggerRepository GetRepository(string repositoryName) { if (repositoryName == null) { throw new ArgumentNullException("repositoryName"); } lock(this) { // Lookup in map ILoggerRepository rep = m_name2repositoryMap[repositoryName] as ILoggerRepository; if (rep == null) { throw new LogException("Repository [" + repositoryName + "] is NOT defined."); } return rep; } } /// /// Create a new repository for the assembly specified /// /// the assembly to use to create the repository to associate with the . /// The type of repository to create, must implement . /// The repository created. /// /// /// The created will be associated with the repository /// specified such that a call to with the /// same assembly specified will return the same repository instance. /// /// /// The type of the created and /// the repository to create can be overridden by specifying the /// attribute on the /// . The default values are to use the /// implementation of the /// interface and to use the /// as the name of the repository. /// /// /// The created will be automatically /// configured using any /// attributes defined on the . /// /// /// If a repository for the already exists /// that repository will be returned. An error will not be raised and that /// repository may be of a different type to that specified in . /// Also the attribute on the /// assembly may be used to override the repository type specified in /// . /// /// /// is . public ILoggerRepository CreateRepository(Assembly repositoryAssembly, Type repositoryType) { return CreateRepository(repositoryAssembly, repositoryType, DefaultRepositoryName, true); } /// /// Creates a new repository for the assembly specified. /// /// the assembly to use to create the repository to associate with the . /// The type of repository to create, must implement . /// The name to assign to the created repository /// Set to true to read and apply the assembly attributes /// The repository created. /// /// /// The created will be associated with the repository /// specified such that a call to with the /// same assembly specified will return the same repository instance. /// /// /// The type of the created and /// the repository to create can be overridden by specifying the /// attribute on the /// . The default values are to use the /// implementation of the /// interface and to use the /// as the name of the repository. /// /// /// The created will be automatically /// configured using any /// attributes defined on the . /// /// /// If a repository for the already exists /// that repository will be returned. An error will not be raised and that /// repository may be of a different type to that specified in . /// Also the attribute on the /// assembly may be used to override the repository type specified in /// . /// /// /// is . public ILoggerRepository CreateRepository(Assembly repositoryAssembly, Type repositoryType, string repositoryName, bool readAssemblyAttributes) { if (repositoryAssembly == null) { throw new ArgumentNullException("repositoryAssembly"); } // If the type is not set then use the default type if (repositoryType == null) { repositoryType = m_defaultRepositoryType; } lock(this) { // Lookup in map ILoggerRepository rep = m_assembly2repositoryMap[repositoryAssembly] as ILoggerRepository; if (rep == null) { // Not found, therefore create LogLog.Debug(declaringType, "Creating repository for assembly [" + repositoryAssembly + "]"); // Must specify defaults string actualRepositoryName = repositoryName; Type actualRepositoryType = repositoryType; if (readAssemblyAttributes) { // Get the repository and type from the assembly attributes GetInfoForAssembly(repositoryAssembly, ref actualRepositoryName, ref actualRepositoryType); } LogLog.Debug(declaringType, "Assembly [" + repositoryAssembly + "] using repository [" + actualRepositoryName + "] and repository type [" + actualRepositoryType + "]"); // Lookup the repository in the map (as this may already be defined) rep = m_name2repositoryMap[actualRepositoryName] as ILoggerRepository; if (rep == null) { // Create the repository rep = CreateRepository(actualRepositoryName, actualRepositoryType); if (readAssemblyAttributes) { try { // Look for aliasing attributes LoadAliases(repositoryAssembly, rep); // Look for plugins defined on the assembly LoadPlugins(repositoryAssembly, rep); // Configure the repository using the assembly attributes ConfigureRepository(repositoryAssembly, rep); } catch (Exception ex) { LogLog.Error(declaringType, "Failed to configure repository [" + actualRepositoryName + "] from assembly attributes.", ex); } } } else { LogLog.Debug(declaringType, "repository [" + actualRepositoryName + "] already exists, using repository type [" + rep.GetType().FullName + "]"); if (readAssemblyAttributes) { try { // Look for plugins defined on the assembly LoadPlugins(repositoryAssembly, rep); } catch (Exception ex) { LogLog.Error(declaringType, "Failed to configure repository [" + actualRepositoryName + "] from assembly attributes.", ex); } } } m_assembly2repositoryMap[repositoryAssembly] = rep; } return rep; } } /// /// Creates a new repository for the specified repository. /// /// The repository to associate with the . /// The type of repository to create, must implement . /// If this param is then the default repository type is used. /// The new repository. /// /// /// The created will be associated with the repository /// specified such that a call to with the /// same repository specified will return the same repository instance. /// /// /// is . /// already exists. public ILoggerRepository CreateRepository(string repositoryName, Type repositoryType) { if (repositoryName == null) { throw new ArgumentNullException("repositoryName"); } // If the type is not set then use the default type if (repositoryType == null) { repositoryType = m_defaultRepositoryType; } lock(this) { ILoggerRepository rep = null; // First check that the repository does not exist rep = m_name2repositoryMap[repositoryName] as ILoggerRepository; if (rep != null) { throw new LogException("Repository [" + repositoryName + "] is already defined. Repositories cannot be redefined."); } else { // Lookup an alias before trying to create the new repository ILoggerRepository aliasedRepository = m_alias2repositoryMap[repositoryName] as ILoggerRepository; if (aliasedRepository != null) { // Found an alias // Check repository type if (aliasedRepository.GetType() == repositoryType) { // Repository type is compatible LogLog.Debug(declaringType, "Aliasing repository [" + repositoryName + "] to existing repository [" + aliasedRepository.Name + "]"); rep = aliasedRepository; // Store in map m_name2repositoryMap[repositoryName] = rep; } else { // Invalid repository type for alias LogLog.Error(declaringType, "Failed to alias repository [" + repositoryName + "] to existing repository ["+aliasedRepository.Name+"]. Requested repository type ["+repositoryType.FullName+"] is not compatible with existing type [" + aliasedRepository.GetType().FullName + "]"); // We now drop through to create the repository without aliasing } } // If we could not find an alias if (rep == null) { LogLog.Debug(declaringType, "Creating repository [" + repositoryName + "] using type [" + repositoryType + "]"); // Call the no arg constructor for the repositoryType rep = (ILoggerRepository)Activator.CreateInstance(repositoryType); // Set the name of the repository rep.Name = repositoryName; // Store in map m_name2repositoryMap[repositoryName] = rep; // Notify listeners that the repository has been created OnLoggerRepositoryCreatedEvent(rep); } } return rep; } } /// /// Test if a named repository exists /// /// the named repository to check /// true if the repository exists /// /// /// Test if a named repository exists. Use /// to create a new repository and to retrieve /// a repository. /// /// public bool ExistsRepository(string repositoryName) { lock(this) { return m_name2repositoryMap.ContainsKey(repositoryName); } } /// /// Gets a list of objects /// /// an array of all known objects /// /// /// Gets an array of all of the repositories created by this selector. /// /// public ILoggerRepository[] GetAllRepositories() { lock(this) { ICollection reps = m_name2repositoryMap.Values; ILoggerRepository[] all = new ILoggerRepository[reps.Count]; reps.CopyTo(all, 0); return all; } } #endregion Implementation of IRepositorySelector #region Public Instance Methods /// /// Aliases a repository to an existing repository. /// /// The repository to alias. /// The repository that the repository is aliased to. /// /// /// The repository specified will be aliased to the repository when created. /// The repository must not already exist. /// /// /// When the repository is created it must utilize the same repository type as /// the repository it is aliased to, otherwise the aliasing will fail. /// /// /// /// is . /// -or- /// is . /// public void AliasRepository(string repositoryAlias, ILoggerRepository repositoryTarget) { if (repositoryAlias == null) { throw new ArgumentNullException("repositoryAlias"); } if (repositoryTarget == null) { throw new ArgumentNullException("repositoryTarget"); } lock(this) { // Check if the alias is already set if (m_alias2repositoryMap.Contains(repositoryAlias)) { // Check if this is a duplicate of the current alias if (repositoryTarget != ((ILoggerRepository)m_alias2repositoryMap[repositoryAlias])) { // Cannot redefine existing alias throw new InvalidOperationException("Repository [" + repositoryAlias + "] is already aliased to repository [" + ((ILoggerRepository)m_alias2repositoryMap[repositoryAlias]).Name + "]. Aliases cannot be redefined."); } } // Check if the alias is already mapped to a repository else if (m_name2repositoryMap.Contains(repositoryAlias)) { // Check if this is a duplicate of the current mapping if ( repositoryTarget != ((ILoggerRepository)m_name2repositoryMap[repositoryAlias]) ) { // Cannot define alias for already mapped repository throw new InvalidOperationException("Repository [" + repositoryAlias + "] already exists and cannot be aliased to repository [" + repositoryTarget.Name + "]."); } } else { // Set the alias m_alias2repositoryMap[repositoryAlias] = repositoryTarget; } } } #endregion Public Instance Methods #region Protected Instance Methods /// /// Notifies the registered listeners that the repository has been created. /// /// The repository that has been created. /// /// /// Raises the event. /// /// protected virtual void OnLoggerRepositoryCreatedEvent(ILoggerRepository repository) { LoggerRepositoryCreationEventHandler handler = m_loggerRepositoryCreatedEvent; if (handler != null) { handler(this, new LoggerRepositoryCreationEventArgs(repository)); } } #endregion Protected Instance Methods #region Private Instance Methods /// /// Gets the repository name and repository type for the specified assembly. /// /// The assembly that has a . /// in/out param to hold the repository name to use for the assembly, caller should set this to the default value before calling. /// in/out param to hold the type of the repository to create for the assembly, caller should set this to the default value before calling. /// is . private void GetInfoForAssembly(Assembly assembly, ref string repositoryName, ref Type repositoryType) { if (assembly == null) { throw new ArgumentNullException("assembly"); } try { LogLog.Debug(declaringType, "Assembly [" + assembly.FullName + "] Loaded From [" + SystemInfo.AssemblyLocationInfo(assembly) + "]"); } catch { // Ignore exception from debug call } try { // Look for the RepositoryAttribute on the assembly #if NETSTANDARD1_3 object[] repositoryAttributes = assembly.GetCustomAttributes(typeof(log4net.Config.RepositoryAttribute)).ToArray(); #else object[] repositoryAttributes = Attribute.GetCustomAttributes(assembly, typeof(log4net.Config.RepositoryAttribute), false); #endif if (repositoryAttributes == null || repositoryAttributes.Length == 0) { // This is not a problem, but its nice to know what is going on. LogLog.Debug(declaringType, "Assembly [" + assembly + "] does not have a RepositoryAttribute specified."); } else { if (repositoryAttributes.Length > 1) { LogLog.Error(declaringType, "Assembly [" + assembly + "] has multiple log4net.Config.RepositoryAttribute assembly attributes. Only using first occurrence."); } log4net.Config.RepositoryAttribute domAttr = repositoryAttributes[0] as log4net.Config.RepositoryAttribute; if (domAttr == null) { LogLog.Error(declaringType, "Assembly [" + assembly + "] has a RepositoryAttribute but it does not!."); } else { // If the Name property is set then override the default if (domAttr.Name != null) { repositoryName = domAttr.Name; } // If the RepositoryType property is set then override the default if (domAttr.RepositoryType != null) { // Check that the type is a repository if (typeof(ILoggerRepository).IsAssignableFrom(domAttr.RepositoryType)) { repositoryType = domAttr.RepositoryType; } else { LogLog.Error(declaringType, "DefaultRepositorySelector: Repository Type [" + domAttr.RepositoryType + "] must implement the ILoggerRepository interface."); } } } } } catch (Exception ex) { LogLog.Error(declaringType, "Unhandled exception in GetInfoForAssembly", ex); } } /// /// Configures the repository using information from the assembly. /// /// The assembly containing /// attributes which define the configuration for the repository. /// The repository to configure. /// /// is . /// -or- /// is . /// private void ConfigureRepository(Assembly assembly, ILoggerRepository repository) { if (assembly == null) { throw new ArgumentNullException("assembly"); } if (repository == null) { throw new ArgumentNullException("repository"); } // Look for the Configurator attributes (e.g. XmlConfiguratorAttribute) on the assembly #if NETSTANDARD1_3 object[] configAttributes = assembly.GetCustomAttributes(typeof(log4net.Config.ConfiguratorAttribute)).ToArray(); #else object[] configAttributes = Attribute.GetCustomAttributes(assembly, typeof(log4net.Config.ConfiguratorAttribute), false); #endif if (configAttributes != null && configAttributes.Length > 0) { // Sort the ConfiguratorAttributes in priority order Array.Sort(configAttributes); // Delegate to the attribute the job of configuring the repository foreach(log4net.Config.ConfiguratorAttribute configAttr in configAttributes) { if (configAttr != null) { try { configAttr.Configure(assembly, repository); } catch (Exception ex) { LogLog.Error(declaringType, "Exception calling ["+configAttr.GetType().FullName+"] .Configure method.", ex); } } } } if (repository.Name == DefaultRepositoryName) { // Try to configure the default repository using an AppSettings specified config file // Do this even if the repository has been configured (or claims to be), this allows overriding // of the default config files etc, if that is required. string repositoryConfigFile = SystemInfo.GetAppSetting("log4net.Config"); if (repositoryConfigFile != null && repositoryConfigFile.Length > 0) { string applicationBaseDirectory = null; try { applicationBaseDirectory = SystemInfo.ApplicationBaseDirectory; } catch(Exception ex) { LogLog.Warn(declaringType, "Exception getting ApplicationBaseDirectory. appSettings log4net.Config path ["+repositoryConfigFile+"] will be treated as an absolute URI", ex); } string repositoryConfigFilePath = repositoryConfigFile; if (applicationBaseDirectory != null) { repositoryConfigFilePath = Path.Combine(applicationBaseDirectory, repositoryConfigFile); } // Determine whether to watch the file or not based on an app setting value: bool watchRepositoryConfigFile = false; #if NET_2_0 || MONO_2_0 || MONO_3_5 || MONO_4_0 || NETSTANDARD1_3 Boolean.TryParse(SystemInfo.GetAppSetting("log4net.Config.Watch"), out watchRepositoryConfigFile); #else { string watch = SystemInfo.GetAppSetting("log4net.Config.Watch"); if (watch != null && watch.Length > 0) { try { watchRepositoryConfigFile = Boolean.Parse(watch); } catch (FormatException) { // simply not a Boolean } } } #endif if (watchRepositoryConfigFile) { // As we are going to watch the config file it is required to resolve it as a // physical file system path pass that in a FileInfo object to the Configurator FileInfo repositoryConfigFileInfo = null; try { repositoryConfigFileInfo = new FileInfo(repositoryConfigFilePath); } catch (Exception ex) { LogLog.Error(declaringType, "DefaultRepositorySelector: Exception while parsing log4net.Config file physical path [" + repositoryConfigFilePath + "]", ex); } try { LogLog.Debug(declaringType, "Loading and watching configuration for default repository from AppSettings specified Config path [" + repositoryConfigFilePath + "]"); XmlConfigurator.ConfigureAndWatch(repository, repositoryConfigFileInfo); } catch (Exception ex) { LogLog.Error(declaringType, "DefaultRepositorySelector: Exception calling XmlConfigurator.ConfigureAndWatch method with ConfigFilePath [" + repositoryConfigFilePath + "]", ex); } } else { // As we are not going to watch the config file it is easiest to just resolve it as a // URI and pass that to the Configurator Uri repositoryConfigUri = null; try { repositoryConfigUri = new Uri(repositoryConfigFilePath); } catch(Exception ex) { LogLog.Error(declaringType, "Exception while parsing log4net.Config file path ["+repositoryConfigFile+"]", ex); } if (repositoryConfigUri != null) { LogLog.Debug(declaringType, "Loading configuration for default repository from AppSettings specified Config URI ["+repositoryConfigUri.ToString()+"]"); try { // TODO: Support other types of configurator XmlConfigurator.Configure(repository, repositoryConfigUri); } catch (Exception ex) { LogLog.Error(declaringType, "Exception calling XmlConfigurator.Configure method with ConfigUri ["+repositoryConfigUri+"]", ex); } } } } } } /// /// Loads the attribute defined plugins on the assembly. /// /// The assembly that contains the attributes. /// The repository to add the plugins to. /// /// is . /// -or- /// is . /// private void LoadPlugins(Assembly assembly, ILoggerRepository repository) { if (assembly == null) { throw new ArgumentNullException("assembly"); } if (repository == null) { throw new ArgumentNullException("repository"); } // Look for the PluginAttribute on the assembly #if NETSTANDARD1_3 object[] configAttributes = assembly.GetCustomAttributes(typeof(log4net.Config.PluginAttribute)).ToArray(); #else object[] configAttributes = Attribute.GetCustomAttributes(assembly, typeof(log4net.Config.PluginAttribute), false); #endif if (configAttributes != null && configAttributes.Length > 0) { foreach(log4net.Plugin.IPluginFactory configAttr in configAttributes) { try { // Create the plugin and add it to the repository repository.PluginMap.Add(configAttr.CreatePlugin()); } catch(Exception ex) { LogLog.Error(declaringType, "Failed to create plugin. Attribute [" + configAttr.ToString() + "]", ex); } } } } /// /// Loads the attribute defined aliases on the assembly. /// /// The assembly that contains the attributes. /// The repository to alias to. /// /// is . /// -or- /// is . /// private void LoadAliases(Assembly assembly, ILoggerRepository repository) { if (assembly == null) { throw new ArgumentNullException("assembly"); } if (repository == null) { throw new ArgumentNullException("repository"); } // Look for the AliasRepositoryAttribute on the assembly #if NETSTANDARD1_3 object[] configAttributes = assembly.GetCustomAttributes(typeof(log4net.Config.AliasRepositoryAttribute)).ToArray(); #else object[] configAttributes = Attribute.GetCustomAttributes(assembly, typeof(log4net.Config.AliasRepositoryAttribute), false); #endif if (configAttributes != null && configAttributes.Length > 0) { foreach(log4net.Config.AliasRepositoryAttribute configAttr in configAttributes) { try { AliasRepository(configAttr.Name, repository); } catch(Exception ex) { LogLog.Error(declaringType, "Failed to alias repository [" + configAttr.Name + "]", ex); } } } } #endregion Private Instance Methods #region Private Static Fields /// /// The fully qualified type of the DefaultRepositorySelector class. /// /// /// Used by the internal logger to record the Type of the /// log message. /// private readonly static Type declaringType = typeof(DefaultRepositorySelector); private const string DefaultRepositoryName = "log4net-default-repository"; #endregion Private Static Fields #region Private Instance Fields private readonly Hashtable m_name2repositoryMap = new Hashtable(); private readonly Hashtable m_assembly2repositoryMap = new Hashtable(); private readonly Hashtable m_alias2repositoryMap = new Hashtable(); private readonly Type m_defaultRepositoryType; private event LoggerRepositoryCreationEventHandler m_loggerRepositoryCreatedEvent; #endregion Private Instance Fields } } #endif // !NETCF