Extending Hiearchy and XmlHierarchyConfigurator might not be the best way to go 
about adding in additional config file parsing cabilities. If you wanted to add 
additional features to the parsing code with the extension method you would 
have to write a new Hiearchy and XmlHierarchyConfigurator for each new 
extension. That doesn't lend itself well to sharing extensions with other 
people.

XmlHierarchyConfigurator does interesting things while its parsing the xml 
config file. If we expose those interesting events, outsiders could attach and 
participate in the parsing process. If we add support for parsing plugin nodes 
in the config file via a ParsePlugin method (code is similiar to how other top 
level elements like appenders, loggers, renderers, etc. are processed):

        protected void ParsePlugin(XmlElement pluginElement)
        {
            string pluginName = pluginElement.GetAttribute(NAME_ATTR);
            string typeName = pluginElement.GetAttribute(TYPE_ATTR);

            IPlugin plugin = (IPlugin)OptionConverter.InstantiateByClassName(
                typeName, 
                new object[] {pluginName}, 
                typeof(IPlugin), 
                null);

            m_hierarchy.PluginMap.Add(plugin);

            OnPluginPostAttach(plugin, pluginElement);
        }

we could change our configuration xml snippet to this:

<plugin name="FlushAdoNetAppenders" 
type="Company.FlushBufferingAppendersPlugin">
 <interval value="10000" />
 <appender-ref ref="AdoNetAppender" />
</plugin>
<plugin name="FlushBufferingAppenders" 
type="Company.FlushBufferingAppendersPlugin">

 <interval value="5000" />
 <appender-ref ref="BufferingAppender1" />
 <appender-ref ref="BufferingAppender2" />

</plugin>

Inside of IPlugin.Attach, we have a reference to the parent repository. With a 
minor change to Hierarchy (making its configurator a public property) we can 
get the configurator and attach to its PluginPostAttach. Notice that the 
PluginPostAttach event will send the XmlElement as part of the EventArgs so the 
plugin can process its custom xml configuration:

        public override void Attach(ILoggerRepository repository)
        {
            base.Attach(repository);

            XmlHierarchyConfigurator xmlHierarchyConfigurator = 
                GetXmlHierarchyConfigurator(repository);

            if (xmlHierarchyConfigurator != null)
            {
                xmlHierarchyConfigurator.PluginPostAttach += new 
PluginEventHandler(xmlHierarchyConfigurator_PluginPostAttach);
            }
        }

The plugin is responsible for parsing the XmlElement. If the plugin has 
properties that follow the standard log4net property conventions you'd need to 
loop through each of the child elements set the elements value to the matching 
property:

        private void xmlHierarchyConfigurator_PluginPostAttach(object sender, 
PluginXmlElementEventArgs e)
        {
            XmlHierarchyConfigurator xmlHierarchyConfigurator = 
                (XmlHierarchyConfigurator)e.RepositoryConfigurator;

            foreach (XmlElement currentElement in e.XmlElement.ChildNodes)
            {
                xmlHierarchyConfigurator.SetParameter(currentElement, this);
            }
        }

Using our FlushAdoNetAppenders example from above, that code should take care 
of setting the plugin's Interval property. For more complex settings (like 
appender-ref nodes) the code might look something like this:

        private void xmlHierarchyConfigurator_PluginPostAttach(object sender, 
PluginXmlElementEventArgs e)
        {
            XmlHierarchyConfigurator xmlHierarchyConfigurator = 
                (XmlHierarchyConfigurator)e.RepositoryConfigurator

            foreach (XmlNode currentNode in e.XmlElement.ChildNodes)
            {
                if (currentNode.NodeType == XmlNodeType.Element)
                {
                    XmlElement currentElement = (XmlElement)currentNode;

                    if (currentElement.LocalName == "appender-ref")
                    {
                        BufferingAppenderSkeleton bufferingAppender = 
                            
(BufferingAppenderSkeleton)xmlHierarchyConfigurator.FindAppenderByReference(currentElement);

                        if (bufferingAppender != null)
                        {
                            bufferingAppenders.Add(bufferingAppender);
                        }
                    }
                    else
                    {
                        xmlHierarchyConfigurator.SetParameter(currentElement, 
this);
                    }
                }
            }
        }

At this point we'd have a plugin that has a list of buffered appenders it 
should flush at a given interval. We can access this information from code:

FlushBufferingAppendersPlugin flusher = 
    
(FlushBufferingAppendersPlugin)LogManager.GetRepository().PluginMap["FlushAdoNetAppenders"];
Console.WriteLine("Plugin will flush [{0}] appenders every [{1}] 
milliseconds.", flusher.Appenders.Count, flusher.Interval);

The plugin's timer could be started in the Hierarchy's ConfigurationChanged 
event:

        private void hierarchy_ConfigurationChanged(object sender, 
System.EventArgs e)
        {
            if (timer != null)
            {
                timer.Stop();
            }

            if (Interval > 0)
            {
                timer = new Timer();
                timer.Interval = Interval;
                timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
                timer.Start();
            }
        }

----- Original Message ----
From: Ron Grabowski <[EMAIL PROTECTED]>
To: [email protected]
Sent: Saturday, December 23, 2006 1:54:21 PM
Subject: Re: XmlHierarchyConfigurator [from log4net-users]

If I wanted to add this additional snippet to my configuration which would 
automatically call Flush on some buffered appenders at the given interval:

<flushBufferingAppendersTimer>
    <interval value="5000" />
    <appender-ref ref="BufferingAppender1" />
    <appender-ref ref="BufferingAppender2" />
</flushBufferingAppendersTimer>

my code would look something like this (I need two classes because the 
Hierarchy and the way its configured are currently two seperate classes):

[snip]

You could write something similiar to automatically delete, move, compress, 
etc. old log files at a given interval on a different thread within log4net 
without the application having to worry about that.

The code is quite long :-( If things were done with plugins it would be 
possible to retrieve the plugin from the repository and change its properties 
during the application's lifetime similiar to how you can retrieve an 
IHttpModule by name and call methods on it:

// might appear in an admin area for an ASP.Net application
GZippedPageOutput gZippedPageOutput = 
    (GZippedPageOutput)Context.ApplicationInstance.Modules["GZippedPageOutput"];
gZippedPageOutput.CompressionLevel = 2;

// might appear on an admin screen for your application
FlushBufferingAppendersPlugin flusher = 
    
(FlushBufferingAppendersPlugin)LogManager.GetRepository().PluginMap["FlushBufferingAppendersPlugin"];
// calls Stop, sets Timer.Interval to the new value, then calls Start
flusher.SetInterval(10000);

The plugin architecture would allow you to add multiple plugins (compress old 
log files, flush buffers at a given internval, retrieve logging statistics, 
etc.) and easily retrieve them by name. Going back my recent post on 
plugins...I can't figure out a good way to allow plugins to hook into the 
configuration process parse the raw configuration file.

Comments?


Reply via email to