Exception handling block with semantic logging

Topics: Exception Handling Application Block, Semantic Logging Application Block
May 24, 2013 at 2:47 PM
hi,
i successully implemented semantic logging with the new entlib 6. then i wanted to implement exception handling block that will log exceptions in the semantic logging database. i don't know if i'm missing something but there's seems to don't have handler for semantic logging in the exception handling block. there's a logging handler that point on the Logging Application Block but there's no one for the semantic logging block. do i have to create a custom handler for semantic database ? let's say for exemple, if i want to log exceptions out of process in the semantic database ?

thanks
May 24, 2013 at 8:22 PM
Hi,

If you want to log exceptions with the out-of-process service, you can simply configure the service with the SQL sink and set the level as "Error" (assuming you have your event defined in your EventSource class to log on Error level).
Notice that you still need to handle the exception in your application and call your EventSource class to send the event with the exception information. You can do this with any of the known interception techniques like a custom call handler as you suggested but in this case you will just call the EventSource method that will send the event to be handled out-of-process or in-process if you don't want to set the out-of-process service (in this case you will need to configure it programmatically).
Editor
May 27, 2013 at 2:38 PM
There is no exception handler for semantic logging. The main reason is probably that you, the user, define the semantic logging interface so it makes it difficult to create a general purpose handler.

Here's an example of an IExceptionHandler that invokes an Action<> (which is specifically crafted to coincide with the desired EventSource method. The EventSource looks like this:
    [EventSource(Name = "MyCompany")]
    public class MyCompanyEventSource : EventSource
    {
        // Helper method
        [NonEvent]
        public void LogException(Exception e)
        {
            LogException(e.GetType().FullName, e.Message, e.StackTrace, e.ToString());
        }

        [Event(100, Level = EventLevel.Error)]
        public void LogException(string exceptionType, string message, string stackTrace, string formattedException)
        {
            WriteEvent(100, exceptionType, message, stackTrace, formattedException);
        }

        public static readonly MyCompanyEventSource Log = new MyCompanyEventSource();
    }

This exception handler approach follows the existing Logging Handler in that it uses a formatter to create (and also pass in) a formatted exception. The formatter is optional and all formatting could also be done by the semantic logging handler if you wish but the ExceptionFormatter does add some useful information such as MachineName, AppDomainName, WindowsIdentity.
    public class SemanticLoggingErrorHandler : IExceptionHandler
    {
        private Action<string, string, string, string> writeToLog;
        private Func<TextWriter, Exception, Guid, ExceptionFormatter> formatterCreator;

        public SemanticLoggingErrorHandler(Action<string, string, string, string> writeToLog)
            : this (writeToLog, null)
        {
        }

        public SemanticLoggingErrorHandler(Action<string, string, string, string> writeToLog, Type formatterType)
        {
            Guard.ArgumentNotNull(writeToLog, "writeToLog");

            if (formatterType != null)
            {
                if (!typeof(ExceptionFormatter).IsAssignableFrom(formatterType))
                {
                    throw new ArgumentOutOfRangeException("formatterType", "formatterType must be of type ExceptionFormatter");
                }

                this.formatterCreator = GetFormatterCreator(formatterType);
            }

            this.writeToLog = writeToLog;
        }

        public Exception HandleException(Exception exception, Guid handlingInstanceId)
        {
            writeToLog(exception.GetType().FullName, 
                exception.Message, 
                exception.StackTrace, 
                CreateMessage(exception, handlingInstanceId));
            
            return exception;
        }

        private string CreateMessage(Exception exception, Guid handlingInstanceID)
        {
            if (formatterCreator != null)
            {
                using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
                {
                    ExceptionFormatter formatter = formatterCreator(writer, exception, handlingInstanceID);

                    if (formatter != null)
                    {
                        formatter.Format();
                    }

                    return writer.ToString();
                }
            }
            else
            {
                return string.Empty;
            }
        }

        private Func<TextWriter, Exception, Guid, ExceptionFormatter> GetFormatterCreator(Type formatterType)
        {
            ConstructorInfo ctor = formatterType.GetConstructor(new Type[] { 
                typeof(TextWriter), typeof(Exception), typeof(Guid) });

            if (ctor == null)
            {
                throw new ExceptionHandlingException(
                    string.Format("The configured exception formatter '{0}' must expose a public constructor that takes a TextWriter object, an Exception object and a GUID instance as parameters.",
                        formatterType.AssemblyQualifiedName));
            }

            ParameterExpression[] argsExp = new ParameterExpression[]
            { 
                Expression.Parameter(typeof(TextWriter)),
                Expression.Parameter(typeof(Exception)),
                Expression.Parameter(typeof(Guid))
            };

            NewExpression newExp = Expression.New(ctor, argsExp);

            var lambda = Expression.Lambda<Func<TextWriter, Exception, Guid, ExceptionFormatter>>(newExp, argsExp);
            formatterCreator = lambda.Compile();

            return formatterCreator;
        }
    }

