Thursday, February 19, 2015

Avoiding exception wrapping and exposing Exception.Data via log4net.

In .NET exception handling is often a layered approach involving several classes and methods catching a specific exception and then throwing a new exception with some additional details and the original exception wrapped as the inner exception. The additional details are often useful, but after passing through several layers of try-catch-throw-new-exception the root exception can become obscured somewhere down the stack trace and inner exceptions.

A common methods exception might be something like:

public string Foo(string someInput)
{
    try 
    {
        doSomethingWithTheInput(someInput);
    }
    catch(Exception ex)
    {
         throw new FooIsBrokenException("Foo is bad for: " + someInput, ex);
    }
    finally 
    {
        // Common cleanup code to release any resources etc...
    }
}

If the exception is going to be exposed outside the current project then the wrapping exception will likely be useful. However, if it is only going to be for internal use as a way of adding additional context to the base exception you can save some effort by using Exception.Data.

public string Foo(string someInput)
{
    try 
    {
        doSomethingWithTheInput(someInput);
    }
    catch(Exception ex)
    {
        // Consider what to do if the key is already present.
        ex.Data.Add("someInput", someInput);
        throw;
    }
    finally 
    {
        // Common cleanup code to release any resources etc...
    }
}

log4net

Now you've got the details in the Data dictionary they need to be exposed. I'm working on an older project that started in 2007 and uses log4net. There is a suggestion on Logging Exception.Data using Log4Net to use a custom PatternLayoutConverter on each layout appender to convert the data dictionary.

I've gone with a slightly different approach by registering an IObjectRenderer with log4net you can append the additional Exception.Data values. The advantage here is that it will work with all the appenders to render exceptions.

public class ExceptionObjectLogger : IObjectRenderer
{
    public void RenderObject(RendererMap rendererMap, object obj, TextWriter writer)
    {
        var ex = obj as Exception;

        if (ex == null)
        {
            // Shouldn't happen if only configured for the System.Exception type.
            rendererMap.DefaultRenderer.RenderObject(rendererMap, obj, writer);
        }
        else
        {
            while (ex != null)
            {
                rendererMap.DefaultRenderer.RenderObject(rendererMap, obj, writer);
                RenderExceptionData(rendererMap, ex, writer);
                ex = ex.InnerException;
            }
        }
    }

    private void RenderExceptionData(RendererMap rendererMap, Exception ex, TextWriter writer)
    {
        foreach (DictionaryEntry entry in ex.Data)
        {
            if (entry.Key is string)
            {
                writer.Write(entry.Key);
            }
            else
            {
                IObjectRenderer keyRenderer = rendererMap.Get(entry.Key.GetType());
                keyRenderer.RenderObject(rendererMap, entry.Key, writer);
            }

            writer.Write(": ");

            if (entry.Value is string)
            {
                writer.Write(entry.Value);
            }
            else
            {
                IObjectRenderer valueRenderer = rendererMap.Get(entry.Value.GetType());
                valueRenderer.RenderObject(rendererMap, entry.Value, writer);
            }
            writer.WriteLine();
        }
    }
}

Logging configuration additions. Note that it only applies to classes inheriting from Exception.


    ...
    
    ...

See also: