Tech Blog‎ > ‎

C# - Capture Full Stack Trace For Exception Inside Catch Block

posted Aug 13, 2015, 2:03 PM by Victor Zakharov   [ updated Aug 13, 2015, 2:06 PM ]
The problem is very clearly described in this article on CodeProject:
In short, when you catch the exception, you only have stack trace relative to the catch point. Why is this important?

For example, suppose the error occurs when sending email and this is a non-critical part of your application.
Let's say you send confirmation emails to clients as part of the ordering process.
Yes, it's bad if the email cannot reach your client (say, their email address is invalid), but your web server should not crash because of that.

In most generic case, you want the full stack trace, i.e. to investigate later, but the application should continue running (skipping the faulted area).
Problem here is that with how .NET exceptions work you either get the full trace (global exception handler) OR your application continues running and you only get partial stack trace.
Depending on where you would like to log your error, partial stack trace can be completely useless to developer.

If you want to have full trace, i.e. for logging purposes, and have your application continue running, you will need to write custom code.
The approach I used below is using Environment.StackTrace, wiping unnecessary information from there (external calls - without a line number), and merging with stack trace coming from ex object inside the catch block.
Another possible way to solve it is by using StackTrace object, if you want to go this direction, check this article:
I tried using StackTrace, looks like you would need a custom ToString() to mimic ex.StackTrace output. Otherwise the stack trace information is not full. To me it was not worth the extra effort.
Below is an extension method I wrote wrapped in a static class (C#).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

/// <summary>
///  Extension methods for Exception class.
/// </summary>
static class ExceptionExtensions
{
    /// <summary>
    ///  Provides full stack trace for the exception that occurred.
    /// </summary>
    /// <param name="exception">Exception object.</param>
    /// <param name="environmentStackTrace">Environment stack trace, for pulling additional stack frames.</param>
    public static string ToLogString(this Exception exception, string environmentStackTrace)
    {
        List<string> environmentStackTraceLines = ExceptionExtensions.GetUserStackTraceLines(environmentStackTrace);
        environmentStackTraceLines.RemoveAt(0);

        List<string> stackTraceLines = ExceptionExtensions.GetStackTraceLines(exception.StackTrace);
        stackTraceLines.AddRange(environmentStackTraceLines);

        string fullStackTrace = String.Join(Environment.NewLine, stackTraceLines);
        
        string logMessage = exception.Message + Environment.NewLine + fullStackTrace;
        return logMessage;
    }

    /// <summary>
    ///  Gets a list of stack frame lines, as strings.
    /// </summary>
    /// <param name="stackTrace">Stack trace string.</param>
    private static List<string> GetStackTraceLines(string stackTrace)
    {
        return stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None).ToList();
    }

    /// <summary>
    ///  Gets a list of stack frame lines, as strings, only including those for which line number is known.
    /// </summary>
    /// <param name="fullStackTrace">Full stack trace, including external code.</param>
    private static List<string> GetUserStackTraceLines(string fullStackTrace)
    {
        List<string> outputList = new List<string>();
        Regex regex = new Regex(@"([^\)]*\)) in (.*):line (\d)*$");

        List<string> stackTraceLines = ExceptionExtensions.GetStackTraceLines(fullStackTrace);
        foreach (string stackTraceLine in stackTraceLines)
        {
            if (!regex.IsMatch(stackTraceLine))
            {
                continue;
            }

            outputList.Add(stackTraceLine);
        }

        return outputList;
    }
}
Usage is simple:
try
{
    //some code here throws exception 
}
catch (Exception ex)
{
    string logMessage = ex.ToLogString(Environment.StackTrace);
    //write logMessage to file
}