When something goes wrong in a .NET application, it’s important to log sufficient information from the exception for diagnostic purposes. Here are some ideas on how to do this well.

Under pressure to deliver working code, I find it very tempting to throw a token piece of logging into place and then to move on:

try
{
    // ...
}
catch (Exception ex)
{
    Log.Error("{0} ({1})", ex.Message, ex.GetType().Name);
}

The problem with this approach is that is discards information; when used correctly, a .NET exception includes a great deal of information useful for diagnostic purposes.

The first improvement is to recognise that it’s common for some frameworks to wrap exceptions to supply context to the error. We need to be checking the InnerException and handling this wrapping.

Since we don’t want to write the same code over and over, let’s wrap this into an extension method:

/// <summary>
/// Turn a single exception into a sequence by flattening the nested inner exceptions
/// </summary>
/// <param name="exception"></param>
/// <returns>A sequence of all the exceptions.</returns>
public static IEnumerable<Exception> AsEnumerable(this Exception exception)
{
    var e = exception;
    do
    {
        yield return e;
        e = e.InnerException;
    } while (e != null);
}

Catching and logging an exception now looks like this:

try
{
    // ...
}
catch (Exception ex)
{
    foreach (var e in ex.AsEnumerable())
    {
        Log.Error("{0} ({1})", ex.Message, ex.GetType().Name);
    }
}

This is an improvement - but we can do better.

Every exception contains a dictionary of name/object values - we should include those in our output. ADO.NET, for example, includes details about the database connection, server version and the SQL being executed in every exception.

Again, we don’t want to write the same code over and over, so let’s create a new extension method:

/// <summary>
/// Turn a single exception into a sequence of strings for logging
/// </summary>
/// <param name="exception"></param>
/// <returns>A sequence of strings</returns>
public static IEnumerable<string> AsStrings(this Exception exception)
{
    yield return string.Format("{0} ({1})", 
       exception.Message, exception.GetType().Name);

    if (exception.Data != null)
    {
        foreach (DictionaryEntry p in exception.Data)
        {
            yield return string.Format("  {0}: {1}", p.Key, p.Value);
        }
    }
}

With this method, we get all the detail (if any) out of the dictionary of values.

Combine the two methods together and we get the following:

try
{
    // ...
}
catch (Exception ex)
{
    foreach (var s in ex.AsEnumerable().SelectMany(e => e.AsStrings()))
    {
        Log.Error(s);
    }
}

For bonus credit, extend the logging framework so that we can write this:

try
{
    // ...
}
catch (Exception ex)
{
    Log.Exception(ex);
}

With the implementation of Log.Exception left as an exercise for the reader.

Comments

blog comments powered by Disqus
Next Post
Why Monospaced Fonts?  24 Jan 2016
Prior Post
SharpDox Tips  02 Jan 2016
Related Posts
Error Methods  25 Nov 2017
Pass implementations, not representations  14 Oct 2017
Avoiding the Singleton Pattern  22 Jul 2017
Implementing the Singleton Pattern  15 Jul 2017
Static Analysis tools for the Win  15 Apr 2017
On the Merits of Simple Code  28 Nov 2015
Semantic Types  27 Sep 2015
Command Line Processing  21 Sep 2014
Easy String Conversion  24 Aug 2014
Simpler code with DirectoryInfo  13 May 2014
More smart-code posts »
Related Pages
January 2016 archive