Monday, May 07, 2007

WCF: Exception Handling with Logging

In my 'WCF Exception Shielding using Generics' post there was a "//TODO: add logging as applicable" comment, which I now have implemented using the EntLib3 Logging Application Block.

As part of the error information I wanted to write to the Windows Application Event Log was the actual request message causing the service operation exception. Thus, I googled for a solution for how to get the XML of the incoming message, so that I could log it along with the stack trace. There are a lot of options for getting at the message in WCF:

The problem of the first option is that I only want to get at the message XML when handling exceptions. The second option is not an option (duh!) as I want my operations to be strongly typed using my data contracts. The third option was more like what I wanted, but it seemed to be unneccessary complicated code.

The code to get at the message XML from within an operation is quite simple:

private static string GetMessageXml(DefaultMessage request)
{
return OperationContext.Current. RequestContext.RequestMessage.ToString();
}

This approach does not work for operations with no RequestContext, such as one-way operations.

The logging code is added after creating the fault details, this makes it possible to use fault details data in the log. This is how my WCF exception shielding with logging looks like:

public static FaultException<T> NewFaultException<T>(Exception ex)
where T : FaultContracts.DefaultFaultContract, new()
{
return NewFaultException<T>(ex, null);
}

public static FaultException<T> NewFaultException<T>(Exception ex, DefaultMessage request)
where T : FaultContracts.DefaultFaultContract, new()
{
return CreateNewFaultException<T>(ex, request);
}

private static FaultException<T> CreateNewFaultException<T>(Exception ex, DefaultMessage request)
where T : FaultContracts.DefaultFaultContract, new()
{
StackFrame stackFrame = new StackFrame(2);
string methodName = stackFrame.GetMethod().Name;

T fault = new T();
Type type = fault.GetType();
type.InvokeMember("SetFaultDetails", BindingFlags.Public BindingFlags.Instance BindingFlags.InvokeMethod, null, fault, new object[] { ex });

string msg = String.Format("An unexpected error occurred in {0}.", methodName);
Dictionary<string, object> props = new Dictionary<string, object>();
props.Add("stackTrace", ex.ToString());
if(request!=null)
{
string xml = GetMessageXml(request);
props.Add("requestMessage", xml);
}
Logger.Write(msg, Category.General, Priority.Highest, (int)EventLogId.Exception, TraceEventType.Error, ex.Message, props);

string reasonText = methodName + " failed, check detailed fault information.";

#if DEBUG
reasonText = methodName + " error: " + ex.ToString();
#endif

return new FaultException<T>(fault, new FaultReason(reasonText));
}

Note that I now use an inner private method to create the new fault to be thrown and thus must skip two stack frames to get the details of the original method called. The DEBUG section is there to make it simple to see full exception details in NUnit or with Fiddler when debugging the service. This DEBUG code must never make it into release builds, as this is not exactly exception shielding.
I have not looked into the WCF app-blocks integration mechanisms added in the april release of EntLib3, that is next on my task list for quieter days. There is not much info about this to be found, but Guy Burstein provides some samples. Btw, you should check out the Patterns and Practices Guidance site, it contains useful stuff and tutorials about EntLib3.

2 comments:

Anonymous said...

Looking forward to reading about integration of WCF and entlib3

Kjell-Sverre Jerijærvi said...

In addition to the WCF+entlib3 validation and exception posts at Guy Burstein's blog (also at CodeProject), this info from David Hayden's blog about logging should get you started:

*A new EntLibLoggingProxyTraceListener class. This trace listener is designed to be used in WCF’s System.ServiceModel trace source, which is configured within the <system.diagnostics> section. This trace listener receives WCF messages logged to this trace source, wraps them in an XmlLogEntry class, and forwarda them to the Logging Application Block where it can be processed according to the application block configuration.

*A new XmlLogEntry class, which derives from LogEntry but includes a new Xml property that preserves the original XML data provided by WCF.

*A new XmlTraceListener class, which derives from the .NET XmlWriterTextWriter class. This class can extract XML data from an XmlLogEntry class and write this data to an XML text file. The output of this trace listener can be used with the WCF log file analysis tools.