Skip to content

Instantly share code, notes, and snippets.

@houseofcat
Last active December 6, 2021 04:03
Show Gist options
  • Save houseofcat/2bf0c43c8382f56a0c5779fec13332d7 to your computer and use it in GitHub Desktop.
Save houseofcat/2bf0c43c8382f56a0c5779fec13332d7 to your computer and use it in GitHub Desktop.
C# Prettify Stack Trace
using System;
using System.Collections.Generic;
namespace PrettifyStackTrace
{
public class Stacky
{
public string ExceptionType { get; set; }
public string Method { get; set; }
public string FileName { get; set; }
public int Line { get; set; }
public List<string> StackLines { get; set; }
}
public static class Helpers
{
public static Stacky PrettifyStackTrace(string stackTrace, Type exceptionType)
{
var stacky = new Stacky();
stacky.ExceptionType = exceptionType.ToString();
if (!string.IsNullOrEmpty(stackTrace))
{
stacky.StackLines = new List<string>();
var lines = stackTrace.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
var stackCount = 0;
try // Try to Prettify
{
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i];
if (i == 0)
{
var subStrings = line.Split(new string[] { " in " }, StringSplitOptions.RemoveEmptyEntries);
var methodStrings = subStrings[0].Split(new string[] { " at " }, StringSplitOptions.RemoveEmptyEntries);
var fileStrings = (subStrings.Length > 1) ? subStrings[1].Split(new string[] { ":line " }, StringSplitOptions.RemoveEmptyEntries) : new string[] { subStrings[0], string.Empty };
stacky.Method = methodStrings[methodStrings.Length - 1];
stacky.FileName = fileStrings[0].Contains(".cs") ? fileStrings[0] : "System/NET Exception";
stacky.Line = int.Parse(fileStrings[1]);
stacky.StackLines.Add(stacky.Method);
}
else if (line.StartsWith("---"))
{
stackCount++;
stacky.StackLines.Add($"=== Sub-stack {stackCount} ===");
}
else
{
stacky.StackLines.Add(line.Replace(" at ", " @ "));
}
}
}
catch // Else just print the lines as is.
{
stackCount = 0;
stacky.StackLines.Clear();
for (int i = 0; i < lines.Length; i++)
{
var line = lines[i];
if (line.StartsWith("---"))
{
stackCount++;
stacky.StackLines.Add($"=== Sub-stack {stackCount} ===");
}
else
{ stacky.StackLines.Add(line.Replace(" at ", " @ ")); }
}
}
}
return stacky;
}
}
}
@houseofcat
Copy link
Author

houseofcat commented Apr 19, 2018

Example usage:

using static PrettifyStackTrace.Helpers;

...

PrettifyStackTrace(stackTrace, typeof(Exception));

Sample input string:

"   at [REDACTED].Controllers.v1.AdminController.PostStackTrace(String stackTrace) in C:\\[REDACTED]\\Controllers\\v1\\AdminController.cs:line 125
   at lambda_method(Closure , Object , Object[] )
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass6_1.<GetExecutor>b__3(Object instance, Object[] methodParameters)
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Filters.ActionFilterAttribute.<ExecuteActionFilterAsyncCore>d__5.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__5.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__6.MoveNext()"

Output serialized to JSON:

{
    "ExceptionType": "System.Exception",
    "Method": "[REDACTED].Controllers.v1.AdminController.PostStackTrace(String stackTrace)",
    "FileName": "C:\\[REDACTED]\\Controllers\\v1\\AdminController.cs",
    "Line": 125,
    "StackLines": [
        "[REDACTED].Controllers.v1.AdminController.PostStackTrace(String stackTrace)",
        " @ lambda_method(Closure , Object , Object[] )",
        " @ System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass6_1.<GetExecutor>b__3(Object instance, Object[] methodParameters)",
        " @ System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)",
        "=== Sub-stack 1 ===",
        " @ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()",
        " @ System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)",
        " @ System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__1.MoveNext()",
        "=== Sub-stack 2 ===",
        " @ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()",
        " @ System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)",
        " @ System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__6.MoveNext()",
        "=== Sub-stack 3 ===",
        " @ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()",
        " @ System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__6.MoveNext()",
        "=== Sub-stack 4 ===",
        " @ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()",
        " @ System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)",
        " @ System.Web.Http.Filters.ActionFilterAttribute.<ExecuteActionFilterAsyncCore>d__5.MoveNext()",
        "=== Sub-stack 5 ===",
        " @ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()",
        " @ System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)",
        " @ System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__5.MoveNext()",
        "=== Sub-stack 6 ===",
        " @ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()",
        " @ System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)",
        " @ System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__3.MoveNext()",
        "=== Sub-stack 7 ===",
        " @ System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()",
        " @ System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)",
        " @ System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__6.MoveNext()\""
    ]
}

