Tuesday, February 2, 2010

Dependency Injection based System.Diagnostics TraceListener (Spring.NET) or how do you configure EventLogTraceListener with a log and machine name?

Long time ago when writing rolling XmlTraceListener for system.diagnostics I felt quite limited by the only choice of passing parameters via initialization string. Not talking about amount of unit tests accompanying the initialization string parsing!

Dissatisfied with standard system.diagnostics capabilities in this area, I decided to introduce a hint of dependency injection to make the configuration more flexible. The implementation is very simple and code can be found here: http://code.google.com/p/toolsdotnet/source/browse/trunk/Tools.Net/src/Tools.Logging.Ioc/IoCWrapperTraceListener.cs

IoCWrapperTraceListener is a very thin wrapper on top of whatever trace listener that you decide to use. A target trace listener is injected in the constructor:

public IocWrapperTraceListener(string initializationData)
            : this()
        {
            using (IApplicationContext ctx = ContextRegistry.GetContext())
            {
                
                traceListener =
                    (TraceListener) ctx.GetObject(initializationData);
            }
        }

and all of its methods are just wrapped by IoCWrapperTraceListener:

public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id,
                                        string format, params object[] args)
        {
            traceListener.TraceEvent(eventCache, source, eventType, id, format, args);
        }

Example configuration can be:

    <sharedListeners>
      <add initializeData="EventLogTraceListener" type="Tools.Logging.Ioc.IocWrapperTraceListener, Tools.Logging.Ioc" name="EvenLogListener" traceOutputOptions="DateTime">
      </add>    
    </sharedListeners>

Intentionally, I’m not using any of the custom listeners but System.Diagnostics.EventLogTraceListener just to show the benefits of DI here:

<object name="EventLogTraceListener"
type="System.Diagnostics.EventLogTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
singleton="true">
    <constructor-arg name="eventLog" type="System.Diagnostics.EventLog, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" ref="CmdExecEventLog" />
    <property name="Filter" ref="WarningTraceFilter" />
  </object>  
  <object name="CmdExecEventLog"
type="System.Diagnostics.EventLog, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
singleton="false">
    <constructor-arg type="string" name="logName" value="CmdRouter" />
    <constructor-arg type="string" name="machineName" value="." />
    <constructor-arg type="string" name="source" value="CmdRouter" />
  </object>
  <object name="WarningTraceFilter"
type="System.Diagnostics.EventTypeFilter, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
singleton="false">
    <constructor-arg name="level" value="Warning" />
  </object>

All the objects configured above are standard System.Diagnostics objects, but there is no need for any initialization string and you can configure ANY of the constructor parameters and properties. E.g. logName couldn’t be even passed in the initialization string as its function is limited in EventLogTraceListener as:

public EventLogTraceListener(string source)
{
    this.eventLog = new EventLog();
    this.eventLog.Source = source;
}

But now it can be configured via constructor injection of EventLog itself:

public EventLogTraceListener(EventLog eventLog) : base((eventLog != null) ? eventLog.Source : string.Empty)
{
    this.eventLog = eventLog;
}

Simple, but was quite helpful to me that time.

No comments: