opentelemetry-dotnet/docs/trace/reporting-exceptions/README.md

5.4 KiB

Reporting Exceptions

The following doc describes how to report Exceptions to OpenTelemetry tracing when user is manually creating Activities. If the user is using one of the instrumentation libraries, it may provide these functionalities automatically. Please refer to the respective documentation for guidance.

User-handled Exception

The term User-handled Exception is used to describe exceptions that are handled by the application, as shown in the below sample code.

try
{
    Func();
}
catch (SomeException ex)
{
    DoSomething();
}
catch (Exception ex)
{
    DoSomethingElse();
    throw;
}

OpenTelemetry .NET provides several options to report Exceptions in Activity. It varies from the most basic option of setting Status, to fully recording the Exception itself to activity.

Option 1 - Set Activity Status manually

The most basic option is to set Activity status to Error to indicate that an Exception has occurred.

While using Activity API, the common pattern would be:

using (var activity = MyActivitySource.StartActivity("Foo"))
{
    try
    {
        Func();
    }
    catch (SomeException ex)
    {
        activity?.SetStatus(ActivityStatusCode.Error);
        DoSomething();
    }
    catch (Exception ex)
    {
        activity?.SetStatus(ActivityStatusCode.Error);
        throw;
    }
}

Option 2 - Set Activity Status using SetErrorStatusOnException feature

The approach described in Option 1 could become hard to manage if there are deeply nested Activity objects, or there are activities created in a 3rd party library.

The following configuration will automatically detect exception and set the activity status to Error:

Sdk.CreateTracerProviderBuilder()
    .SetErrorStatusOnException()
    // ...

A complete example can be found here.

Note: this feature is platform dependent as it relies on System.Runtime.InteropServices.Marshal.GetExceptionPointers.

Option 3 - Set Activity Status with Error description

While convenient, the SetErrorStatusOnException feature only sets the activity status to Error and nothing more. It is sometimes desirable to store the exception message as the Status description. The following code shows how to do that:

using (var activity = MyActivitySource.StartActivity("Foo"))
{
    try
    {
        Func();
    }
    catch (SomeException ex)
    {
        activity?.SetStatus(ActivityStatusCode.Error, ex.message);
    }
}

Option 4 - Use Activity.RecordException

Both options 1 and 2 above showed the most basic reporting of Exception, by leveraging Activity status. Neither of the approach actually records the Exception itself to do more richer debugging. Activity.RecordException() allows the exception to be stored in the Activity as ActivityEvent as per OpenTelemetry convention, as shown below:

using (var activity = MyActivitySource.StartActivity("Foo"))
{
    try
    {
        Func();
    }
    catch (SomeException ex)
    {
        activity?.SetStatus(ActivityStatusCode.Error, ex.message);
        activity?.RecordException(ex);
    }
}

Unhandled Exception

The term Unhandled Exception is used to describe exceptions that are not handled by the application. When an unhandled exception happened, the behavior will depend on the presence of a debugger:

  • If there is no debugger, the exception will normally crash the process or terminate the thread.
  • If a debugger is attached, the debugger will be notified that an unhandled exception happened.
  • In case a postmortem debugger is configured, the postmortem debugger will be activated and normally it will collect a crash dump.

It might be useful to automatically capture the unhandled exceptions, travel through the unfinished activities and export them for troubleshooting. Here goes one possible way of doing this:

WARNING: Use AppDomain.UnhandledException with caution. A throw in the handler puts the process into an unrecoverable state.

using System;
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;

public class Program
{
    private static readonly ActivitySource MyActivitySource = new ActivitySource("MyCompany.MyProduct.MyLibrary");

    public static void Main()
    {
        AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;

        using var tracerProvider = Sdk.CreateTracerProviderBuilder()
            .AddSource("MyCompany.MyProduct.MyLibrary")
            .SetSampler(new AlwaysOnSampler())
            .SetErrorStatusOnException()
            .AddConsoleExporter()
            .Build();

        using (MyActivitySource.StartActivity("Foo"))
        {
            using (MyActivitySource.StartActivity("Bar"))
            {
                throw new Exception("Oops!");
            }
        }
    }

    private static void UnhandledExceptionHandler(object source, UnhandledExceptionEventArgs args)
    {
        var ex = (Exception)args.ExceptionObject;

        var activity = Activity.Current;

        while (activity != null)
        {
            activity.RecordException(ex);
            activity.Dispose();
            activity = activity.Parent;
        }
    }
}