Skip to content

Crash Reporting

Hermes provides built-in crash interception that captures unhandled exceptions, unobserved task failures, and WebView process crashes. Rather than coupling to a specific reporting backend, Hermes delivers structured crash context through a simple callback — you decide where it goes.

Quick Start

csharp
using Hermes.Diagnostics;

HermesCrashInterceptor.ProductName = "MyApp";
HermesCrashInterceptor.ProductVersion = "1.0.0";

HermesCrashInterceptor.OnCrash = context =>
{
    Console.WriteLine($"Crash: {context.Exception.ExceptionType} — {context.Exception.Message}");
    Console.WriteLine($"Source: {context.Source}");
    Console.WriteLine($"OS: {context.Platform.OperatingSystem} {context.Platform.OsVersion}");
};

HermesApplication.EnableCrashInterception();

Or if you're using the Blazor app builder:

csharp
HermesCrashInterceptor.ProductName = "MyApp";
HermesCrashInterceptor.ProductVersion = "1.0.0";
HermesCrashInterceptor.OnCrash = context => { /* your handler */ };

HermesBlazorApp.CreateBuilder(args)
    .Build()
    .EnableCrashInterception()
    .Run();

Packages

The crash reporting data types (HermesCrashContext, HermesExceptionInfo, HermesPlatformInfo, HermesStackFrame, CrashSource) are published in the Hermes.Contracts package. This lightweight package has no dependency on the full Hermes runtime, so backend services or shared libraries can reference the types directly.

Apps that depend on the Hermes package get these types transitively.

Crash Context

When a crash is intercepted, the OnCrash callback receives a HermesCrashContext containing everything needed to file a report.

HermesCrashContext

PropertyTypeDescription
ExceptionHermesExceptionInfoParsed exception with type, message, and stack frames
PlatformHermesPlatformInfoOS, architecture, product, and .NET version
CrashedAtDateTimeOffsetWhen the crash occurred
SourceCrashSourceWhat triggered the crash
AnonymousSessionIdstring?Optional session identifier
AdditionalContextIDictionary<string, string>?Arbitrary metadata

HermesExceptionInfo

PropertyTypeDescription
ExceptionTypestringFully qualified type name (e.g. System.InvalidOperationException)
MessagestringException message
StackTraceIReadOnlyList<HermesStackFrame>Parsed stack frames
InnerExceptionHermesExceptionInfo?Recursive inner exception

HermesStackFrame

PropertyTypeDescription
FileNamestring?Source file path
MethodNamestring?Method name
TypeNamestring?Declaring type
LineNumberint?Line number
ColumnNumberint?Column number

HermesPlatformInfo

PropertyTypeDescription
ProductNamestringFrom HermesCrashInterceptor.ProductName
ProductVersionstringFrom HermesCrashInterceptor.ProductVersion
OperatingSystemstringe.g. macOS, Windows, Linux
OsVersionstringOS version string
Architecturestringe.g. arm64, x64
DotNetVersionstring?.NET runtime version
DeviceModelstring?Platform-specific device model

Crash Sources

SourceTriggerNotes
UnhandledExceptionAppDomain.UnhandledExceptionUnhandled exception on any thread
UnobservedTaskTaskScheduler.UnobservedTaskExceptionUnawaited task that throws
WebViewCrashPlatform WebView process failureWebView2, WKWebView, or WebKit2GTK crash
NativeSignalNative signal (SIGSEGV, SIGABRT)Reserved for future use

Writing Crash Reports to Disk

The simplest approach is writing crash reports to a local file. This works well for desktop apps where you want to collect logs for support tickets or post-mortem debugging.

csharp
HermesCrashInterceptor.OnCrash = context =>
{
    var timestamp = context.CrashedAt.ToString("yyyyMMdd_HHmmss");
    var path = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
        "MyApp", "crashes", $"crash_{timestamp}.txt");

    Directory.CreateDirectory(Path.GetDirectoryName(path)!);

    var report = new StringBuilder();
    report.AppendLine($"Crash: {context.Exception.ExceptionType}");
    report.AppendLine($"Message: {context.Exception.Message}");
    report.AppendLine($"Source: {context.Source}");
    report.AppendLine($"Time: {context.CrashedAt:O}");
    report.AppendLine($"OS: {context.Platform.OperatingSystem} {context.Platform.OsVersion} ({context.Platform.Architecture})");
    report.AppendLine($".NET: {context.Platform.DotNetVersion}");
    report.AppendLine();
    report.AppendLine("Stack Trace:");
    foreach (var frame in context.Exception.StackTrace)
    {
        report.AppendLine($"  at {frame.TypeName}.{frame.MethodName} in {frame.FileName}:{frame.LineNumber}");
    }

    File.WriteAllText(path, report.ToString());
};

Reporting to an HTTP Endpoint

Here's a production-style pattern for reporting crashes over HTTP. Two key constraints apply when reporting from a crash handler:

  1. Use synchronous HTTP — the process is dying, so async calls may never complete
  2. Never throw — exceptions in the crash handler would mask the original crash
csharp
HermesCrashInterceptor.ProductName = "Horizon";
HermesCrashInterceptor.ProductVersion = typeof(Program).Assembly
    .GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "unknown";

HermesCrashInterceptor.OnCrash = context =>
{
    try
    {
        var json = JsonSerializer.Serialize(context, new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        });

        using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
        using var content = new StringContent(json, Encoding.UTF8, "application/json");
        using var response = client.Send(
            new HttpRequestMessage(HttpMethod.Post, "https://api.example.com/crash-reports")
            {
                Content = content
            });
    }
    catch
    {
        // Swallow — never throw from a crash handler
    }
};

HermesApplication.EnableCrashInterception();

Since the crash context types live in Hermes.Contracts, your backend can deserialize incoming reports directly into the same types without manual mapping.

Configuration Properties

Set these before calling Enable():

PropertyTypeDescription
ProductNamestring?Included in HermesPlatformInfo. Defaults to "Unknown"
ProductVersionstring?Included in HermesPlatformInfo. Defaults to "0.0.0"
AnonymousSessionIdstring?Included in crash context for session correlation

Important Notes

  • Enable/Disable are idempotent — calling Enable() multiple times is safe. Call Disable() to remove all handlers.
  • AOT builds — when compiled with AOT and StackTraceSupport=false, HermesExceptionInfo.StackTrace will be an empty list. Enable StackTraceSupport in your project if you need stack frames.
  • Callback safety — exceptions thrown inside OnCrash are caught and logged, never propagated. Your callback won't crash the crash handler.
  • WebView crashes — detected automatically on all platforms (WebView2 on Windows, WKWebView on macOS, WebKit2GTK on Linux) when crash interception is enabled.