@bboyle1234
Copy link

Can you update this to work for aggregate exceptions as well please?

@houseofcat
Copy link
Author

It would be my absolute pleasure to do so.

@bboyle1234
Copy link

I modified it for my own use:

    [Serializable]
    public class ExceptionPretty {
        public string Message;
        public string ExceptionType;

        public string Method;
        public string FileName;
        public int Line;
        public List<string> StackLines = new List<string>(); // empty array [] makes better json than null
        public List<ExceptionPretty> InnerExceptions = new List<ExceptionPretty>(); // empty array [] makes better json than null

        public ExceptionPretty(Exception x) {

            Message = x.Message;
            ExceptionType = x.GetType().ToString();

            if (x is AggregateException aggregateException) {
                InnerExceptions = aggregateException.InnerExceptions.Select(inner => new ExceptionPretty(inner)).ToList();
            } else if (null != x.InnerException) {
                InnerExceptions.Add(new ExceptionPretty(x.InnerException));
            }

            var lines = (x.StackTrace ?? "") // if stack trace is null we end up with an empty array
                .Split('\n') // independent of operating system
                .Select(l => l.Replace("\r", "")) // independent of operating system
                .Where(l => !string.IsNullOrWhiteSpace(l)) // exclude empty strings
                .ToArray();

            var stackCount = 0;

            try // Try to Prettify
            {
                for (int i = 0; i < lines.Length; i++) {
                    var line = lines[i];

                    if (i == 0) {
                        var subStrings = line.Split(new string[] { " in " }, StringSplitOptions.RemoveEmptyEntries);
                        var methodStrings = subStrings[0].Split(new string[] { "   at " }, StringSplitOptions.RemoveEmptyEntries);
                        var fileStrings = (subStrings.Length > 1) ? subStrings[1].Split(new string[] { ":line " }, StringSplitOptions.RemoveEmptyEntries) : new string[] { subStrings[0], string.Empty };
                        Method = methodStrings[methodStrings.Length - 1];
                        FileName = fileStrings[0].Contains(".cs") ? fileStrings[0] : "System/NET Exception";
                        Line = int.Parse(fileStrings[1]);

                        StackLines.Add(Method);
                    } else if (line.StartsWith("---")) {
                        stackCount++;
                        StackLines.Add($"=== Sub-stack {stackCount} ===");
                    } else {
                        StackLines.Add(line.Replace("   at ", " @ "));
                    }
                }
            } catch // Else just print the lines as is.
              {
                stackCount = 0;
                StackLines.Clear();

                for (int i = 0; i < lines.Length; i++) {
                    var line = lines[i];

                    if (line.StartsWith("---")) {
                        stackCount++;
                        StackLines.Add($"=== Sub-stack {stackCount} ===");
                    } else { StackLines.Add(line.Replace("   at ", " @ ")); }
                }
            }
        }
    }

@bboyle1234
Copy link

Here's an other little helper nugget that I like to use occasionally:

    public static string UnwindMessage(this Exception x, string separator = " => ") {
        return null == x.InnerException
            ? x.Message
            : x.Message + separator + x.InnerException.UnwindMessage(separator);
    }

@bboyle1234
Copy link

And then I refactored ExceptionPretty to ExceptionSummary ... until a better name can be conjured.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment