Merge pull request #60 from SergeyTeplyakov/toStringDemystified
Add pure ToStringDemystified extension method that does not change exception's state
This commit is contained in:
commit
21d47323cc
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
48
test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs
Normal file
48
test/Ben.Demystifier.Test/ToDemystifiedStringTests.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user