Custom Trace Filter for a WCF Service

By | April 26, 2010

My team now has a set of Net.TCP WCF services for some of our core business functions, with email notifications whenever any errors arise. Given our penchant for bug-free code, the emails were few and far between (cough). When an error did occur however, we would get roughly six emails for every “legitimate” error, which made the initial assessment troublesome to say the least. To combat this we wrote a bare bones custom trace filter, which I’ll be reviewing below.

The code for this builds on the the email custom trace listener I reviewed in an earlier post, and can be found on CodePlex here. This is the same code base I’ll be building on for our filter.

Implementation

Creating a custom trace filter is very straightforward, simply requiring you to inherit from TraceFilter and implement the required method. Following sound design principles (!), I’ll add a new project for our new filter, calling it CustomTraceFilters. Additionally, I’ll create our implementation of TraceFilter and call it SimpleRegexFilter.

4-26-2010 7-27-32 PM

using System;
using System.Diagnostics;
namespace CustomTraceFilters
{
   public class SimpleRegexFilter : TraceFilter
   {
      public override bool ShouldTrace(
         TraceEventCache cache,
         string source,
         TraceEventType eventType,
         int id,
         string formatOrMessage,
         object[] args,
         object data1,
         object[] data)
      {
         throw new NotImplementedException();
      }
   }
}

As is obvious from the above, we’re going to implement a simple regex filter so we can filter out some of the more mundane System.ServiceModel exceptions that come standard with a Net.TCP self-hosted WCF service.

Filter Implementation

We’ll create a very bare bones filter consisting of a list of inclusion and exclusion regexes. The class will take text, and return true if the specified filter is satisfied, false otherwise. Satisfying the filter requires not being excluded by any of the exclusion regexes, and matching at least one of the inclusion regexes if any are specified. The interface and subsequent implementation are below.

using System;
using System.Collections.Generic;
namespace CustomTraceFilters
{
   interface ITraceRegexFilter
   {
      List<String> Exclusions { get; }
      List<String> Inclusions { get; }
      bool DoesTextPassFilter(string text);
   }
}
public TraceRegexFilter(string exclusions, string inclusions)
{
   if (String.IsNullOrEmpty(inclusions))
   {
      _inclusions = new List<string>();
   }
   else
   {
      _inclusions = inclusions.Split(",".ToCharArray()).ToList<string>();
   }
   if (String.IsNullOrEmpty(exclusions))
   {
      _exclusions = new List<string>();
   }
   else
   {
      _exclusions = exclusions.Split(",".ToCharArray()).ToList<string>();
   }
}

public bool DoesTextPassFilter(string text)
{
   if (String.IsNullOrEmpty(text)) return true;

   foreach (string e in Exclusions)
   {
      if (Regex.IsMatch(text, e))
      {
         return false;
      }
   }

   foreach (string i in Inclusions)
   {
      if (Regex.IsMatch(text, i))
      {
         return true;
      }
   }
   return !(Inclusions.Count > 0);
}

I’ve omitted the backing properties for brevity. Below is how our solution looks after adding these objects to our solution.

Return to the Filter

Now we have to lace the regex filter into the actual bare bones custom trace filter. You’ll notice that our overridden ShouldTrace method has a good number of parameters. For simple traces, as in our console test app, most of these are null. In more complicated scenarios more of these are filled. A detailed explanation of each of these is the subject of another post; for the purposes of our bare bones custom trace filter, we will only be applying our regex filter to the formatOrMessage and data1 properties. The code is below. The basic intent is that if one of these items is not null, we include it’s filter passage in the final determination of whether or not the current trace passes the filter. If both items are null, we consider the current trace as having failed the filter.


public override bool ShouldTrace(
   TraceEventCache cache,
   string source,
   TraceEventType eventType,
   int id,
   string formatOrMessage,
   object[] args,
   object data1,
   object[] data)
{
   bool? messagePassed = null;
   if (formatOrMessage != null)
   {
      messagePassed = _filter.DoesTextPassFilter(formatOrMessage);
   }
   bool? dataPassed = null;
   if (data1 != null)
   {
      dataPassed = _filter.DoesTextPassFilter(data1.ToString());
   }
   // don't want a trace if both of the filter targets are null
   if (messagePassed == null && dataPassed == null) return false;

   if (messagePassed == null)
   {
      return dataPassed ?? true;
   }
   else if (dataPassed == null)
   {
      return messagePassed ?? true;
   }
   else
   {
      return (dataPassed.Value || messagePassed.Value);
   }
}

Finally, we have to pull the inclusion and exclusion regex lists from somewhere. In keeping with our bare bones tradition, we’ll just pull them from the app config file in the SimpleRegexFilter constructor.

public SimpleRegexFilter()
{
   string exclusions = ConfigurationSettings.AppSettings["simple regex filter exclusion list"];
   string inclusions = ConfigurationSettings.AppSettings["simple regex filter inclusion list"];
   _filter = new TraceRegexFilter(exclusions, inclusions);
}

Putting it Together

Now that we have all this pretty code, we need to wire it into our application so we stop receiving six messages per error. To hook our filter in, make sure the filter DLL is in the application directory and modify the tracing configuration as below, which is taken from the console tester included in the solution on CodePlex.

<listeners>
   <add name="Console"
      type="System.Diagnostics.ConsoleTraceListener">
      <filter type="CustomTraceFilters.SimpleRegexFilter,CustomTraceFilters" />
   </add>
</listeners>

Play around with the filter’s exclusions and inclusions to get a feel for how they work. We’ll get more sophisticated with this in the coming weeks.