And here is how to use the exception handler:
    class Program
    {
        static void Main(string[] args)
        {
            InitializeSemanticLogging();

            ExceptionManager exceptionManager = CreateExceptionManager();

            exceptionManager.Process(() =>
            {
                Console.WriteLine("Start");
                int x = 0;
                int z = 4 / x;
            }, "Policy");
        }

        static void InitializeSemanticLogging()
        {
            ObservableEventListener listener = new ObservableEventListener();
            listener.EnableEvents(MyCompanyEventSource.Log, EventLevel.LogAlways);
            listener.LogToFlatFile("error.log");
        }

        static ExceptionManager CreateExceptionManager()
        {
            var policies = new List<ExceptionPolicyDefinition>();
 
            var policyEntries = new List<ExceptionPolicyEntry>
              {
                  {
                      new ExceptionPolicyEntry(typeof(Exception),
                          PostHandlingAction.ThrowNewException,
                          new IExceptionHandler[]
                            {
                              // Pass in Action<> for handler to use to invoke semantic logging
                              new SemanticLoggingErrorHandler(MyCompanyEventSource.Log.LogException
                                  , typeof(TextExceptionFormatter))
                            })
                  }
              };
 
            policies.Add(new ExceptionPolicyDefinition(
              "Policy", policyEntries));

            ExceptionManager exceptionManager = new ExceptionManager(policies);

            return exceptionManager;
        }
    }

Note that the above only supports programmatic configuration and not declarative or fluent configuration. Also, I'm using Action<> to invoke semantic logging but you could create an interface to formalize the interaction.

~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
Jan 22 at 12:25 PM
Hi

I have tried to follow this pattern in vb.net, but cannot sort a couple of issues! I confess to having used a code converter, but I can't see what I need to change to make it work.

I currently have the code below and am stuck with two errors!
Error 1 Overload resolution failed because no accessible 'LogException' accepts this number of arguments.

Error 2 Overload resolution failed because no accessible 'New' can be called without a narrowing conversion:

'Public Sub New(policyName As String, policyEntries As System.Collections.Generic.IDictionary(Of System.Type,
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionPolicyEntry))': Argument matching parameter 'policyEntries'
narrows from 'Object' to 'System.Collections.Generic.IDictionary(Of System.Type,
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionPolicyEntry)'.

'Public Sub New(policyName As String, policyEntries As System.Collections.Generic.IEnumerable(Of
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionPolicyEntry))': Argument matching parameter 'policyEntries'
narrows from 'Object' to 'System.Collections.Generic.IEnumerable(Of Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionPolicyEntry)'.
Imports System.Diagnostics.Tracing
Imports Microsoft.Practices.EnterpriseLibrary.ExceptionHandling
Imports System.IO
Imports System.Linq.Expressions
Imports System.Reflection
Imports System.Globalization
Imports Microsoft.Practices.EnterpriseLibrary.SemanticLogging
Imports Microsoft.Practices.EnterpriseLibrary.Common.Utility
Imports System.Diagnostics.CodeAnalysis

