Writing an Email Custom Trace Listener For a WCF Service

By | April 20, 2010

Now that our set of WCF services was approaching prime time, I wanted my team to be notified immediately upon any errors. I also wanted to piggy-back on the built-in tracing that we had already enabled during the early development process in order to track down initial connection issues.

There are scads of good references online for how to do this, such as here, here, and here. My implementation is decidedly bare-bones, but it provides a framework upon which one could build a more robust solution.

I’ll be using Visual 2010 RC for the below example.

Implementation

First I create a new class library to hold my custom trace listener. I rename Class1 to EmailTraceListener.

4-18-2010 12-38-40 PM

Next, import System.Diagnostics and inherit from TraceListener. Then click on the base class name, press CTRL+., and hit enter.

4-18-2010 12-50-39 PM

   1:  using System;
   2:  using System.Diagnostics;
   3:  
   4:  namespace MyCustomTraceListeners
   5:  {
   6:      public class EmailTraceListener : TraceListener
   7:      {
   8:          public override void Write(string message)
   9:          {
  10:              throw new NotImplementedException();
  11:          }
  12:  
  13:          public override void WriteLine(string message)
  14:          {
  15:              throw new NotImplementedException();
  16:          }
  17:      }
  18:  }

Visual Studio was nice enough to tell us exactly what we need to override. In the interests of design, let’s create a separate class to encompass the mailing functionality instead of stuffing it into the EmailTraceListener class. First I’ll define an interface for our desired email functionality.

   1:  namespace MyCustomTraceListeners
   2:  {
   3:      public interface ITraceEmailer
   4:      {
   5:          void SendTraceEmail(string message);
   6:  
   7:          string MailFrom { get; set; }
   8:          string MailTo { get; set; }
   9:          string Subject { get; set; }
  10:      }
  11:  }

