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?