diff --git a/src/Ben.Demystifier/ExceptionExtentions.cs b/src/Ben.Demystifier/ExceptionExtentions.cs index b4ab1b9..23203d1 100644 --- a/src/Ben.Demystifier/ExceptionExtentions.cs +++ b/src/Ben.Demystifier/ExceptionExtentions.cs @@ -1,35 +1,54 @@ -// Copyright (c) Ben A Adams. All rights reserved. +// 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.Collections.Generic.Enumerable; using System.Reflection; namespace System.Diagnostics { + /// public static class ExceptionExtentions { 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 { try { + if (originalStacksTracker?.ContainsKey(exception) == false) + { + originalStacksTracker[exception] = exception.GetStackTracesString(); + } + var stackTrace = new EnhancedStackTrace(exception); if (stackTrace.FrameCount > 0) { - stackTraceString.SetValue(exception, stackTrace.ToString()); + exception.SetStackTracesString(stackTrace.ToString()); } if (exception is AggregateException aggEx) { foreach (var ex in EnumerableIList.Create(aggEx.InnerExceptions)) { - ex.Demystify(); + ex.Demystify(originalStacksTracker); } } - exception.InnerException?.Demystify(); + exception.InnerException?.Demystify(originalStacksTracker); } catch { @@ -38,5 +57,39 @@ namespace System.Diagnostics return exception; } + + /// + /// Gets demystified string representation of the . + /// + /// + /// method mutates the exception instance that can cause + /// issues if a system relies on the stack trace be in the specific form. + /// Unlike this method is pure. It calls first, + /// 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(); + } } } diff --git a/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs b/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs new file mode 100644 index 0000000..47ff7e2 --- /dev/null +++ b/test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Ben.Demystifier.Test +{ + public sealed class ToDemystifiedStringTests + { + private readonly ITestOutputHelper _output; + + public ToDemystifiedStringTests(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void DemystifyShouldNotAffectTheOriginalStackTrace() + { + try + { + SimpleMethodThatThrows().Wait(); + } + catch (Exception e) + { + var original = e.ToString(); + var stringDemystified = e.ToStringDemystified(); + + _output.WriteLine("Demystified: "); + _output.WriteLine(stringDemystified); + + _output.WriteLine("Original: "); + var afterDemystified = e.ToString(); + _output.WriteLine(afterDemystified); + + Assert.Equal(original, afterDemystified); + } + + async Task SimpleMethodThatThrows() + { + throw new InvalidOperationException("message"); + } + } + } +}