Skip to content

Instantly share code, notes, and snippets.

@he-dev
Created March 22, 2021 13:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save he-dev/ddecec5babba41ef4536a5757a82ba62 to your computer and use it in GitHub Desktop.
Save he-dev/ddecec5babba41ef4536a5757a82ba62 to your computer and use it in GitHub Desktop.
ExceptionPrettifier for prettier exception strings
void Main(params string[] args)
{
try
{
try
{
Printery.Print("abc");
}
catch (Exception ex)
{
throw new InvalidOperationException("This was wrong.", ex);
}
}
catch (Exception ex)
{
ex.ToString().Dump();
Console.WriteLine("");
ex.ToPrettyString(ExceptionOrder.Ascending).Dump();
}
}
class Printery
{
public static void PrintLine(string qux)
{
try
{
throw new KeyNotFoundException("Where's the key?");
}
catch (Exception ex)
{
throw;
}
}
internal static void Print(string baz)
{
var exceptions = new List<Exception>();
try
{
var fnfe = new FileNotFoundException(message: "The file wast't there.", fileName: "missing.txt");
fnfe.Data["Not printed"] = baz;
throw fnfe;
}
catch (Exception ex) { exceptions.Add(ex); }
try
{
//throw new EntitySqlException("Bad entity!");
throw new DivideByZeroException("Blub!");
}
catch (Exception ex) { exceptions.Add(ex); }
exceptions.Add(new AggregateException(new ArgumentException(), new NotSupportedException(), new ReadOnlyException()));
throw new AggregateException(exceptions);
}
}
public static class ExceptionPrettifier
{
public static string ToPrettyString<TException>(this TException exception, ExceptionOrder order = ExceptionOrder.Ascending, int indentWidth = 4) where TException : Exception
{
var exceptionStrings = new List<StringBuilder>();
var exceptions = exception.GetInnerExceptions();
var indent = new Func<int, int, string>((depth, nestedDepth) => new string(' ', indentWidth * (depth + nestedDepth)));
foreach (var node in exceptions)
{
var ex = node.Value;
var text = new StringBuilder();
var depth = (int)node.Depth;
if (text.Length > 0) { text.AppendLine(); }
//text.Append(indent(0, level)).AppendLine($"{new string('.', level + 1)}");
//text.Append(indent(0, depth)).AppendLine($"{depth}");
text.Append(indent(0, depth)).AppendLine($"{ex.GetType().Name}: \"{ex.Message}\"");
if (node.Value is AggregateException)
{
text.Append(indent(1, depth)).AppendLine($"InnerExceptions: \"{((AggregateException)ex).InnerExceptions.Count}\"");
}
foreach (var property in ex.GetPropertiesExcept<Exception>())
{
text.Append(indent(1, depth)).AppendLine($"{property.Name}: \"{property.Value}\"");
}
foreach (var property in node.Value.GetData())
{
text.Append(indent(1, depth)).AppendLine($"Data[{property.Key}]: \"{property.Value}\"");
}
text.Append(indent(1, depth)).AppendLine($"StackTrace:");
foreach (var stackTrace in ex.GetStackTrace() ?? System.Linq.Enumerable.Empty<dynamic>())
{
text.Append(indent(2, depth)).AppendLine($"{stackTrace.Caller} in \"{stackTrace.FileName}\" Ln {stackTrace.LineNumber}");
}
exceptionStrings.Add(text);
}
if (order == ExceptionOrder.Ascending) { exceptionStrings.Reverse(); }
return string.Join(Environment.NewLine, exceptionStrings);
}
private static IEnumerable<dynamic> GetPropertiesExcept<TExceptException>(this Exception exception) where TExceptException : Exception
{
var propertyFlags = BindingFlags.Instance | BindingFlags.Public;
var properties = exception.GetType()
.GetProperties(propertyFlags)
.Except(typeof(TExceptException).GetProperties(propertyFlags), x => x.Name)
.Select(p => new { p.Name, Value = p.GetValue(exception) })
.Where(p => p.Value != null && !string.IsNullOrEmpty(p.Value as string));
return properties;
}
private static IEnumerable<dynamic> GetData(this Exception exception)
{
foreach (var key in exception.Data.Keys)
{
yield return new { Key = key, Value = exception.Data[key] };
}
}
private static IEnumerable<dynamic> GetStackTrace(this Exception exception)
{
var stackTrace = new StackTrace(exception, true);
var stackFrames = stackTrace.GetFrames();
var result = stackFrames?.Select(sf => new
{
Caller = (sf.GetMethod() as MethodInfo)?.ToShortString() ?? string.Empty,
FileName = Path.GetFileName(sf.GetFileName()),
LineNumber = sf.GetFileLineNumber(),
});
return result;
}
}
public enum ExceptionOrder
{
Ascending,
Descending
}
public class Node<T>
{
public Node(T value, int depth)
{
Value = value;
Depth = depth;
}
public T Value { get; }
public int Depth { get; }
public static implicit operator T(Node<T> node) => node.Value;
}
public static class Enumerator
{
public static IEnumerable<Node<Exception>> GetInnerExceptions(this Exception exception, bool includeCurrent = true)
{
if (exception == null) { throw new ArgumentNullException(nameof(exception)); }
var exceptionStack = new Stack<Node<Exception>>();
var depth = 0;
if (includeCurrent)
{
exceptionStack.Push(new Node<Exception>(exception, depth));
}
while (exceptionStack.Any())
{
var current = exceptionStack.Pop();
yield return current;
if (current.Value is AggregateException)
{
depth++;
foreach (var innerException in ((AggregateException)current).InnerExceptions)
{
exceptionStack.Push(new Node<Exception>(innerException, depth + 1));
}
continue;
}
if (current.Value.InnerException != null)
{
depth++;
exceptionStack.Push(new Node<Exception>(current.Value.InnerException, depth));
depth--;
}
}
}
}
public static class Reflection
{
public static string ToShortString(this MethodInfo method)
{
if (method == null) { throw new ArgumentNullException(nameof(method)); }
var indentWidth = 4;
var indent = new Func<int, string>(depth => new string(' ', indentWidth * depth));
var parameters = method.GetParameters().Select(p => $"{p.ParameterType.ToShortString()} {p.Name}");
// public/internal/protected/private [static] [abstract/virtual/override] retVal
var accessModifier = new[]
{
method.IsPublic ? "public" : string.Empty,
method.IsAssembly ? "internal" : string.Empty,
method.IsPrivate ? "private" : string.Empty,
method.IsFamily ? "protected" : string.Empty,
}
.First(x => !string.IsNullOrEmpty(x));
var inheritanceModifier = new[]
{
method.IsAbstract ? " abstract" : string.Empty,
method.IsVirtual ? " virtual" : string.Empty,
method.GetBaseDefinition() != method ? " override" : string.Empty,
}
.FirstOrDefault(x => !string.IsNullOrEmpty(x));
var signature = new StringBuilder()
.Append(method.DeclaringType?.FullName)
.Append(" { ")
.Append(accessModifier)
.Append(method.IsStatic ? " static" : string.Empty)
.Append(inheritanceModifier)
.Append(method.GetCustomAttribute<AsyncStateMachineAttribute>() != null ? " async" : string.Empty)
.Append(" ").Append(method.ReturnType.ToShortString())
.Append(" ").Append(method.Name)
.Append("(").Append(string.Join(", ", parameters)).Append(") { ... }")
.Append(" } ")
.ToString();
return signature;
}
public static string ToShortString(this Type type)
{
var codeDomProvider = CodeDomProvider.CreateProvider("C#");
var typeReferenceExpression = new CodeTypeReferenceExpression(type);
using (var writer = new StringWriter())
{
codeDomProvider.GenerateCodeFromExpression(typeReferenceExpression, writer, new CodeGeneratorOptions());
return writer.GetStringBuilder().ToString();
}
}
}
internal static class Enumerable
{
public static IEnumerable<TArg> Except<TArg, TProjection>(
this IEnumerable<TArg> first,
IEnumerable<TArg> second,
Func<TArg, TProjection> projection)
{
var mec = new AutoEqualityComparer<TArg, TProjection>(projection);
return first.Except(second, mec);
}
}
internal class AutoEqualityComparer<TArg, TProjection> : EqualityComparer<TArg>
{
private readonly Func<TArg, TProjection> _projection;
public AutoEqualityComparer(Func<TArg, TProjection> projection)
{
_projection = projection;
}
public override bool Equals(TArg left, TArg right)
{
if (left == null && right == null)
{
return true;
}
if (left == null)
{
return false;
}
if (right == null)
{
return false;
}
var xData = _projection(left);
var yData = _projection(right);
return EqualityComparer<TProjection>.Default.Equals(xData, yData);
}
public override int GetHashCode(TArg obj)
{
if (obj == null)
{
return 0;
}
var objData = _projection(obj);
return EqualityComparer<TProjection>.Default.GetHashCode(objData);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment