ETW最佳记录异常的方法是什么?

16

有没有一种标准的方法来使用ETW记录异常?

据我所见,唯一的方法是记录消息和可能的内部异常消息,因为没有针对异常类型的强类型参数。

3个回答

26

如果启用了CLR运行时提供程序,所有CLR异常(包括首次出现的异常和可能导致应用程序崩溃的异常)都会被记录到ETW中。

这是一个完全“结构化”的事件,包括调用堆栈(如果需要)。事实上,您可以使用TraceEvent NuGet包(Install-Package Microsoft.Diagnostics.Tracing.TraceEvent)编写监视应用程序。

以下是我经常使用的监视代码。将其放入控制台应用程序中,调用Run方法,然后从任何进程中抛出一些托管异常,它将打印信息及其调用堆栈。

注意:您需要引用NuGet包并引用其程序集,然后此代码才能编译。

class TraceLogMonitor
{
    static TextWriter Out = AllSamples.Out;

    public static void Run()
    {
        var monitoringTimeSec = 10;
       TraceEventSession session = null;

        Console.CancelKeyPress += (object sender, ConsoleCancelEventArgs cancelArgs) =>
        {
            if (session != null)
                session.Dispose();
            cancelArgs.Cancel = true;
        };

        var exceptionGeneationTask = Task.Factory.StartNew(delegate
        {
            Thread.Sleep(3000);
            ThrowException();
        });

        Timer timer = null;

        using (session = new TraceEventSession("TraceLogSession"))
        {
            Out.WriteLine("Enabling Image load, Process and Thread events.  These are needed to look up native method names.");
            session.EnableKernelProvider(

                KernelTraceEventParser.Keywords.ImageLoad |
                KernelTraceEventParser.Keywords.Process,  
                KernelTraceEventParser.Keywords.None
                );

            Out.WriteLine("Enabling CLR Exception and Load events (and stack for those events)");

            session.EnableProvider(
                ClrTraceEventParser.ProviderGuid,
                TraceEventLevel.Informational,
                (ulong)(ClrTraceEventParser.Keywords.Jit |              
                ClrTraceEventParser.Keywords.JittedMethodILToNativeMap |
                ClrTraceEventParser.Keywords.Loader |                   
                ClrTraceEventParser.Keywords.Exception |               
                ClrTraceEventParser.Keywords.Stack));                   

            Out.WriteLine("Enabling CLR Events to 'catch up' on JIT compiled code in running processes.");
            session.EnableProvider(ClrRundownTraceEventParser.ProviderGuid, TraceEventLevel.Informational,
                (ulong)(ClrTraceEventParser.Keywords.Jit |          
                ClrTraceEventParser.Keywords.JittedMethodILToNativeMap | 
                ClrTraceEventParser.Keywords.Loader |               
                ClrTraceEventParser.Keywords.StartEnumeration));    

            TextWriter SymbolLookupMessages = new StringWriter();

            var symbolPath = new SymbolPath(SymbolPath.SymbolPathFromEnvironment).Add(SymbolPath.MicrosoftSymbolServerPath);
            SymbolReader symbolReader = new SymbolReader(SymbolLookupMessages, symbolPath.ToString());

            Out.WriteLine("Open a real time TraceLog session (which understands how to decode stacks).");
            using (TraceLogEventSource traceLogSource = TraceLog.CreateFromTraceEventSession(session)) 
            {
                Action<TraceEvent> PrintEvent = ((TraceEvent data) => Print(data, symbolReader));

                traceLogSource.Clr.ExceptionStart += PrintEvent;
                traceLogSource.Clr.LoaderModuleLoad += PrintEvent;

                traceLogSource.Kernel.PerfInfoSample += ((SampledProfileTraceData data) => Print(data, symbolReader));

                Out.WriteLine("Waiting {0} sec for Events.  Run managed code to see data. ", monitoringTimeSec);
                Out.WriteLine("Keep in mind there is a several second buffering delay");

                timer = new Timer(delegate(object state)
                {
                    Out.WriteLine("Stopped Monitoring after {0} sec", monitoringTimeSec);
                    if (session != null)
                        session.Dispose();
                    session = null;
                }, null, monitoringTimeSec * 1000, Timeout.Infinite);

                traceLogSource.Process();
            }
        }
        Out.WriteLine("Finished");
        if (timer != null)
            timer.Dispose();
    }

    static void Print(TraceEvent data, SymbolReader symbolReader)
    {
        if (data.Opcode == TraceEventOpcode.DataCollectionStart)
            return;

        if (data is ExceptionTraceData && ((ExceptionTraceData) data).ExceptionType.Length == 0)
            return;

        Out.WriteLine("EVENT: {0}", data.ToString());
        var callStack = data.CallStack();
        if (callStack != null)
        {
            ResolveNativeCode(callStack, symbolReader);
            Out.WriteLine("CALLSTACK: {0}", callStack.ToString());
        }
    }

    static private void ResolveNativeCode(TraceCallStack callStack, SymbolReader symbolReader)
    {
        while (callStack != null)
        {
            var codeAddress = callStack.CodeAddress;
            if (codeAddress.Method == null)
            {
                var moduleFile = codeAddress.ModuleFile;
                if (moduleFile == null)
                    Trace.WriteLine(string.Format("Could not find module for Address 0x{0:x}", codeAddress.Address));
                else
                    codeAddress.CodeAddresses.LookupSymbolsForModule(symbolReader, moduleFile);
            }
            callStack = callStack.Caller;
        }
    }

    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    private static void ThrowException()
    {
        ThrowException1();
    }

    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    private static void ThrowException1()
    {
        Out.WriteLine("Causing an exception to happen so a CLR Exception Start event will be generated.");
        try
        {
            throw new Exception("This is a test exception thrown to generate a CLR event");
        }
        catch (Exception) { }
    }
}

当调用CallStack()时,我看到以下异常:尝试在非TraceLog TraceEventSource上使用TraceLog支持。你以前见过这种情况吗? - Aaron Hudon

1
在 catch 块中使用额外的事件,并将异常消息作为参数传递到事件中。
[Event(1, Message = "Application Falure: {0}", Level = EventLevel.Error, Keywords = Keywords.Diagnostic)]
public void Failure(string message) 
{ 
    if (this.IsEnabled())
    {
        this.WriteEvent(1, message); 
    }
}

通过调整级别和关键字,控制是否始终记录日志。


由于您无法对EventSource使用强类型异常,所以是否最好像这样调用它:EventSource.Log.Failure(MyException.ToString())? - jaffa

1

ETW并非针对.NET特定的,因此不会有任何强类型的.NET特定API来记录.NET异常。相反,您需要创建自己的强类型API。这就是语义日志记录和语义日志记录应用程序块背后的思想。


那么归根结底,为了使用ETW,只能使用原始数据类型、字符串和数字(以字符串形式)吗?将异常纯粹序列化为字符串就足够了吗?还是有一些非.NET应用程序跟踪异常的模式? - BozoJoe
2
ETW是二进制有效载荷,并支持您定义的结构体。http://msdn.microsoft.com/en-us/library/windows/desktop/aa382774(v=vs.85).aspx只是当您选择这种方式时,您必须编写自定义解码器。在.NET情况下,已经由.NET团队提供了一个明确定义的解码器。然而,像许多本地代码中的事情一样,有多种方法可以做到这一点。C++异常?SEH异常?等等。异常最有趣的部分是调用堆栈(及其EIP,以获取行号),它由ETW标准化和其堆栈行走器。除此之外的任何信息都取决于您。 - mjsabby

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