Add pure ToStringDemystified extension method that does not change exception's state.

This commit is contained in:
Sergey Tepliakov 2018-02-21 19:50:30 -08:00
parent 54f4b0c24b
commit 449f0dabfe
2 changed files with 105 additions and 4 deletions

View File

@ -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. // 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.Collections.Generic.Enumerable;
using System.Reflection; using System.Reflection;
namespace System.Diagnostics namespace System.Diagnostics
{ {
/// <nodoc />
public static class ExceptionExtentions public static class ExceptionExtentions
{ {
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 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)
=> stackTraceString.SetValue(exception, value);
/// <summary>
/// Demystifies the given <paramref name="exception"/> and tracks the original stack traces for the whole exception tree.
/// </summary>
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)
{ {
stackTraceString.SetValue(exception, stackTrace.ToString()); exception.SetStackTracesString(stackTrace.ToString());
} }
if (exception is AggregateException aggEx) if (exception is AggregateException aggEx)
{ {
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
{ {
@ -38,5 +57,39 @@ namespace System.Diagnostics
return exception; return exception;
} }
/// <summary>
/// Gets demystified string representation of the <paramref name="exception"/>.
/// </summary>
/// <remarks>
/// <see cref="Demystify{T}"/> method mutates the exception instance that can cause
/// issues if a system relies on the stack trace be in the specific form.
/// Unlike <see cref="Demystify{T}"/> this method is pure. It calls <see cref="Demystify{T}"/> first,
/// computes a demystified string representation and then restores the original state of the exception back.
/// </remarks>
[Contracts.Pure]
public static string ToStringDemystified(this Exception exception)
{
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();
}
} }
} }

View File

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