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");
+ }
+ }
+ }
+}