Creating a Custom Filter for Logging Application Block

Topics: Enterprise Library Core, Logging Application Block
Jul 6, 2007 at 3:43 PM
Hi all,

I am using a TextFormatter to write all error events (retrieved from a MSMQ) to a event log but I want to add custom logic to write only error events that have a Event ID greater than 5999. So I created the following class:

using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Logging.Filters;
using Microsoft.Practices.EnterpriseLibrary.Logging.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;

ConfigurationElementType(typeof(CustomLogFilterData))
public class CustomEventFilter : LogFilter
{
public CustomEventFilter() : base("CustomEventFilter") { }

public override bool Filter(Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry log)
{
return (log.EventId > 5999);
}
}

and built it into a dll called RKDEventFilter using a class library project.


I added the following into my config file (done using the Enterprise Library Configuration tool):

<logFilters>
<add type="CustomEventFilter, RKDEventFilter, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="RKD Critical Events Filter" />
</logFilters>


but I am stuck with Enterprise Library not being able to instatiate my custom filter class using reflection. The error I get is:


Exception Information Details:
======================================
Exception Type: System.MissingMethodException
Message: Constructor on type 'CustomEventFilter' not found.
Data: System.Collections.ListDictionaryInternal
TargetSite: System.Object CreateInstanceImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo, System.Object[])
HelpLink: NULL
Source: mscorlib

