【问题标题】:Attach StackTrace To Exception Without Throwing in C# / .NET将 StackTrace 附加到异常而不抛出 C#/.NET
【发布时间】:2016-09-02 18:13:59
【问题描述】:

我有一个组件使用返回值而不是标准异常处理来处理错误。除了错误代码之外,它还返回错误发生位置的堆栈跟踪。我用来调用组件的包装器会解释返回码并抛出异常。

我想让包装器抛出一个异常,其中包括从组件捕获的堆栈跟踪信息。我希望它看起来好像异常是从错误的原始站点抛出的,即使它是在其他地方抛出的。 更具体地说,我希望 Visual Studio 测试运行程序显示的堆栈跟踪能够反映正确的位置。

有没有办法做到这一点?如果我能避免访问私有成员的低级反射技巧也很好,但我会尽我所能。

我不关心如何捕获堆栈跟踪,我关心的是将已捕获的堆栈跟踪附加到异常。

我尝试覆盖 StackTrace 属性,但 Visual Studio 正在从其他地方提取堆栈跟踪数据,并且似乎完全忽略了被覆盖的属性

CustomException GenerateExcpetion()
{
    return new CustomException();
}

void ThrowException(Exception ex)
{
    Trace.WriteLine("Displaying Exception");
    Trace.WriteLine(ex.ToString());
    var edi = ExceptionDispatchInfo.Capture(ex);
    edi.Throw();
}

[TestMethod]
public void Test006()
{
    var ex = GenerateExcpetion();
    ThrowException(ex);
}

public class CustomException : Exception
{
    string _stackTrace;
    public CustomException()
    {
        _stackTrace = Environment.StackTrace;
    }

    public override string StackTrace
    {
        get
        {
            return base.StackTrace;
        }
    }
}

Exception.ToString() 方法从私有属性中提取堆栈跟踪数据,因此不会显示来自覆盖的堆栈跟踪。

CustomException:引发了“CustomException”类型的异常。

ExceptionDispatchInfo 也会从私有属性中查找堆栈跟踪数据,因此它无法找到任何数据,并且当您抛出此自定义异常时,新的堆栈跟踪会附加到异常的位置它被抛出。如果直接使用throw,则将私有堆栈信息设置为发生throw的地方。

【问题讨论】:

标签: c# .net


【解决方案1】:

只需创建您自己的Exception 类型并覆盖StackTrace 属性:

class MyException : Exception
{
    private string oldStackTrace;

    public MyException(string message, string stackTrace) : base(message)
    {
        this.oldStackTrace = stackTrace;
    }


    public override string StackTrace
    {
        get
        {
            return this.oldStackTrace;
        }
    }
}

【讨论】:

  • 这种方法的问题是,当异常被传递给记录器时,StackTrace 仍然是旧的,而不是被覆盖的。唯一可行的方法是通过反射。
  • 实际上没有必要创建oldStackTrace。通过覆盖StackTrace,它可以在构造函数中设置。
  • 当您在异常对象上调用.ToString() 时,这不起作用,因为底层ToString() 方法使用对象的私有字段而不是属性。
  • @Mayank 只是覆盖 ToString()?
  • @adjan 不能正常工作,好像代码的某些部分显式使用堆栈,那么它将有无效的信息。
【解决方案2】:

好吧,没有什么优雅的,这是我的基于反射的方法。

public static class ExceptionUtilities
{
    private static readonly FieldInfo STACK_TRACE_STRING_FI = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance);
    private static readonly Type TRACE_FORMAT_TI = Type.GetType("System.Diagnostics.StackTrace").GetNestedType("TraceFormat", BindingFlags.NonPublic);
    private static readonly MethodInfo TRACE_TO_STRING_MI = typeof(StackTrace).GetMethod("ToString", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { TRACE_FORMAT_TI }, null);

    public static Exception SetStackTrace(this Exception target, StackTrace stack)
    {
        var getStackTraceString = TRACE_TO_STRING_MI.Invoke(stack, new object[] { Enum.GetValues(TRACE_FORMAT_TI).GetValue(0) });
        STACK_TRACE_STRING_FI.SetValue(target, getStackTraceString);
        return target;
    }
}

