Exception Shielding #6: Exception Logging Implementation

Our Exception Shielding design sets the foundation to implement exception logging. This is a very important feature on any business application.

Our CustomersService architecture requires exception logging at two different levels:

  • Inside the service implementation, if the Create operation raises any exception, especially those that aren't handled and returned to the client as faults.
  • In the service client implementation, in case any unexpected error is raised, in particular executing service requests.

Enterprise Library Logging Application Block

The Microsoft Enterprise Library provides a set of application blocks (reusable code) that support the implementation of a number of common features in enterprise applications, from configuration to caching.

In fact, this library also includes a Exception Handling block that was designed exactly to implement exception shielding. I choose not use it because I disagree with the idea that how exceptions are handled by an application can (or should) be "changed" by the user (or IT for that matter) in configuration. I see that behavior as a significant part of the application design that is the architect's role to define.

The Logging application block, on the other hand, is very useful in this context as it allows the user to customize how the application's logging entries will be stored.

Having said this, the first piece of our "exception logging service" is a class that interacts with EntLib to perform logging. The implementation is very simple. The Logger class provides a method to log exceptions:

/// <summary>
/// Logs an exception.
/// </summary>
/// <param name="ex">Exception to log.</param>
/// <param name="priority">Log entry priority.</param>
/// <param name="eventId">Log entry event identifier.</param>
/// <param name="severity">Log entry severity.</param>
public static void Exception(Exception ex,
   int priority, int eventId, TraceEventType severity)
{
   if (ex == null) throw new ArgumentNullException("ex");
   WriteEntry(ex.ToString(), priority, eventId,
   severity, CategoryHandledExceptions, TitleHandledExceptions);
}
/// <summary>
/// Writes a log entry.
/// </summary>
/// <param name="message">Log message.</param>
/// <param name="priority">Entry priority.</param>
/// <param name="eventId">Entry event id.</param>
/// <param name="severity">Entry severity</param>
/// <param name="category">Entry category.</param>
/// <param name="title">Entry title.</param>
private static void WriteEntry(string message,
   int priority, int eventId, TraceEventType severity,
   string category, string title)
{
   try
   {
      LogEntry entry = new LogEntry();
      entry.Categories.Add(category);
      entry.TimeStamp = DateTime.Now;
      entry.EventId = eventId;
      entry.Message = message;
      entry.Priority = priority;
      entry.Severity = severity;
      entry.Title = title;
      Logger.Write(entry);
   }
   catch
   {
     // Ignore errors
   }
}

Exceptions will be logged according to the configuration defined for EntLib Logging block in the "Exceptions" (CategoryHandledExceptions) category. This is import to understand how the log entries will be stored on the service and on the client.

Logging Exceptions in the Service

In the CustomersServiceAdapter we have defined a private method that is responsible for handling all the exceptions raised in the service implementation:

private static void HandleException(Exception ex,
   int errorId, string errorMessage)
{
   // Log exception

   ExceptionsHandler.LogException(ex);

   // Raise fault

   CustomersServiceFault fault = new CustomersServiceFault();
   fault.ErrorId = errorId;
   fault.ErrorMessage = errorMessage;
   throw new FaultException<CustomersServiceFault>(
      fault, new FaultReason(new FaultReasonText(
      fault.ErrorMessage, CultureInfo.CurrentCulture)));
}

The ExceptionsHandler does nothing more here than calling our Logger service to log the exception:

/// <summary>
/// Logs an exception.
/// </summary>
/// <param name="ex">Exception to log.</param>
public static void LogException(Exception ex)
{
   Log.Exception(ex);
}

Using the Enterprise Library Configuration tool we have setup the logging block to log all entries in the Exceptions category in a flat log file:

Logging Application Block Configuration

Flat Log File Configuration

This log file is stored in a folder named "Logs" on the application server web. Notice that this log file holds all the exception details and acts as the primary source of troubleshooting information in case of any error:

Application Server Log

NOTE: We choose to log in a text file to overcome security issues raised when writing to the Event Log in a application hosted in IIS.

Logging Exceptions in the Client

The client application, on the other hand, works pretty much the same except the errors need to presented to the user (in a friendly dialog).

/// <summary>
/// Saves the customer.
/// </summary>
private void btnOK_Click(object sender, EventArgs e)
{
   // Call service

   try
   {
      using (CustomersService.CustomersServiceClient proxy
         = new CustomersService.CustomersServiceClient())
      {
         CustomersService.Customer request = GetCustomer();
         proxy.Create(request);
      }
   }
   catch (System.ServiceModel.FaultException ex)
   {
      UIExceptionsHandler.HandleException(this, ex);
      return;
   }

   // Confirmation

   Dialogs.ShowInformation(this,
      Properties.Resources.RES_CustomerCreated);
   ClearFields();
   this.ActiveControl = txtCustomerId;
}
/// <summary>
/// Generic handler for expected exceptions.
/// </summary>
/// <param name="parent">Parent window.</param>
/// <param name="ex">Exception to handle.</param>
public static void HandleException(IWin32Window parent, Exception ex)
{
   // Log exception

   Log.Exception(ex);

   // Show exception

   Dialogs.ShowException(parent, ex);
}

In this case the log entries are stored in the Event Viewer:

Client Logging Configuration

Client Log

Notice that this log still doesn't include any implementation details that would violate the exception shielding design.

Conclusion

This is it for the Exception Shielding design pattern and exception handling in general in the context of WCF services.

We could improve this sample in a number of different ways but I think that this enough for now to illustrate the concepts and provide some background for those future improvements. I will get back to it in time.

Published 06 May 07 11:28 by hgr
Filed under: , , ,

Comments

No Comments
Anonymous comments are disabled