mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 17:02:51 +08:00
Improves exception rendering for async methods
This commit is contained in:
parent
ff4215f431
commit
a0e20f299c
@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Spectre.Console.Examples;
|
namespace Spectre.Console.Examples;
|
||||||
|
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -43,6 +44,18 @@ public static class Program
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await DoMagicAsync(42, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.Write(new Rule("Async").LeftAligned());
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
AnsiConsole.WriteException(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DoMagic(int foo, string[,] bar)
|
private static void DoMagic(int foo, string[,] bar)
|
||||||
@ -61,4 +74,23 @@ public static class Program
|
|||||||
{
|
{
|
||||||
throw new InvalidCredentialException("The credentials are invalid.");
|
throw new InvalidCredentialException("The credentials are invalid.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task DoMagicAsync(int foo, string[,] bar)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await CheckCredentialsAsync(foo, bar);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Whaaat?", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CheckCredentialsAsync(int qux, string[,] corgi)
|
||||||
|
{
|
||||||
|
await Task.Delay(0);
|
||||||
|
throw new InvalidCredentialException("The credentials are invalid.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,14 +55,29 @@ internal static class ExceptionFormatter
|
|||||||
|
|
||||||
// Stack frames
|
// Stack frames
|
||||||
var stackTrace = new StackTrace(ex, fNeedFileInfo: true);
|
var stackTrace = new StackTrace(ex, fNeedFileInfo: true);
|
||||||
foreach (var frame in stackTrace.GetFrames().Where(f => f != null).Cast<StackFrame>())
|
var frames = stackTrace
|
||||||
|
.GetFrames()
|
||||||
|
.FilterStackFrames()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var frame in frames)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
// Method
|
// Method
|
||||||
var shortenMethods = (settings.Format & ExceptionFormats.ShortenMethods) != 0;
|
var shortenMethods = (settings.Format & ExceptionFormats.ShortenMethods) != 0;
|
||||||
var method = frame.GetMethod();
|
var method = frame.GetMethod();
|
||||||
var methodName = method.GetName();
|
if (method == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var methodName = GetMethodName(ref method, out var isAsync);
|
||||||
|
if (isAsync)
|
||||||
|
{
|
||||||
|
builder.Append("async ");
|
||||||
|
}
|
||||||
|
|
||||||
builder.Append(Emphasize(methodName, new[] { '.' }, styles.Method, shortenMethods, settings));
|
builder.Append(Emphasize(methodName, new[] { '.' }, styles.Method, shortenMethods, settings));
|
||||||
builder.AppendWithStyle(styles.Parenthesis, "(");
|
builder.AppendWithStyle(styles.Parenthesis, "(");
|
||||||
AppendParameters(builder, method, settings);
|
AppendParameters(builder, method, settings);
|
||||||
@ -161,4 +176,158 @@ internal static class ExceptionFormatter
|
|||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ShowInStackTrace(StackFrame frame)
|
||||||
|
{
|
||||||
|
// NET 6 has an attribute of StackTraceHiddenAttribute that we can use to clean up the stack trace
|
||||||
|
// cleanly. If the user is on an older version we'll fall back to all the stack frames being included.
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
var mb = frame.GetMethod();
|
||||||
|
if (mb == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mb.MethodImplementationFlags & MethodImplAttributes.AggressiveInlining) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (mb.IsDefined(typeof(StackTraceHiddenAttribute), false))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var declaringType = mb.DeclaringType;
|
||||||
|
if (declaringType?.IsDefined(typeof(StackTraceHiddenAttribute), false) == true)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// if we can't get the attributes then fall back to including it.
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<StackFrame> FilterStackFrames(this IEnumerable<StackFrame?> frames)
|
||||||
|
{
|
||||||
|
var allFrames = frames.ToArray();
|
||||||
|
var numberOfFrames = allFrames.Length;
|
||||||
|
|
||||||
|
for (var i = 0; i < numberOfFrames; i++)
|
||||||
|
{
|
||||||
|
var thisFrame = allFrames[i];
|
||||||
|
if (thisFrame == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// always include the last frame
|
||||||
|
if (i == numberOfFrames - 1)
|
||||||
|
{
|
||||||
|
yield return thisFrame;
|
||||||
|
}
|
||||||
|
else if (ShowInStackTrace(thisFrame))
|
||||||
|
{
|
||||||
|
yield return thisFrame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetMethodName(ref MethodBase method, out bool isAsync)
|
||||||
|
{
|
||||||
|
var declaringType = method.DeclaringType;
|
||||||
|
|
||||||
|
if (declaringType?.IsDefined(typeof(CompilerGeneratedAttribute), false) == true)
|
||||||
|
{
|
||||||
|
isAsync = typeof(IAsyncStateMachine).IsAssignableFrom(declaringType);
|
||||||
|
if (isAsync || typeof(IEnumerator).IsAssignableFrom(declaringType))
|
||||||
|
{
|
||||||
|
TryResolveStateMachineMethod(ref method, out declaringType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isAsync = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new StringBuilder(256);
|
||||||
|
|
||||||
|
var fullName = method.DeclaringType?.FullName;
|
||||||
|
if (fullName != null)
|
||||||
|
{
|
||||||
|
// See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs#L247-L253
|
||||||
|
builder.Append(fullName.Replace('+', '.'));
|
||||||
|
builder.Append('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(method.Name);
|
||||||
|
if (method.IsGenericMethod)
|
||||||
|
{
|
||||||
|
builder.Append('[');
|
||||||
|
builder.Append(string.Join(",", method.GetGenericArguments().Select(t => t.Name)));
|
||||||
|
builder.Append(']');
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType)
|
||||||
|
{
|
||||||
|
// https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs#L400-L455
|
||||||
|
declaringType = method.DeclaringType ?? throw new ArgumentException("Method must have a declaring type.", nameof(method));
|
||||||
|
|
||||||
|
var parentType = declaringType.DeclaringType;
|
||||||
|
if (parentType == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IEnumerable<MethodInfo> GetDeclaredMethods(IReflect type) => type.GetMethods(
|
||||||
|
BindingFlags.Public |
|
||||||
|
BindingFlags.NonPublic |
|
||||||
|
BindingFlags.Static |
|
||||||
|
BindingFlags.Instance |
|
||||||
|
BindingFlags.DeclaredOnly);
|
||||||
|
|
||||||
|
var methods = GetDeclaredMethods(parentType);
|
||||||
|
|
||||||
|
foreach (var candidateMethod in methods)
|
||||||
|
{
|
||||||
|
var attributes = candidateMethod.GetCustomAttributes<StateMachineAttribute>(false);
|
||||||
|
|
||||||
|
bool foundAttribute = false, foundIteratorAttribute = false;
|
||||||
|
foreach (var asma in attributes)
|
||||||
|
{
|
||||||
|
if (asma.StateMachineType != declaringType)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foundAttribute = true;
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
foundIteratorAttribute |= asma is IteratorStateMachineAttribute or AsyncIteratorStateMachineAttribute;
|
||||||
|
#else
|
||||||
|
foundIteratorAttribute |= asma is IteratorStateMachineAttribute;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundAttribute)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
method = candidateMethod;
|
||||||
|
declaringType = candidateMethod.DeclaringType!;
|
||||||
|
return foundIteratorAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,33 +0,0 @@
|
|||||||
namespace Spectre.Console;
|
|
||||||
|
|
||||||
internal static class MethodExtensions
|
|
||||||
{
|
|
||||||
public static string GetName(this MethodBase? method)
|
|
||||||
{
|
|
||||||
if (method is null)
|
|
||||||
{
|
|
||||||
return "<unknown method>";
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder = new StringBuilder(256);
|
|
||||||
|
|
||||||
var fullName = method.DeclaringType?.FullName;
|
|
||||||
if (fullName != null)
|
|
||||||
{
|
|
||||||
// See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs#L247-L253
|
|
||||||
builder.Append(fullName.Replace('+', '.'));
|
|
||||||
builder.Append('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Append(method.Name);
|
|
||||||
|
|
||||||
if (method.IsGenericMethod)
|
|
||||||
{
|
|
||||||
builder.Append('[');
|
|
||||||
builder.Append(string.Join(",", method.GetGenericArguments().Select(t => t.Name)));
|
|
||||||
builder.Append(']');
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user