控制台如何获取异常的堆栈跟踪?

时间:2016-02-18 22:35:38

标签: c# .net exception stack-trace

在C#中,我试图修改我创建的自定义异常的StackTrace属性。为此,我只是覆盖了StackTrace getter,它似乎正在运行(Exception将它作为virtual方法,所以它应该可以工作)。这是我的自定义异常:

class CustomTimeoutException : Exception
{
    private string oldStack;

    public CustomTimeoutException(string message, String stack)
        : base(message)
    {
        oldStack = stack;
    }

    public override string StackTrace
    {
        get
        {

            return oldStack;
        }
    }
    public override string Message
    {
        get
        {
            return "lalalala2";
        }
    }

    public override System.Collections.IDictionary Data
    {
        get
        {
            return null;
        }
    }

}

我通过以下方式使用此课程:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Program p = new Program();
            p.throwTimeout2();
        }
        catch(TimeoutException e)
        {
            CustomTimeoutException tor = new CustomTimeoutException(e.Message + "with more", e.StackTrace);
            Console.WriteLine(tor);
            Console.WriteLine(tor.StackTrace);
            throw tor;
        }
    }
    public void throwTimeout2()
    {
        throwTimeout();
    }
    public void throwTimeout()
    {
        throw new TimeoutException("this is a message ");
    }
}

当我将属性StackTrace写入控制台时,它会打印StackTrace的{​​{1}},这意味着它实际上会覆盖它。但是,当控制台显示错误时,TimeoutException不是从我的媒体获取的错误,但StackTrace是从Message属性获取的Message我是也是压倒一切。

那么,控制台从何处获取异常的StackTrace?我正在使用Visual Studio进行运行。

1 个答案:

答案 0 :(得分:0)

(注意:有一点我仍然不明白你的问题是,虽然你似乎想要一种方法来影响如何向控制台报告异常,你也说你有一些"工具"显然可以访问Exception对象本身,并报告最内层的异常,即在InnerException属性链之后,直到它到达最后一个这两个陈述似乎彼此矛盾,所以我不确定我是否完全理解这个问题。但是,我花了一些时间研究它,并将分享我所拥有的。:)。)


恕我直言,没有一个好方法可以做你想要的。有一种丑陋的方式(见下文),但我不推荐它。特别是因为你的主要限制是"tool that uses the message";恕我直言,改变生产代码只是为了适应某些工具,这是一个非常的坏主意。如果不是彻头彻尾的错误代码,那条路会导致严重的维护问题。

如果您确实必须更改异常消息,您应该只是咬住子弹并将要保留的整个堆栈跟踪文本折腾到消息中。试图弄乱最终抛出的异常本身的堆栈跟踪是坏消息。

那说......


如果要将Console.WriteLine()的诊断调用添加到方法覆盖中,您将确认(因为您可能已经猜到)在重新抛出异常时未调用StackTrace属性并由CLR报告给控制台。覆盖该物业无济于事。

尝试该策略至少有两个问题:

  1. StackTrace属性只是一个string,包含有关堆栈跟踪的格式化信息。 Exception对象确实包含私有字段_stackTrace。当您使用throw对象执行Exception语句时,CLR会将当前堆栈跟踪信息存储到此字段中。事实上你必须找到一些方法来覆盖这个字段,以伪造一个不同于实际的网站。
  2. CLR异常处理利用现有的Windows异常处理基础结构。抛出异常时发生的第一件事是异常处理代码试图在堆栈中找到处理程序。如果找不到,那么你会得到" Unhandled Exception:"信息。但请注意,在此过程中,堆栈尚未解开。因此,异常处理代码甚至不需要检查Exception对象来确定抛出站点;堆栈仍然存在,可以直接报告其状态。如果确实异常处理代码直接查看堆栈状态,则可以使用Exception对象来实现 nothing ,这可能会影响报告。
  3. 现在,实际上,对于我在Windows 8.1计算机上运行的.NET版本,似乎CLR正在报告存储在Exception对象和不直接检查堆栈,按照上面的选项#2。所以这样的事情确实有效:

    ExceptionWrapper类

    class ExceptionWrapper : Exception
    {
        private readonly string _trace;
        private readonly string _message;
        private readonly string _toString;
        private readonly object _stackTrace;
    
        public ExceptionWrapper(Exception e)
        {
            _trace = e.StackTrace;
            _message = e.Message + ", wrapped";
            _toString = e.ToString();
            _stackTrace = typeof(Exception).GetField("_stackTrace",
                BindingFlags.NonPublic | BindingFlags.Instance).GetValue(e);
        }
    
        public override string StackTrace { get { return _trace; } }
    
        public override string Message
        {
            get
            {
                typeof(Exception)
                    .GetField("_stackTrace", BindingFlags.NonPublic | BindingFlags.Instance)
                    .SetValue(this, _stackTrace);
                return _message;
            }
        }
    
        public override string ToString() { return _toString; }
    }
    

    测试计划

    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                M1();
            }
            catch (Exception e)
            {
                throw;
            }
        }
    
        private static void M1()
        {
            try
            {
                M();
            }
            catch (Exception e)
            {
                throw new ExceptionWrapper(e);
            }
        }
    
        static void M()
        {
            throw new Exception("test exception");
        }
    }
    

    但有一个非常重要的警告。这只是因为两个具体事实:

    1. CLR没有利用当前堆栈状态,而是信任Exception对象以在_stackTrace字段中包含正确的堆栈信息。
    2. 在检索堆栈跟踪之前,CLR调用Message对象的Exception getter。这使得对象有机会在CLR访问它之前覆盖_stackTrace字段,以便报告堆栈跟踪。
    3. 就我所知,这些都是完全无证的实施细节。第二个特别是非常脆弱;虽然我可以看到为什么CLR可能总是必须坚持第一个细节,但我认为没有理由认为微软(更不用担心CLS的其他实现者)应该感到被迫在第二点保留行为。

      编写依赖于这些实现细节的代码只是在寻找麻烦。无论这里的目标是什么,我都无法想象为了满足这种需求而遭受破坏项目的风险是值得的。恕我直言,简单地重写这个"工具"会更合理。你正试图容纳。

      工具本质上(或者至少应该是)本质上是简单的,并且不仅可以重写它,而且可以产生您想要的结果,而无需在第三方代码中查找私有实现细节,它还可以为您提供有机会 a)添加新功能,例如提供控制如何处理异常数据的选项(例如,查看除最内层异常之外的其他内容),以及 b)有一个你的工具有源代码,你可以用它来进一步改进。


      我强烈建议你不要把自己画在这个角落里。 :)