Keeps message (#79)
* Test for exception message
* Revert "Allow demystifier to add strings to existing StringBuilder (#73)"
This reverts commit 7aa753d5c7.
* Bump version
This commit is contained in:
parent
9c2917fa47
commit
cdf53b2655
@ -171,10 +171,11 @@ namespace System.Diagnostics
|
|||||||
// ResolveStateMachineMethod may have set declaringType to null
|
// ResolveStateMachineMethod may have set declaringType to null
|
||||||
if (type != null)
|
if (type != null)
|
||||||
{
|
{
|
||||||
methodDisplayInfo.DeclaringType = type;
|
var declaringTypeName = TypeNameHelper.GetTypeDisplayName(type, fullName: true, includeGenericParameterNames: true);
|
||||||
|
methodDisplayInfo.DeclaringTypeName = declaringTypeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method is MethodInfo mi)
|
if (method is System.Reflection.MethodInfo mi)
|
||||||
{
|
{
|
||||||
var returnParameter = mi.ReturnParameter;
|
var returnParameter = mi.ReturnParameter;
|
||||||
if (returnParameter != null)
|
if (returnParameter != null)
|
||||||
@ -187,6 +188,7 @@ namespace System.Diagnostics
|
|||||||
{
|
{
|
||||||
Prefix = "",
|
Prefix = "",
|
||||||
Name = "",
|
Name = "",
|
||||||
|
Type = TypeNameHelper.GetTypeDisplayName(mi.ReturnType, fullName: false, includeGenericParameterNames: true).ToString(),
|
||||||
ResolvedType = mi.ReturnType,
|
ResolvedType = mi.ReturnType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -538,6 +540,7 @@ namespace System.Diagnostics
|
|||||||
{
|
{
|
||||||
var parameterType = parameter.ParameterType;
|
var parameterType = parameter.ParameterType;
|
||||||
var prefix = GetPrefix(parameter, parameterType);
|
var prefix = GetPrefix(parameter, parameterType);
|
||||||
|
var parameterTypeString = "?";
|
||||||
|
|
||||||
if (parameterType == null)
|
if (parameterType == null)
|
||||||
{
|
{
|
||||||
@ -545,6 +548,7 @@ namespace System.Diagnostics
|
|||||||
{
|
{
|
||||||
Prefix = prefix,
|
Prefix = prefix,
|
||||||
Name = parameter.Name,
|
Name = parameter.Name,
|
||||||
|
Type = parameterTypeString,
|
||||||
ResolvedType = parameterType,
|
ResolvedType = parameterType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -568,22 +572,40 @@ namespace System.Diagnostics
|
|||||||
parameterType = parameterType.GetElementType();
|
parameterType = parameterType.GetElementType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parameterTypeString = TypeNameHelper.GetTypeDisplayName(parameterType, fullName: false, includeGenericParameterNames: true);
|
||||||
|
|
||||||
return new ResolvedParameter
|
return new ResolvedParameter
|
||||||
{
|
{
|
||||||
Prefix = prefix,
|
Prefix = prefix,
|
||||||
Name = parameter.Name,
|
Name = parameter.Name,
|
||||||
|
Type = parameterTypeString,
|
||||||
ResolvedType = parameterType,
|
ResolvedType = parameterType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ResolvedParameter GetValueTupleParameter(IList<string> tupleNames, string prefix, string name, Type parameterType)
|
private static ResolvedParameter GetValueTupleParameter(IList<string> tupleNames, string prefix, string name, Type parameterType)
|
||||||
{
|
{
|
||||||
return new ValueTupleResolvedParameter
|
string typeName;
|
||||||
|
|
||||||
|
if (parameterType.IsValueTuple())
|
||||||
|
{
|
||||||
|
typeName = GetValueTupleParameterName(tupleNames, parameterType);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Need to unwrap the first generic argument first.
|
||||||
|
var genericTypeName = TypeNameHelper.GetTypeNameForGenericType(parameterType);
|
||||||
|
var valueTupleFullName = GetValueTupleParameterName(tupleNames, parameterType.GetGenericArguments()[0]);
|
||||||
|
typeName = $"{genericTypeName}<{valueTupleFullName}>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResolvedParameter
|
||||||
{
|
{
|
||||||
TupleNames = tupleNames,
|
|
||||||
Prefix = prefix,
|
Prefix = prefix,
|
||||||
Name = name,
|
Name = name,
|
||||||
ResolvedType = parameterType
|
Type = typeName,
|
||||||
|
ResolvedType = parameterType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic.Enumerable;
|
using System.Collections.Generic.Enumerable;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace System.Diagnostics
|
namespace System.Diagnostics
|
||||||
{
|
{
|
||||||
@ -13,16 +12,27 @@ namespace System.Diagnostics
|
|||||||
{
|
{
|
||||||
private static readonly FieldInfo stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
|
private static readonly FieldInfo stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
|
||||||
|
public static T Demystify<T>(this T exception) where T : Exception
|
||||||
|
=> Demystify(exception, originalStacksTracker: null);
|
||||||
|
|
||||||
|
private static string GetStackTracesString(this Exception exception)
|
||||||
|
=> (string)stackTraceString.GetValue(exception);
|
||||||
|
|
||||||
private static void SetStackTracesString(this Exception exception, string value)
|
private static void SetStackTracesString(this Exception exception, string value)
|
||||||
=> stackTraceString.SetValue(exception, value);
|
=> stackTraceString.SetValue(exception, value);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Demystifies the given <paramref name="exception"/> and tracks the original stack traces for the whole exception tree.
|
/// Demystifies the given <paramref name="exception"/> and tracks the original stack traces for the whole exception tree.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static T Demystify<T>(this T exception) where T : Exception
|
private static T Demystify<T>(this T exception, Dictionary<Exception, string> originalStacksTracker) where T : Exception
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (originalStacksTracker?.ContainsKey(exception) == false)
|
||||||
|
{
|
||||||
|
originalStacksTracker[exception] = exception.GetStackTracesString();
|
||||||
|
}
|
||||||
|
|
||||||
var stackTrace = new EnhancedStackTrace(exception);
|
var stackTrace = new EnhancedStackTrace(exception);
|
||||||
|
|
||||||
if (stackTrace.FrameCount > 0)
|
if (stackTrace.FrameCount > 0)
|
||||||
@ -34,11 +44,11 @@ namespace System.Diagnostics
|
|||||||
{
|
{
|
||||||
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
|
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
|
||||||
{
|
{
|
||||||
ex.Demystify();
|
ex.Demystify(originalStacksTracker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exception.InnerException?.Demystify();
|
exception.InnerException?.Demystify(originalStacksTracker);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -58,7 +68,28 @@ namespace System.Diagnostics
|
|||||||
/// computes a demystified string representation and then restores the original state of the exception back.
|
/// computes a demystified string representation and then restores the original state of the exception back.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Contracts.Pure]
|
[Contracts.Pure]
|
||||||
public static string ToStringDemystified(this Exception exception)
|
public static string ToStringDemystified(this Exception exception)
|
||||||
=> new StringBuilder().AppendDemystified(exception).ToString();
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var originalStacks = new Dictionary<Exception, string>();
|
||||||
|
exception.Demystify(originalStacks);
|
||||||
|
|
||||||
|
var result = exception.ToString();
|
||||||
|
|
||||||
|
foreach (var kvp in originalStacks)
|
||||||
|
{
|
||||||
|
kvp.Key.SetStackTracesString(kvp.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Processing exceptions shouldn't throw exceptions; if it fails
|
||||||
|
}
|
||||||
|
|
||||||
|
return exception.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,8 +11,8 @@ namespace System.Diagnostics
|
|||||||
{
|
{
|
||||||
public MethodBase MethodBase { get; set; }
|
public MethodBase MethodBase { get; set; }
|
||||||
|
|
||||||
public Type DeclaringType { get; set; }
|
public string DeclaringTypeName { get; set; }
|
||||||
|
|
||||||
public bool IsAsync { get; set; }
|
public bool IsAsync { get; set; }
|
||||||
|
|
||||||
public bool IsLambda { get; set; }
|
public bool IsLambda { get; set; }
|
||||||
@ -39,9 +39,11 @@ namespace System.Diagnostics
|
|||||||
|
|
||||||
internal StringBuilder Append(StringBuilder builder)
|
internal StringBuilder Append(StringBuilder builder)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (IsAsync)
|
if (IsAsync)
|
||||||
{
|
{
|
||||||
builder.Append("async ");
|
builder
|
||||||
|
.Append("async ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ReturnParameter != null)
|
if (ReturnParameter != null)
|
||||||
@ -50,7 +52,7 @@ namespace System.Diagnostics
|
|||||||
builder.Append(" ");
|
builder.Append(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DeclaringType != null)
|
if (!string.IsNullOrEmpty(DeclaringTypeName))
|
||||||
{
|
{
|
||||||
|
|
||||||
if (Name == ".ctor")
|
if (Name == ".ctor")
|
||||||
@ -58,16 +60,17 @@ namespace System.Diagnostics
|
|||||||
if (string.IsNullOrEmpty(SubMethod) && !IsLambda)
|
if (string.IsNullOrEmpty(SubMethod) && !IsLambda)
|
||||||
builder.Append("new ");
|
builder.Append("new ");
|
||||||
|
|
||||||
AppendDeclaringTypeName(builder);
|
builder.Append(DeclaringTypeName);
|
||||||
}
|
}
|
||||||
else if (Name == ".cctor")
|
else if (Name == ".cctor")
|
||||||
{
|
{
|
||||||
builder.Append("static ");
|
builder.Append("static ");
|
||||||
AppendDeclaringTypeName(builder);
|
builder.Append(DeclaringTypeName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AppendDeclaringTypeName(builder)
|
builder
|
||||||
|
.Append(DeclaringTypeName)
|
||||||
.Append(".")
|
.Append(".")
|
||||||
.Append(Name);
|
.Append(Name);
|
||||||
}
|
}
|
||||||
@ -142,10 +145,5 @@ namespace System.Diagnostics
|
|||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private StringBuilder AppendDeclaringTypeName(StringBuilder builder)
|
|
||||||
{
|
|
||||||
return DeclaringType != null ? builder.AppendTypeDisplayName(DeclaringType, fullName: true, includeGenericParameterNames: true) : builder;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ namespace System.Diagnostics
|
|||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
public Type ResolvedType { get; set; }
|
public Type ResolvedType { get; set; }
|
||||||
|
|
||||||
public string Prefix { get; set; }
|
public string Prefix { get; set; }
|
||||||
@ -23,15 +25,7 @@ namespace System.Diagnostics
|
|||||||
.Append(" ");
|
.Append(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ResolvedType != null)
|
sb.Append(Type);
|
||||||
{
|
|
||||||
AppendTypeName(sb);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.Append("?");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Name))
|
if (!string.IsNullOrEmpty(Name))
|
||||||
{
|
{
|
||||||
sb.Append(" ")
|
sb.Append(" ")
|
||||||
@ -40,10 +34,5 @@ namespace System.Diagnostics
|
|||||||
|
|
||||||
return sb;
|
return sb;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void AppendTypeName(StringBuilder sb)
|
|
||||||
{
|
|
||||||
sb.AppendTypeDisplayName(ResolvedType, fullName: false, includeGenericParameterNames: true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
// Copyright (c) Ben A Adams. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Collections.Generic.Enumerable;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace System.Diagnostics
|
|
||||||
{
|
|
||||||
public static class StringBuilderExtentions
|
|
||||||
{
|
|
||||||
public static StringBuilder AppendDemystified(this StringBuilder builder, Exception exception)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var stackTrace = new EnhancedStackTrace(exception);
|
|
||||||
|
|
||||||
if (stackTrace.FrameCount > 0)
|
|
||||||
{
|
|
||||||
stackTrace.Append(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exception is AggregateException aggEx)
|
|
||||||
{
|
|
||||||
foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions))
|
|
||||||
{
|
|
||||||
builder.AppendInnerException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exception.InnerException != null)
|
|
||||||
{
|
|
||||||
builder.AppendInnerException(exception.InnerException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Processing exceptions shouldn't throw exceptions; if it fails
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AppendInnerException(this StringBuilder builder, Exception exception)
|
|
||||||
=> builder.Append(" ---> ")
|
|
||||||
.AppendDemystified(exception)
|
|
||||||
.AppendLine()
|
|
||||||
.Append(" --- End of inner exception stack trace ---");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,7 +7,7 @@ using System.Text;
|
|||||||
namespace System.Diagnostics
|
namespace System.Diagnostics
|
||||||
{
|
{
|
||||||
// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs
|
// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs
|
||||||
public static class TypeNameHelper
|
public class TypeNameHelper
|
||||||
{
|
{
|
||||||
public static readonly Dictionary<Type, string> BuiltInTypeNames = new Dictionary<Type, string>
|
public static readonly Dictionary<Type, string> BuiltInTypeNames = new Dictionary<Type, string>
|
||||||
{
|
{
|
||||||
@ -43,12 +43,6 @@ namespace System.Diagnostics
|
|||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StringBuilder AppendTypeDisplayName(this StringBuilder builder, Type type, bool fullName = true, bool includeGenericParameterNames = false)
|
|
||||||
{
|
|
||||||
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a name of given generic type without '`'.
|
/// Returns a name of given generic type without '`'.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1,62 +0,0 @@
|
|||||||
// Copyright (c) Ben A Adams. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.Internal;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace System.Diagnostics
|
|
||||||
{
|
|
||||||
public class ValueTupleResolvedParameter : ResolvedParameter
|
|
||||||
{
|
|
||||||
public IList<string> TupleNames { get; set; }
|
|
||||||
|
|
||||||
protected override void AppendTypeName(StringBuilder sb)
|
|
||||||
{
|
|
||||||
if (ResolvedType.IsValueTuple())
|
|
||||||
{
|
|
||||||
AppendValueTupleParameterName(sb, ResolvedType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Need to unwrap the first generic argument first.
|
|
||||||
sb.Append(TypeNameHelper.GetTypeNameForGenericType(ResolvedType));
|
|
||||||
sb.Append("<");
|
|
||||||
AppendValueTupleParameterName(sb, ResolvedType.GetGenericArguments()[0]);
|
|
||||||
sb.Append(">");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void AppendValueTupleParameterName(StringBuilder sb, Type parameterType)
|
|
||||||
{
|
|
||||||
sb.Append("(");
|
|
||||||
var args = parameterType.GetGenericArguments();
|
|
||||||
for (var i = 0; i < args.Length; i++)
|
|
||||||
{
|
|
||||||
if (i > 0)
|
|
||||||
{
|
|
||||||
sb.Append(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendTypeDisplayName(args[i], fullName: false, includeGenericParameterNames: true);
|
|
||||||
|
|
||||||
if (i >= TupleNames.Count)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var argName = TupleNames[i];
|
|
||||||
if (argName == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append(" ");
|
|
||||||
sb.Append(argName);
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.Append(")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -47,5 +47,39 @@ namespace Ben.Demystifier.Test
|
|||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DemystifyKeepsMessage()
|
||||||
|
{
|
||||||
|
Exception ex = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("aaa")
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
{
|
||||||
|
["bbb"] = "ccc",
|
||||||
|
["ddd"] = "eee",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ex = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
var original = ex.ToString();
|
||||||
|
var endLine = (int)Math.Min((uint)original.IndexOf('\n'), original.Length);
|
||||||
|
|
||||||
|
original = original.Substring(0, endLine);
|
||||||
|
|
||||||
|
var stringDemystified = ex.ToStringDemystified();
|
||||||
|
endLine = (int)Math.Min((uint)stringDemystified.IndexOf('\n'), stringDemystified.Length);
|
||||||
|
|
||||||
|
stringDemystified = stringDemystified.Substring(0, endLine);
|
||||||
|
|
||||||
|
Assert.Equal(original, stringDemystified);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"publicReleaseRefSpec": [
|
"publicReleaseRefSpec": [
|
||||||
"^refs/heads/master$", // we release out of master
|
"^refs/heads/master$", // we release out of master
|
||||||
"^refs/heads/dev$", // we release out of develop
|
"^refs/heads/dev$", // we release out of develop
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user