Could not log FaultException<TDetail>

Topics: Logging Application Block
Aug 23, 2012 at 7:17 PM

Im trying to log FaultException<TDetail> using enterprise logging. But its not logging the exception properly. I tried applying [Serializable] attribute to TDetail but still that didn’t work. Here is my testing code. Not sure what im missing here

        [TestMethod]
        public void TestFaultExceptionLogging()
        {
            MyFault detail = new MyFault()
            {
                Message = " MyFault Detail Message",
                Reason = "MyFault Detail Reason"
            };

            FaultException<MyFault> ex = new FaultException<MyFault>(detail, "Fault Exception Reason");
            Microsoft.Practices.EnterpriseLibrary.Logging.Logger.Write(ex, "General", 1, 1, System.Diagnostics.TraceEventType.Error);
        }

[DataContract]
    public class MyFault
    {
        [DataMember]
        public string Reason { get; set; }

        [DataMember]
        public string Message { get; set; }
    }

Here is the generated log

Timestamp: 8/23/2012 6:14:40 PM
Message: System.ServiceModel.FaultException`1[Tests.MyFault]: MyFault Fault Exception Reason (Fault Detail is equal to Tests.MyFault).
Category: General
Priority: 1
EventId: 1
Severity: Error
Title:
Machine: DAL_1
App Domain: TestAppDomain: 5281bf78-ab47-44e2-8045-e56a6168c9f2
ProcessId: 3216
Process Name: c:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\QTAgent32.exe
Thread Name: Agent: adapter run thread for test 'TestFaultExceptionLogging' with id 'e672d624-31ce-4154-9a27-8fbb314879bc'
Win32 ThreadId:4680
Extended Properties: 

Aug 24, 2012 at 4:28 AM

Similar to many interfaces that take an object (e.g. Console.WriteLine()), when a textual representation is required the ToString() method is invoked.  The default behavior of Object is to return the type information.  FaultException<T> overrides this behavior by also returning the Message and the Detail.ToString().  

So one solution would be to override the ToString() method of MyFault:

    [DataContract]
    public class MyFault
    {
        [DataMember]
        public string Reason { get; set; }

        [DataMember]
        public string Message { get; set; }

        public override string ToString()
        {
            return string.Format("Reason: {0}, Message: {1}", Reason, Message);
        }
    }

Then the Message will be set to: 

"System.ServiceModel.FaultException`1[ConsoleApplication36.MyFault]: Fault Exception Reason (Fault Detail is equal to Reason: MyFault Detail Reason, Message:  MyFault Detail Message)."

If that still doesn't suit you then you could create an extension method on LogWriter that still uses the MyFault.ToString():

public static class LoggingExtensions
{
    public static void FormattedWrite<T>(this LogWriter logWriter,
            T data, string category, int priority, int eventId,
            System.Diagnostics.TraceEventType type)
    {
        string formattedData = null;

        if (data != null)
        {
            if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(FaultException<>))
            {
                PropertyInfo property = typeof(T).GetProperty("Detail");
                object obj = property.GetValue(data, null);

                if (obj != null)
                {
                    formattedData = obj.ToString();
                }
            }
        }

        logWriter.Write(formattedData, "General", 1, 1, System.Diagnostics.TraceEventType.Error);
    }
}

This method will perform a ToString() on the object unless the type is a FaultException<> then it will extract the Detail and perform a ToString() on it.  This gives a bit more control of the output but still requires you to override ToString().

Another option would be serialize the object.  In that case then you would not need to override ToString() since the serializer would handle that.  For example:

public static class LoggingExtensions
{
    public static void FormattedWrite<T>(this LogWriter logWriter,
        T data, string category, int priority, int eventId,
        System.Diagnostics.TraceEventType type)
    {
        string formattedData = null;

        if (data != null)
        {
            if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(FaultException<>))
            {
                DataContractSerializer dataContractSerializer = new DataContractSerializer(typeof(T).GetGenericArguments().First());

                using (var stringWriter = new StringWriter())
                {
                    using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter))
                    {
                        PropertyInfo property = typeof(T).GetProperty("Detail");

                        dataContractSerializer.WriteObject(xmlWriter, property.GetValue(data, null));
                    }

                    formattedData = stringWriter.ToString();
                }
            }
        }

        logWriter.Write(formattedData, "General", 1, 1, System.Diagnostics.TraceEventType.Error);
    }
}

This would give you something like:

<?xml version="1.0" encoding="utf-16"?>
<MyFault xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication36">
  <Message> MyFault Detail Message</Message>
  <Reason>MyFault Detail Reason</Reason>
</MyFault>

You could serialize the entire FaultContract<> but the XML gets pretty messy for putting in a log.

--
Randy Levy
Enterprise Library support engineer
entlib.support@live.com