Module Module1

    Sub Main()
        InitializeSemanticLogging()

        Dim exceptionManager As ExceptionManager = CreateExceptionManager()

        exceptionManager.Process(Sub()
                                     Console.WriteLine("Start")
                                     Dim x As Integer = 0
                                     Dim z As Integer = 4 / x

                                 End Sub, "Policy")

    End Sub



    Private Sub InitializeSemanticLogging()
        Dim listener As New ObservableEventListener()
        listener.EnableEvents(MyCompanyEventSource.Log, EventLevel.LogAlways)
        listener.LogToFlatFile("error.log")
    End Sub

    Private Function CreateExceptionManager() As ExceptionManager
        Dim policies = New List(Of ExceptionPolicyDefinition)()

        ' Pass in Action<> for handler to use to invoke semantic logging
        Dim policyEntries = New List(Of ExceptionPolicyEntry)() From { _
            {New ExceptionPolicyEntry(GetType(Exception), PostHandlingAction.ThrowNewException, New IExceptionHandler() {New SemanticLoggingErrorHandler(MyCompanyEventSource.Log.LogException, GetType(TextExceptionFormatter))})} _
        }

        policies.Add(New ExceptionPolicyDefinition("Policy", policyEntries))

        Dim exceptionManager As New ExceptionManager(policies)

        Return exceptionManager
    End Function

    <EventSource(Name:="MyCompany")> _
    Public Class MyCompanyEventSource
        Inherits EventSource

        ' Helper method
        <NonEvent> _
        Public Sub LogException(e As Exception)
            LogException(e.[GetType]().FullName, e.Message, e.StackTrace, e.ToString())
        End Sub

        <[Event](100, Level:=EventLevel.[Error])> _
        Public Sub LogException(exceptionType As String, message As String, stackTrace As String, formattedException As String)
            WriteEvent(100, exceptionType, message, stackTrace, formattedException)
        End Sub

        Public Shared ReadOnly Log As New MyCompanyEventSource()
    End Class


    Public Class SemanticLoggingErrorHandler
        Implements IExceptionHandler

        Private writeToLog As Action(Of String, String, String, String)
        Private formatterCreator As Func(Of TextWriter, Exception, Guid, ExceptionFormatter)

        Public Sub New(writeToLog As Action(Of String, String, String, String))
            Me.New(writeToLog, Nothing)
        End Sub

        Public Sub New(writeToLog As Action(Of String, String, String, String), formatterType As Type)
            Guard.ArgumentNotNull(writeToLog, "writeToLog")

            If formatterType IsNot Nothing Then
                If Not GetType(ExceptionFormatter).IsAssignableFrom(formatterType) Then
                    Throw New ArgumentOutOfRangeException("formatterType", "formatterType must be of type ExceptionFormatter")
                End If

                Me.formatterCreator = GetFormatterCreator(formatterType)
            End If

            Me.writeToLog = writeToLog
        End Sub

        <SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId:="0")>
        Public Function HandleException(exception As Exception, handlingInstanceId As Guid) As Exception Implements IExceptionHandler.HandleException
            writeToLog(exception.[GetType]().FullName, exception.Message, exception.StackTrace, CreateMessage(exception, handlingInstanceId))

            Return exception
        End Function

        Private Function CreateMessage(exception As Exception, handlingInstanceID As Guid) As String
            If formatterCreator IsNot Nothing Then
                Using writer As New StringWriter(CultureInfo.InvariantCulture)
                    Dim formatter As ExceptionFormatter = formatterCreator(writer, exception, handlingInstanceID)

                    If formatter IsNot Nothing Then
                        formatter.Format()
                    End If

                    Return writer.ToString()
                End Using
            Else
                Return String.Empty
            End If
        End Function

        Private Function GetFormatterCreator(formatterType As Type) As Func(Of TextWriter, Exception, Guid, ExceptionFormatter)
            Dim ctor As ConstructorInfo = formatterType.GetConstructor(New Type() {GetType(TextWriter), GetType(Exception), GetType(Guid)})

            If ctor Is Nothing Then
                Throw New ExceptionHandlingException(String.Format("The configured exception formatter '{0}' must expose a public constructor that takes a TextWriter object, an Exception object and a GUID instance as parameters.", formatterType.AssemblyQualifiedName))
            End If

            Dim argsExp As ParameterExpression() = New ParameterExpression() {Expression.Parameter(GetType(TextWriter)), Expression.Parameter(GetType(Exception)), Expression.Parameter(GetType(Guid))}

            Dim newExp As NewExpression = Expression.[New](ctor, argsExp)

            Dim lambda = Expression.Lambda(Of Func(Of TextWriter, Exception, Guid, ExceptionFormatter))(newExp, argsExp)
            formatterCreator = lambda.Compile()

            Return formatterCreator
        End Function

    End Class

    '=======================================================
    'Service provided by Telerik (www.telerik.com)
    'Conversion powered by NRefactory.
    'Twitter: @telerik
    'Facebook: facebook.com/telerik
    '=======================================================



End Module
Editor
Jan 23 at 6:20 AM
To get rid of the compiler error create a new Action(of T) for the LogException Action.
Dim policyEntries = New List(Of ExceptionPolicyEntry)() From { _
    { _
        New ExceptionPolicyEntry( _
            GetType(Exception), _
            PostHandlingAction.ThrowNewException, _
            New IExceptionHandler() _
            { _
                New SemanticLoggingErrorHandler( _
                    New Action(Of String, String, String, String)(AddressOf MyCompanyEventSource.Log.LogException), _
                    GetType(TextExceptionFormatter)) _
            } _
    )} _
}
~~
Randy Levy
entlib.support@live.com
Enterprise Library support engineer
Support How-to
Jan 23 at 6:45 AM
That worked great thank you - now I can work on improving my understanding of it :)