From 9987b6b007680e285de6c198c40939d05ec60a3c Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sun, 29 Oct 2017 22:51:08 +0000 Subject: [PATCH] Update README.md --- README.md | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ed4e5b..7818e59 100644 --- a/README.md +++ b/README.md @@ -1 +1,150 @@ -# Ben.Demystifier \ No newline at end of file +# Ben.Demystifier + +## High performance understanding for stack traces + +.NET stack traces output the compiler transformed methods; rather than the source code methods, which make them slow to mentally parse and match back to the source code. + +The current output was good for C# 1.0; but has become progressively worse since C# 2.0 (iterators, generics) as new features are added to the .NET languages and at C# 7.1 the stack traces are esoteric (see: [Problems with current stack traces](#problems-with-current-stack-traces)). + +### Make error logs more productive + +Output the modern C#7 features in stack traces in an understable fashion that looks like the source code that generated them. + +[![Demystified stacktrace](https://aoa.blob.core.windows.net/aspnet/stacktrace-demystified.png)](https://aoa.blob.core.windows.net/aspnet/stacktrace-demystified.png) + +### Usage + +``` +exception.Demystify() +``` +Resolves the stack back to the C# source format of the calls (and is an inspectable list of stack frames) + +Calling `.ToString()` on the Demystified exception will produce a string stacktrace similar to the following (without the comments): + +```csharp +System.InvalidOperationException: Collection was modified; enumeration operation may not execute. + at bool System.Collections.Generic.List+Enumerator.MoveNextRare() + at bool Program.Iterator(int startAt)+MoveNext() // Resolved enumerator + at bool System.Linq.Enumerable+SelectEnumerableIterator.MoveNext() // Resolved enumerator + at string string.Join(string separator, IEnumerable values) + at string Program+GenericClass.GenericMethod(ref TSubType value) + at async Task Program.MethodAsync(int value) // Resolved async + at async Task Program.MethodAsync(TValue value) // Resolved async + at string Program.Method(string value)+()=>{} // Resolved lambda source + at string Program.RunLambda(Func lambda) + at (string val, bool) Program.Method(string value) // Tuple returning + at ref string Program.RefMethod(string value)+LocalFuncRefReturn() // ref return local func + at int Program.RefMethod(string value)+LocalFuncParam(string val) // local function + at string Program.RefMethod(string value) + at (string val, bool) static Program()+(string s, bool b)=>{} // tuple return static lambda + at void static Program()+(string s, bool b)=>{} // void static lambda + at void Program.Start((string val, bool) param) // Resolved tuple param + at void Program.Start((string val, bool) param)+LocalFunc1(long l) // void local function + at bool Program.Start((string val, bool) param)+LocalFunc2(bool b1, bool b2) // bool return local function + at string Program.Start() + at void Program()+()=>{} // ctor defined lambda + at void Program(Action action)+(object state)=>{} // ctor defined lambda + at void Program.RunAction(Action lambda, object state) + at new Program(Action action) // constructor + at new Program() // constructor + at void Program.Main(String[] args) +``` + +Calling `.ToString()` on the same exception would produce the following output + +```csharp +System.InvalidOperationException: Collection was modified; enumeration operation may not execute. + at System.ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion() // ? low value + at System.Collections.Generic.List`1.Enumerator.MoveNextRare() + at Program.d__3.MoveNext() // which enumerator? + at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext() // which enumerator? + at System.String.Join(String separator, IEnumerable`1 values) + at Program.GenericClass`1.GenericMethod[TSubType](TSubType& value) + at Program.d__4.MoveNext() // which async overload? +--- End of stack trace from previous location where exception was thrown --- // ? no value + at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() // ? no value + at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) // ? no value + at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() // ? no value + at Program.d__5`1.MoveNext() // which async overload? +--- End of stack trace from previous location where exception was thrown --- // ? no value + at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() // ? no value + at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) // ? no value + at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() // ? no value + at Program.<>c__DisplayClass8_0.b__0() // ¯\_(ツ)_/¯ + at Program.RunLambda(Func`1 lambda) + at Program.Method(String value) + at Program.g__LocalFuncRefReturn|10_1(<>c__DisplayClass10_0& ) // local function + at Program.g__LocalFuncParam|10_0(String val, <>c__DisplayClass10_0& ) // local function + at Program.RefMethod(String value) + at Program.<>c.<.cctor>b__18_1(String s, Boolean b) // ¯\_(ツ)_/¯ + at Program.<>c.<.cctor>b__18_0(String s, Boolean b) // ¯\_(ツ)_/¯ + at Program.Start(ValueTuple`2 param) // Tuple param? + at Program.g__LocalFunc1|11_0(Int64 l) // local function + at Program.g__LocalFunc2|11_1(Boolean b1, Boolean b2) // local function + at Program.Start() + at Program.<>c.<.ctor>b__1_0() // ¯\_(ツ)_/¯ + at Program.<>c__DisplayClass2_0.<.ctor>b__0(Object state) // ¯\_(ツ)_/¯ + at Program.RunAction(Action`1 lambda, Object state) + at Program..ctor(Action action) // constructor + at Program..ctor() // constructor + at Program.Main(String[] args) +``` +Which is far less helpful, and close to jibberish in places + + +### Problems with current stack traces: + +* **constructors** + + Do not match code, output as `.ctor` and `.cctor` +* **iterators** + + Cannot determine overload `d__3.MoveNext()` rather than `Iterator(int startAt)+MoveNext()` +* **Linq** + + Cannot determine overload + + `Linq.Enumerable.SelectEnumerableIterator``2.MoveNext()` + + rather than + + `Linq.Enumerable+SelectEnumerableIterator.MoveNext()` +* **async** + + Cannot determine overload and no modifier such as `async` + + `d__5``1.MoveNext()` + + rather than + + `async Task Program.MethodAsync(int value)` + + Noise! + ``` + --- End of stack trace from previous location where exception was thrown --- + at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) + at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) + at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task) + at System.Runtime.CompilerServices.TaskAwaiter.GetResult() + ``` + +* **lambdas** + + Mostly jibberish `<>c__DisplayClass2_0.<.ctor>b__0(Object state)` with a suggestion of where they are declared but no hint if there are multiple overloads of the method. +* **local functions** + + Mostly jibberish `g__LocalFuncParam|10_0(String val, <>c__DisplayClass10_0& )` with a suggestion of where they are declared but no hint if there are multiple overloads of the method. + +* **generic parameters** + + Not resolved, only an indication of the number `RunLambda(Func``1 lambda)` rather than `RunLambda(Func lambda)` +* **value tuples** + + Do not match code, output as `ValueTuple``2 param` rather than `(string val, bool) param` +* **primitive types** + + Do not match code, output as `Int64`, `Boolean`, `String` rather than `long`, `bool`, `string` +* **return types** + + Skiped entirely from method signiture \ No newline at end of file