StackTrace Information Details:
======================================
at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
at System.Activator.CreateInstance(Type type, Object[] args)
at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.CustomProviderAssembler`3.Assemble(IBuilderContext context, TConfiguration objectConfiguration, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.AssemblerBasedObjectFactory`2.Create(IBuilderContext context, TConfiguration objectConfiguration, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
at Microsoft.Practices.EnterpriseLibrary.Logging.LogWriterStructureHolderCustomFactory.CreateObject(IBuilderContext context, String name, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
at Microsoft.Practices.EnterpriseLibrary.Logging.LogWriterCustomFactory.CreateObject(IBuilderContext context, String name, IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.ConfiguredObjectStrategy.BuildUp(IBuilderContext context, Type t, Object existing, String id)
at Microsoft.Practices.ObjectBuilder.BuilderStrategy.BuildUp(IBuilderContext context, Type typeToBuild, Object existing, String idToBuild)
at Microsoft.Practices.ObjectBuilder.SingletonStrategy.BuildUp(IBuilderContext context, Type typeToBuild, Object existing, String idToBuild)
at Microsoft.Practices.ObjectBuilder.BuilderStrategy.BuildUp(IBuilderContext context, Type typeToBuild, Object existing, String idToBuild)
at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.ConfigurationNameMappingStrategy.BuildUp(IBuilderContext context, Type t, Object existing, String id)
at Microsoft.Practices.ObjectBuilder.BuilderBase`1.DoBuildUp(IReadWriteLocator locator, Type typeToBuild, String idToBuild, Object existing, PolicyList[] transientPolicies)
at Microsoft.Practices.ObjectBuilder.BuilderBase`1.BuildUp(IReadWriteLocator locator, Type typeToBuild, String idToBuild, Object existing, PolicyList[] transientPolicies)
at Microsoft.Practices.ObjectBuilder.BuilderBase`1.BuildUpTTypeToBuild(IReadWriteLocator locator, String idToBuild, Object existing, PolicyList[] transientPolicies)
at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.EnterpriseLibraryFactory.BuildUpT(IReadWriteLocator locator, IConfigurationSource configurationSource)
at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.EnterpriseLibraryFactory.BuildUpT(IConfigurationSource configurationSource)
at Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder.EnterpriseLibraryFactory.BuildUpT()
at Microsoft.Practices.EnterpriseLibrary.Logging.MsmqDistributor.MsmqListener..ctor(DistributorService distributorService, Int32 timerInterval, String msmqPath)
at Microsoft.Practices.EnterpriseLibrary.Logging.MsmqDistributor.DistributorService.CreateListener(DistributorService distributorService, Int32 timerInterval, String msmqPath)
at Microsoft.Practices.EnterpriseLibrary.Logging.MsmqDistributor.DistributorService.InitializeComponent()



Anyone have any ideas as to what I am doing wrong?


Here is the full config for my MSMQ Distributor service:

<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<configSections>
<section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<section name="msmqDistributorSettings" type="Microsoft.Practices.EnterpriseLibrary.Logging.MsmqDistributor.Configuration.MsmqDistributorSettings, MsmqDistributor"/>
</configSections>
<loggingConfiguration name="Logging Application Block" tracingEnabled="true" defaultCategory="" logWarningsWhenNoCategoriesMatch="true">
<listeners>
<add source="Enterprise Library Logging" formatter="Text Formatter" log="APP_LOG" machineName="." listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null" traceOutputOptions="None" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null" name="Formatted EventLog TraceListener"/>
</listeners>
<formatters>
<add template="..." type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null" name="Text Formatter"/>
</formatters>
<logFilters>
<add type="CustomEventFilter, RKDEventFilter, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="RKD Critical Events Filter"/>
</logFilters>
<specialSources>
<allEvents switchValue="All" name="All Events"/>
<notProcessed switchValue="All" name="Unprocessed Category"/>
<errors switchValue="Error" name="Logging Errors & Warnings">
<listeners>
<add name="Formatted EventLog TraceListener"/>
</listeners>
</errors>
</specialSources>
</loggingConfiguration>
<msmqDistributorSettings msmqPath="FormatName:DIRECT=TCP:10.90.91.55\private$\johntest" queueTimerInterval="1000" serviceName="MsmqDistributor-johntest"/>
</configuration>

Jul 6, 2007 at 3:58 PM
Do I need to specify dependent assemblies in the config file some how? I ask because when I tried to load the contents of the DLL via reflection in code I was required to pre load all referenced assemblies.
Jul 6, 2007 at 6:06 PM
Hi John,

When you define a "custom provider" like your filter, where by "custom" we mean "one that will not define its own strongly-typed configuration object and will rely on the built-in property-bag", your provider type is expected to have a constructor that takes a NameValueCollection instance as its only parameter; this collection will hold the values from the configuration file as strings. In your case there doesn't seem to be any configuration for your object but you still need to provide a constructor with that signature, even if it's going to be ignored.

You can also use the application block factory, which will write most of the plumbing code for you.

Regarding your question about assemblies, you need to make sure the referenced assemblies are available at runtime but you don't need to specify them in the configuration file.

Hope this helps,
Fernando
Jan 23, 2008 at 5:34 PM
I had the same issue and Fernando's hints allowed me to solve it. I was trying to add a custom formatter. Here's how my constructor looks after following Fernando's advice:

public TextFormatterMgamCustom( NameValueCollection nameValueCollection )
: base( nameValueCollection.Get( "template" )) { }

Since my custom formatter is derived from TextFormatter, my constructor needs to call base( string template ). So I must get the template from the nameValueCollection.

The nameValueCollection can be populated using the "Edit Enterprise Library Tool". When editing your app.config file, add your new item (custom filter, custom formatter, etc). The properties for your new custom item will have an Attributes property. Click on its value which displays as "(Collection)" and then click on the ellipsis. This will open a Collection Editor with which you can enter your name / value collection. (Editing this collection and clicking OK does not seem to notify the config editor that app.config needs to be saved. (No asterisk next to its name). So, be sure to edit some other value to force the config editor to save your name / value changes)

In my case, to add a custom formatter, my name / value collection needs an entry with a key of "template" and a value like:
"{timestamp(local:T)} - {customCategory} - {message}".

This works beautifully. The custom formatter can now be used in the app.config editor just like a built-in formatter without having to know anything about its details.
May 27, 2008 at 8:31 PM


Bryan wrote:
I had the same issue and Fernando's hints allowed me to solve it. I was trying to add a custom formatter. Here's how my constructor looks after following Fernando's advice:

public TextFormatterMgamCustom( NameValueCollection nameValueCollection )
: base( nameValueCollection.Get( "template" )) { }

Since my custom formatter is derived from TextFormatter, my constructor needs to call base( string template ). So I must get the template from the nameValueCollection.

Thanks!  This is *exactly* the information I needed to get this working.