diff --git a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs index 5c8ec4f..ff81cc7 100644 --- a/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs +++ b/src/Ben.Demystifier/EnhancedStackTrace.Frames.cs @@ -173,11 +173,10 @@ namespace System.Diagnostics // ResolveStateMachineMethod may have set declaringType to null if (type != null) { - var declaringTypeName = TypeNameHelper.GetTypeDisplayName(type, fullName: true, includeGenericParameterNames: true); - methodDisplayInfo.DeclaringTypeName = declaringTypeName; + methodDisplayInfo.DeclaringType = type; } - if (method is System.Reflection.MethodInfo mi) + if (method is MethodInfo mi) { var returnParameter = mi.ReturnParameter; if (returnParameter != null) @@ -190,7 +189,6 @@ namespace System.Diagnostics { Prefix = "", Name = "", - Type = TypeNameHelper.GetTypeDisplayName(mi.ReturnType, fullName: false, includeGenericParameterNames: true).ToString(), ResolvedType = mi.ReturnType, }; } @@ -542,7 +540,6 @@ namespace System.Diagnostics { var parameterType = parameter.ParameterType; var prefix = GetPrefix(parameter, parameterType); - var parameterTypeString = "?"; if (parameterType == null) { @@ -550,7 +547,6 @@ namespace System.Diagnostics { Prefix = prefix, Name = parameter.Name, - Type = parameterTypeString, ResolvedType = parameterType, }; } @@ -574,40 +570,22 @@ namespace System.Diagnostics parameterType = parameterType.GetElementType(); } - parameterTypeString = TypeNameHelper.GetTypeDisplayName(parameterType, fullName: false, includeGenericParameterNames: true); - return new ResolvedParameter { Prefix = prefix, Name = parameter.Name, - Type = parameterTypeString, ResolvedType = parameterType, }; } private static ResolvedParameter GetValueTupleParameter(IList tupleNames, string prefix, string name, Type parameterType) { - 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 + return new ValueTupleResolvedParameter { + TupleNames = tupleNames, Prefix = prefix, Name = name, - Type = typeName, - ResolvedType = parameterType, + ResolvedType = parameterType }; } diff --git a/src/Ben.Demystifier/ExceptionExtentions.cs b/src/Ben.Demystifier/ExceptionExtentions.cs index 23203d1..beb1d9c 100644 --- a/src/Ben.Demystifier/ExceptionExtentions.cs +++ b/src/Ben.Demystifier/ExceptionExtentions.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Generic.Enumerable; using System.Reflection; +using System.Text; namespace System.Diagnostics { @@ -12,27 +13,16 @@ namespace System.Diagnostics { private static readonly FieldInfo stackTraceString = typeof(Exception).GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); - public static T Demystify(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) => stackTraceString.SetValue(exception, value); /// /// Demystifies the given and tracks the original stack traces for the whole exception tree. /// - private static T Demystify(this T exception, Dictionary originalStacksTracker) where T : Exception + public static T Demystify(this T exception) where T : Exception { try { - if (originalStacksTracker?.ContainsKey(exception) == false) - { - originalStacksTracker[exception] = exception.GetStackTracesString(); - } - var stackTrace = new EnhancedStackTrace(exception); if (stackTrace.FrameCount > 0) @@ -44,11 +34,11 @@ namespace System.Diagnostics { foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) { - ex.Demystify(originalStacksTracker); + ex.Demystify(); } } - exception.InnerException?.Demystify(originalStacksTracker); + exception.InnerException?.Demystify(); } catch { @@ -68,28 +58,7 @@ namespace System.Diagnostics /// computes a demystified string representation and then restores the original state of the exception back. /// [Contracts.Pure] - public static string ToStringDemystified(this Exception exception) - { - try - { - var originalStacks = new Dictionary(); - 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(); - } + public static string ToStringDemystified(this Exception exception) + => new StringBuilder().AppendDemystified(exception).ToString(); } } diff --git a/src/Ben.Demystifier/ResolvedMethod.cs b/src/Ben.Demystifier/ResolvedMethod.cs index 41ad3a3..d4142d7 100644 --- a/src/Ben.Demystifier/ResolvedMethod.cs +++ b/src/Ben.Demystifier/ResolvedMethod.cs @@ -11,8 +11,8 @@ namespace System.Diagnostics { public MethodBase MethodBase { get; set; } - public string DeclaringTypeName { get; set; } - + public Type DeclaringType { get; set; } + public bool IsAsync { get; set; } public bool IsLambda { get; set; } @@ -39,11 +39,9 @@ namespace System.Diagnostics internal StringBuilder Append(StringBuilder builder) { - if (IsAsync) { - builder - .Append("async "); + builder.Append("async "); } if (ReturnParameter != null) @@ -52,7 +50,7 @@ namespace System.Diagnostics builder.Append(" "); } - if (!string.IsNullOrEmpty(DeclaringTypeName)) + if (DeclaringType != null) { if (Name == ".ctor") @@ -60,17 +58,16 @@ namespace System.Diagnostics if (string.IsNullOrEmpty(SubMethod) && !IsLambda) builder.Append("new "); - builder.Append(DeclaringTypeName); + AppendDeclaringTypeName(builder); } else if (Name == ".cctor") { builder.Append("static "); - builder.Append(DeclaringTypeName); + AppendDeclaringTypeName(builder); } else { - builder - .Append(DeclaringTypeName) + AppendDeclaringTypeName(builder) .Append(".") .Append(Name); } @@ -145,5 +142,10 @@ namespace System.Diagnostics return builder; } + + private StringBuilder AppendDeclaringTypeName(StringBuilder builder) + { + return DeclaringType != null ? builder.AppendTypeDisplayName(DeclaringType, fullName: true, includeGenericParameterNames: true) : builder; + } } } diff --git a/src/Ben.Demystifier/ResolvedParameter.cs b/src/Ben.Demystifier/ResolvedParameter.cs index 3843e8d..d611b2e 100644 --- a/src/Ben.Demystifier/ResolvedParameter.cs +++ b/src/Ben.Demystifier/ResolvedParameter.cs @@ -9,8 +9,6 @@ namespace System.Diagnostics { public string Name { get; set; } - public string Type { get; set; } - public Type ResolvedType { get; set; } public string Prefix { get; set; } @@ -25,7 +23,15 @@ namespace System.Diagnostics .Append(" "); } - sb.Append(Type); + if (ResolvedType != null) + { + AppendTypeName(sb); + } + else + { + sb.Append("?"); + } + if (!string.IsNullOrEmpty(Name)) { sb.Append(" ") @@ -34,5 +40,10 @@ namespace System.Diagnostics return sb; } + + protected virtual void AppendTypeName(StringBuilder sb) + { + sb.AppendTypeDisplayName(ResolvedType, fullName: false, includeGenericParameterNames: true); + } } } diff --git a/src/Ben.Demystifier/StringBuilderExtentions.cs b/src/Ben.Demystifier/StringBuilderExtentions.cs new file mode 100644 index 0000000..5eeb8e0 --- /dev/null +++ b/src/Ben.Demystifier/StringBuilderExtentions.cs @@ -0,0 +1,56 @@ +// 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); + + builder.Append(exception.GetType()); + if (!string.IsNullOrEmpty(exception.Message)) + { + builder.Append(": ").Append(exception.Message); + } + builder.Append(Environment.NewLine); + + 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 ---"); + } +} diff --git a/src/Ben.Demystifier/TypeNameHelper.cs b/src/Ben.Demystifier/TypeNameHelper.cs index a4ca3a8..a1426ad 100644 --- a/src/Ben.Demystifier/TypeNameHelper.cs +++ b/src/Ben.Demystifier/TypeNameHelper.cs @@ -7,7 +7,7 @@ using System.Text; namespace System.Diagnostics { // Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.TypeNameHelper.Sources/TypeNameHelper.cs - public class TypeNameHelper + public static class TypeNameHelper { public static readonly Dictionary BuiltInTypeNames = new Dictionary { @@ -43,6 +43,12 @@ namespace System.Diagnostics 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; + } + /// /// Returns a name of given generic type without '`'. /// diff --git a/src/Ben.Demystifier/ValueTupleResolvedParameter.cs b/src/Ben.Demystifier/ValueTupleResolvedParameter.cs new file mode 100644 index 0000000..6750ac9 --- /dev/null +++ b/src/Ben.Demystifier/ValueTupleResolvedParameter.cs @@ -0,0 +1,62 @@ +// 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 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(")"); + } + } +}