将格式化的 StackTrace 字符串写入 _stackTraceString 属性似乎足以让 Visual Studio 测试运行程序和 Exception.ToString() 方法相信堆栈是由 throw 生成的(实际上没有抛出任何东西)。

用法见下文:

    StackTrace GetDeeperStackTrace(int depth)
    {
        if (depth > 0)
        {
            return GetDeeperStackTrace(depth - 1);
        }
        else
        {
            return new StackTrace(0, true);
        }
    }

    [TestMethod]
    public void Test007()
    {
        Exception needStackTrace = new Exception("Some exception");
        var st = GetDeeperStackTrace(3);

        needStackTrace.SetStackTrace(st);

        Trace.Write(needStackTrace.ToString());

        throw new Exception("Nested has custom stack trace", needStackTrace);
    }

【讨论】:

  • 了解更多信息:StackTrace.TraceFormat 是一个枚举(如Enum.GetValues 所示,第一个值为TraceFormat.Normal。您可以查看StackTrace.TraceFormat @ 的参考源987654321@ 和调用的ToString 方法here
  • 这行得通。单元测试现在通过了。一个问题,如果有人有答案。即使堆栈跟踪具有我使用此函数分配的值,如果我使用断点打开异常,我仍然是堆栈跟踪上的旧值。就像VS没有刷新一样。
【解决方案3】:

您可以使用:Environment.StackTrace 捕获组件中发生错误时的堆栈跟踪,然后将其与其他错误信息一起返回或重新抛出。

您可以手动构建堆栈帧以创建完整的跟踪。请参阅StackFrame/StackTrace 了解更多信息。

【讨论】:

    【解决方案4】:

    对我来说,跟随似乎是最好的方法。这种方法适用于记录器,因为 .ToString() for Exception 使用私有字段进行堆栈跟踪。

    public class CustomException : Exception
    {
        public CustomException()
        {
            var stackTraceField = typeof(CustomException).BaseType
                .GetField("_stackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
    
            stackTraceField.SetValue(this, Environment.StackTrace);
        }
    }
    

    【讨论】:

    • 这被证明非常适合我的需求(单元测试以确保被测方法正确使用异常的堆栈跟踪)。
    • 你可以使用typeof(Exception)
    【解决方案5】:

    还有一个更复杂的解决方案,可以避免反射部分的任何运行时开销

    public static class ExceptionExtensions
    {
        public static Exception SetStackTrace(this Exception target, StackTrace stack) => _SetStackTrace(target, stack);
    
        private static readonly Func<Exception, StackTrace, Exception> _SetStackTrace = new Func<Func<Exception, StackTrace, Exception>>(() =>
        {
            ParameterExpression target = Expression.Parameter(typeof(Exception));
            ParameterExpression stack = Expression.Parameter(typeof(StackTrace));
            Type traceFormatType = typeof(StackTrace).GetNestedType("TraceFormat", BindingFlags.NonPublic);
            MethodInfo toString = typeof(StackTrace).GetMethod("ToString", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { traceFormatType }, null);
            object normalTraceFormat = Enum.GetValues(traceFormatType).GetValue(0);
            MethodCallExpression stackTraceString = Expression.Call(stack, toString, Expression.Constant(normalTraceFormat, traceFormatType));
            FieldInfo stackTraceStringField = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance);
            BinaryExpression assign = Expression.Assign(Expression.Field(target, stackTraceStringField), stackTraceString);
            return Expression.Lambda<Func<Exception, StackTrace, Exception>>(Expression.Block(assign, target), target, stack).Compile();
        })();
    }
    

    【讨论】:

    • net core还是没有内置方式?
    【解决方案6】:
    var error = new Exception(ex.Message);
    
    var STACK_TRACE_STRING_FI = typeof(Exception).GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance);
    
    STACK_TRACE_STRING_FI.SetValue(error, ex.StackTrace);
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-03
    • 1970-01-01
    • 2012-01-24
    • 2023-01-03
    • 1970-01-01
    • 2013-03-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多