如何在C#中为每个方法调用添加Trace()?

30

我正在艰难地追踪锁定问题,因此我想记录每个方法调用的进入和退出。我以前在C++中完成过这样的操作,而无需向每个方法添加代码。在C#中是否可能实现?


既然您知道要锁定的内容以及大致位置,不妨发布一些代码... - Mitch Wheat
2
请注意,这样做很可能会显著改变您的应用程序的时间性能,从而使您的锁定问题消失。 - Ana Betts
1
通过自动添加额外的函数调用来更改代码失败的环境可能会修改状态,从而使死锁不再发生。发布代码可能是解决问题的最佳方法。 - Steven Behnke
非常正确 - 即使在应用程序运行时连接调试器也可以防止出现问题。我最终没有选择这种方法,而是使用WinDbg和数十个堆栈跟踪转储找到了原因。 - Jon Tackabury
1
仅供记录,我最终没有采用这种方法。我最终使用WinDbg和数十个堆栈跟踪转储解决了问题。 - Jon Tackabury
我遇到了相同的问题,但是使用的是C++,我不想在每个方法中添加代码。请问你能否指点我正确的方向。我并不是要调试应用程序,而是想使用这个调用入口/退出日志进行其他分析。 - Bruce
6个回答

20

也许您最好的选择是使用AOP(面向切面编程)框架,在方法执行之前和之后自动调用跟踪代码。在AOP和.NET方面,一个受欢迎的选择是PostSharp


谢谢 - PostSharp甚至附带了一个类似的示例项目。 :) - Jon Tackabury
1
最近引入了PostSharp诊断工具包 http://www.sharpcrafters.com/blog/post/Configuring-PostSharp-Diagnostics-Toolkits.aspx - Michael Freidgeim

6
如果您的主要目标是记录函数的进出点以及偶尔的中间信息,我建议使用一个可处理对象作为日志记录工具。在构造函数追踪函数进入点Dispose()方法追踪函数退出点,这样调用代码就可以简单地在每个方法的代码周围包装一个单独的using语句。还提供了任意记录中间内容的方法。这里有一个完整的C# ETW事件跟踪类和一个函数进出点包装器:
using System;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace MyExample
{
    // This class traces function entry/exit
    // Constructor is used to automatically log function entry.
    // Dispose is used to automatically log function exit.
    // use "using(FnTraceWrap x = new FnTraceWrap()){ function code }" pattern for function entry/exit tracing
    public class FnTraceWrap : IDisposable
    {
        string methodName;
        string className;

        private bool _disposed = false;

        public FnTraceWrap()
        {
            StackFrame frame;
            MethodBase method;

            frame = new StackFrame(1);
            method = frame.GetMethod();
            this.methodName = method.Name;
            this.className = method.DeclaringType.Name;

            MyEventSourceClass.Log.TraceEnter(this.className, this.methodName);
        }

        public void TraceMessage(string format, params object[] args)
        {
            string message = String.Format(format, args);
            MyEventSourceClass.Log.TraceMessage(message);
        }

        public void Dispose()
        {
            if (!this._disposed)
            {
                this._disposed = true;
                MyEventSourceClass.Log.TraceExit(this.className, this.methodName);
            }
        }
    }

    [EventSource(Name = "MyEventSource")]
    sealed class MyEventSourceClass : EventSource
    {
        // Global singleton instance
        public static MyEventSourceClass Log = new MyEventSourceClass();

        private MyEventSourceClass()
        {
        }

        [Event(1, Opcode = EventOpcode.Info, Level = EventLevel.Informational)]
        public void TraceMessage(string message)
        {
            WriteEvent(1, message);
        }

        [Event(2, Message = "{0}({1}) - {2}: {3}", Opcode = EventOpcode.Info, Level = EventLevel.Informational)]
        public void TraceCodeLine([CallerFilePath] string filePath = "",
                                  [CallerLineNumber] int line = 0,
                                  [CallerMemberName] string memberName = "", string message = "")
        {
            WriteEvent(2, filePath, line, memberName, message);
        }

        // Function-level entry and exit tracing
        [Event(3, Message = "Entering {0}.{1}", Opcode = EventOpcode.Start, Level = EventLevel.Informational)]
        public void TraceEnter(string className, string methodName)
        {
            WriteEvent(3, className, methodName);
        }

        [Event(4, Message = "Exiting {0}.{1}", Opcode = EventOpcode.Stop, Level = EventLevel.Informational)]
        public void TraceExit(string className, string methodName)
        {
            WriteEvent(4, className, methodName);
        }
    }
}

使用它的代码看起来会像这样:
public void DoWork(string foo)
{
    using (FnTraceWrap fnTrace = new FnTraceWrap())
    {
        fnTrace.TraceMessage("Doing work on {0}.", foo);
        /*
        code ...
        */
    }
}

3

性能分析工具在开发过程中非常有用,但是如果你想在生产环境中进行自定义跟踪,那么像Denis G.提到的PostSharp这样的工具就非常完美:你不需要改变所有的代码,而且可以轻松地开启/关闭。

而且,它只需几分钟即可轻易设置,PostSharp 的创建者 Gaël Fraiteur 甚至有视频向您展示如何将跟踪添加到现有应用程序中。
文档部分中,您会找到示例和教程。


2
使用 Red Gate 的 ANTS Profiler 是你最好的选择。如果失败了,可以研究一下 Castle Windsor 中的 interceptors。不过这假设你是通过 IoC 加载你的类型。
反射也是一种方法,你可以使用 System.Reflection.Emit 方法将代码“写入”内存。该代码可以替换你的方法代码,并执行它,但需要适当记录日志。祝你好运...更容易的方法是使用面向方面编程框架,如 Aspect#

ANTS很棒(VS内置分析器也是),但缺点是你可能会改变应用程序的内存/时间“足迹”,并且可能无法体验锁定... - Mitch Wheat
追踪也会改变时间。 - Mark Brackett
1
是的,这就是海森堡不确定性原理。通过观察实验,你很可能会改变实验结果。这就是线程同步调试为你带来的... - Neil Barnwell

1

它可能正在等待锁问题的发生,对多个线程进行内存转储和分析调用堆栈。 您可以使用DebugDiag或附带Windows调试工具的adplus脚本(在此情况下为 hang 模式)。

Tess Ferrandez还有一个优秀的实验系列,可以使用.NET内存转储学习调试各种问题。 我强烈推荐它。


0

你怎么知道这种情况正在发生?如果这是一个多线程应用程序,我建议在运行时测试条件并调用System.Diagnostics.Debugger.Break()来检测它。然后,只需打开线程窗口并逐个步进每个相关线程的调用堆栈。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接