The implementation is straightforward. The constructor tells us what we need for the class to function properly. The SendTraceEmail method is also straightforward, relying on proper configuration to function instead of properties in code. We’ll see this configuration closer to the end.

 
   1:  using System;
   2:  using System.Net.Mail;
   3:  
   4:  namespace MyCustomTraceListeners
   5:  {
   6:      public class TraceEmailer : ITraceEmailer
   7:      {
   8:          public TraceEmailer(string from, string to, string subject)
   9:          {
  10:              MailFrom = from;
  11:              MailTo = to;
  12:              Subject = subject;
  13:          }
  14:  
  15:          public void SendTraceEmail(string message)
  16:          {
  17:              using (MailMessage msg = new MailMessage(MailFrom, MailTo, Subject, message))
  18:              {
  19:                  SmtpClient smtp = new SmtpClient();
  20:                  smtp.Send(msg);
  21:              }
  22:          }
  23:  }

The property getters/setters are left out for brevity’s sake.

Event Listeners and Configuration

The next step is to incorporate our email class into the custom trace listener. In order to do this, we have to understand how our custom trace listener is being spun up by the framework. This is best demonstrated by creating a simple console app to drive our testing.

4-18-2010 1-18-53 PM

And the code.

   1:  using System;
   2:  using System.Diagnostics;
   3:  
   4:  namespace ConsoleTester
   5:  {
   6:      class Program
   7:      {
   8:          static TraceSource _mySource = new TraceSource("My Source");
   9:  
  10:          static void Main(string[] args)
  11:          {
  12:              _mySource.TraceInformation("test information message");
  13:              _mySource.TraceData(TraceEventType.Critical, 1, "some data");
  14:              Console.WriteLine("test output");
  15:  
  16:              Console.WriteLine("press any key to continue");
  17:              Console.ReadLine();
  18:          }
  19:      }
  20:  }

Nothing crazy going on here. Note the string we’re passing in to the TraceSource constructor on line 8. We’ll need this magic string to get our tester tied to the custom trace listener in the configuration file, which is where most of the craziness actually is going on. First we’ll start with the standard trace source section.

   1:  <system.diagnostics>
   2:      <sources>
   3:          <source name="My Source" switchName="defaultSwitch">
   4:  
   5:              <listeners>
   6:                  <!--<add name="Console"
   7:                           type="System.Diagnostics.ConsoleTraceListener" />-->
   8:  
   9:                  <add name="Email"
  10:                       type="MyCustomTraceListeners.EmailTraceListener,MyCustomTraceListeners"
  11:                           fromAddress ="[insert from address here]"
  12:                           toAddress = "[insert to address here]"
  13:                           subject = "sample trace subject line" />
  14:  
  15:              </listeners>
  16:  
  17:          </source>
  18:      </sources>
  19:  
  20:      <switches>
  21:          <add name="defaultSwitch" value="Information"/>
  22:      </switches>
  23:  </system.diagnostics>

Line 3 has our magic string from the testing app. We only need this here, when we hook our custom listener into the WCF service, we’ll rely on the built-in sources.

Line 3 also has a switch name, which indicates which switch to load up with this source. The switch definition is on lines 20-22. A switch is required in order for an event source to fire up correctly.

Each event source can have one or more listeners, with ours being defined on lines 5-15. The Console listener that is commented out can be handy for testing; if this is enabled, trace messages appear on the console as well. Our custom event listener is defined on lines 9-13. The type is the standard class identifier. The next three parts of the listener definition are custom properties we’ve associated with our listener. We’ll see how to access those from our listener in the next section.

So back the point of all of this, figuring out how our custom listener is being spun up based on configuration file settings. When line 3 in the console tester is executed, the framework attempts to load up each defined listener for the event source in question. It looks for a DLL named MyCustomTraceListeners somewhere in it’s reference paths, and when it finds it, it looks for a class named EmailTraceListener in the MyCustomTraceListeners namespace and instantiates it with the specified Name property. The main point of all of this is that we can’t get fancy with the constructor, which is important to remember to remember when we tie our TraceEmailer class to the EmailTraceListener.

We need one last piece in our configuration file in order for the console tester to be functional, and that is the mail stuff.

   1:  <system.net>
   2:      <mailSettings>
   3:          <smtp deliveryMethod="Network">
   4:              <network host="smtp.ahost.com"
   5:                 userName="yourUsername"
   6:                 password="yourPassword"/>
   7:          </smtp>
   8:      </mailSettings>
   9:  </system.net>

The mail piece is as simple as it could be. If you require more fanciness, there are scads of references for that online as well. If you use one of the simple freeware smtp servers that are out there, you won’t even need a user name or password.

Finishing Up

Now we know we can’t get fancy with the constructor of our EmailTraceListener, which means we can’t use constructor injection. Normally we’d use an IoC container in this scenario, but I don’t want to get that fancy, so we’ll just spin up a TraceEmailer inside of EmailTraceListener on demand.

First I add a read-only property with backing store to EmailTraceListener for its TraceEmailer.

   1:  public ITraceEmailer CurrentTraceEmailer
   2:  {
   3:      get
   4:      {
   5:          if (_traceEmailer == null)
   6:          {
   7:              _traceEmailer = new TraceEmailer(
   8:                  Attributes["fromAddress"],
   9:                  Attributes["toAddress"],
  10:                  Attributes["subject"]);
  11:          }
  12:  
  13:          return _traceEmailer;
  14:      }
  15:  }

Note the reference to an Attributes array on lines 8-10. These are precisely the attributes we defined in our configuration file up above, and are accessed by adding an additional override to our EmailTraceListener class.

   1:  protected override string[] GetSupportedAttributes()
   2:  {
   3:      return new string[] { "fromAddress", "toAddress", "subject" };
   4:  }

All we’re doing initializing an array of strings which map to the attributes we want to declare in the listener configuration.

Finally, we implement the Write and WriteLine methods.

   1:  public override void Write(string message)
   2:  {
   3:      CurrentTraceEmailer.SendTraceEmail(message);
   4:  }
   5:  
   6:  public override void WriteLine(string message)
   7:  {
   8:      CurrentTraceEmailer.SendTraceEmail(message);
   9:  }

And that’s it. Assuming you have your configuration set up correctly (primarily with respect to the SMTP server), you should be able to run the console tester and see the emails go through.

Full source can be found on CodePlex here.

Integrating With a WCF Service

Integrating the above custom trace listener with our WCF service was a matter of copying over the DLLs into the service directory (we are self-hosting) and adding the same snippet from the above configuration file into the windows service configuration file.

   1:  <add name="Email"
   2:           type="MyCustomTraceListeners.EmailTraceListener,MyCustomTraceListeners"
   3:           fromAddress ="[insert from address here]"
   4:           toAddress = "[insert to address here]"
   5:           subject = "sample trace subject line" />

We’ll see how well the notification works in the coming weeks as we roll to production and start experiencing some non-trivial load.