Rolling Flat File Trace Listener with Purging for Enterprise Library
I have used Microsoft's Enterprise Library (EL) from Version 1 back in the day. I have to say I love EL, I have read lots of blog entries telling me that there are better components to do some of the parts of EL but EL has some very compelling reasons to use it
- It has a lot of components of an application that I know I *should* write but that are just overhead. If I did write them I would do it in a stripped down manner so that I could get onto the *real* purpose of the application.
- Because the components are just there I do use it, for example its been years since I haven't fully instrumented my code, which has saved me so much grief.
- When it comes down to thread safe caching code I think Tom Hollander's team are smarter then me.
So one of the key parts that I use a lot it the logging block to instrument my code and a key components of logging is a trace listener. A trace listener is a repository for logging information and there are many options available, for example database, event log etc. Many of my professional and personal applications exist in a hosted environment where the options for recording logging information are limited and writing to a flat file is a solution that works well in this environment. A rolling listener writes to a group of files, moving onto a new file when the previous one gets too big.
When this web site was based around .NET v1 I used Hisham Baz's rolling flat file trace listener and professionally when using EL v2 we used Erwyn van der Meer's trace listener which provided similar functionality. When we got to version 3 (and 4) a rolling flat file was included in EL by Microsoft. However unfortunately the listener supplied by Microsoft did not include purging. The older listeners would delete old files from the group of files being written to. I spend some time looking for a listener and in the end gave up and wrote one. Its was fun seeing how EL was designed to be extended.
The first step is to download and install the Guidance Automation Extensions and Guidance Automation Toolkit, After installing the GAX / GAT and EL then you should see new additional project types in Visual Studio. I created this provider library for EL 3.1 in Visual Studio 2005 (and the screen shots are this environment however the download has been rebuilt to work with Visual Studio 2008 and EL4 and should work in either environment.
I created a new provider library, and called it AAD.Providers, because I created it as part of a project I co-wrote with Andrew Trevarrow.
The solution that is created is made up of two assemblies a ProviderLibrary that contains the objects that we are adding to EL (a RollingFlatFileTraceListener in this case) and a Configuration.Design assembly that is used to extend the EL Configuration Editor GUI
In this case I have created the RollingFlatFileTraceListener by using GAX/GAT Application Block Software Factory to add a trace listener into the provider library. This class derives from
EnterpriseLibrary.Logging.TraceListeners.RollingFlatFileTraceListener
as this listener does everything I want to do except purge.
I found it very useful to have the source code for the class I was deriving from as it meant that I could get a real feel for how other types of listeners derived from each other. The RollingFlatFileTraceListener is pretty straightforward, The constructor has the same parameters as the base class with the addition of any extra configuration, in this case the number of files to keep, and then we override TraceData which calls the base class and then works out when to purge any extra files.
public override void TraceData( TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data) { // output data rolling if necessary base.TraceData(eventCache, source, eventType, id, data); // purge excess if (_nFilesToKeep > 0) PurgeExcessFIles(); } private void PurgeExcessFIles() { string actualFileName = ((FileStream)((StreamWriter)this.Writer).BaseStream).Name; string directory = Path.GetDirectoryName(actualFileName); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(actualFileName); string extension = Path.GetExtension(actualFileName); string pattern = fileNameWithoutExtension + "*" + extension; DirectoryInfo dir = new DirectoryInfo(directory); if (!dir.Exists) { return; } FileInfo[] files = dir.GetFiles(pattern); int filesToPurge = files.Length - _nFilesToKeep; if (filesToPurge <= 0) { return; } // we must sort the folder rather than // relying on the files being written in the correct order IComparer fileComparer = new CompareFileByFilenameTimeStamp( fileNameWithoutExtension, _timestampFormat); Array.Sort(files, fileComparer); foreach (FileInfo file in files) { if ( CompareFileByFilenameTimeStamp.ConvertTimestampToTime( file.Name, fileNameWithoutExtension, _timestampFormat ) < DateTime.MaxValue ) { // only delete files that match the pattern file.Delete(); filesToPurge--; if (filesToPurge < 1) break; } } }
If this was all we wanted to do it would be very simple however I wanted to try and get the designer to work as well.
The next step is to create a Data class (in the Configuration folder of the ProvidersLibrary). This class hold the data needed to configure the listener. In our case we derived from
EnterpriseLibrary.Logging. Configuration.RollingFlatFileTraceListenerData
and add the extra property FilesToKeep. There is a lot of boilerplate code in this class to wrap the data but it is all pretty much straightforward.
Finally we add a RollingFlatFileTraceListenerNode into the Configuration.Design project. This class also inherits from the corresponding class in EL
EnterpriseLibrary.Logging. Configuration.Design.TraceListeners.TraceListenerNode
Up until this point I had managed to derived from the EL class and just add in my extra property however at this point I did need to copy the property definitions into the configuration design class. The properties are all decorated to control how the EL Configuration UI with handle them.
The Register method of the CommandRegistrar class is used to hook up the RollingFlatFileTraceListenerNode to the GUI commands like this
public override void Register() { AddRollingFlatFileTraceListenerNodeCommand(); AddDefaultCommands(typeof(RollingFlatFileTraceListenerNode)); } private void AddRollingFlatFileTraceListenerNodeCommand() { AddMultipleChildNodeCommand( Resources.RollingFlatFileTraceListenerNodeUICommandText, Resources.RollingFlatFileTraceListenerNodeUICommandLongText, typeof(RollingFlatFileTraceListenerNode), typeof(Microsoft.Practices.EnterpriseLibrary.Logging. Configuration.Design.TraceListeners.TraceListenerCollectionNode) ); }
The NodeMapRegistrar class is used to hook up the RollingFlatFileTraceListenerNode and its RollingFlatFileTraceListenerData into the GUI designer
public override void Register() { AddMultipleNodeMap( Resources.RollingFlatFileTraceListenerNodeUICommandText, typeof(RollingFlatFileTraceListenerNode), typeof(AAD.ProvidersLibrary.Configuration. RollingFlatFileTraceListenerData) ); }
The TraceListenerData property on the tracelistener node links the node in the GUI to the TraceListenerData class. The constructor of the TraceListenerData sets this.Type to be the TraceListener, This linkage mechanism ensures that the correct type are written to the xml configuration file for the application.
To make the new TraceListener available in the EL Configuration GUI you must copy the ProviderLibrary and the Configuration.Design assembly to the folder that contains the EL Configuration GUI , usually
C:\Program Files\Microsoft Enterprise Library 4.0 - May 2008
The TraceListener configuration GUI looks like this.
and the XML that configures the application looks like this.
<loggingconfiguration name="Logging Application Block" logwarningswhennocategoriesmatch="true" defaultcategory="Logging" tracingenabled="false"> <listeners> <add name="AAD Extended Rolling Flat File Trace Listener" type="AAD.ProvidersLibrary.RollingFlatFileTraceListener, AAD.ProvidersLibrary, Version=1.0.0.11, Culture=neutral, PublicKeyToken=null" traceoutputoptions="None" listenerdatatype="AAD.ProvidersLibrary.Configuration.RollingFlatFileTraceListenerData, AAD.ProvidersLibrary, Version=1.0.0.11, Culture=neutral, PublicKeyToken=null" footer="" header="" formatter="Text Formatter" rollinterval="None" rollfileexistsbehavior="Overwrite" timestamppattern="yyyy-MM-dd HH.mm.ss" rollsizekb="1024" filename=".\application.log" filestokeep="5" />
You need to deploy the ProviderLibrary assembly with the application but not the Configuration.Design assembly.
You can download the source for the TraceListener, this solution is for VS2008, EL4 if you want the old version for EL3.1 then email me and I will send it to you.