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
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:
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
| Property | Type | Description |
|---|---|---|
Exception | HermesExceptionInfo | Parsed exception with type, message, and stack frames |
Platform | HermesPlatformInfo | OS, architecture, product, and .NET version |
CrashedAt | DateTimeOffset | When the crash occurred |
Source | CrashSource | What triggered the crash |
AnonymousSessionId | string? | Optional session identifier |
AdditionalContext | IDictionary<string, string>? | Arbitrary metadata |
HermesExceptionInfo
| Property | Type | Description |
|---|---|---|
ExceptionType | string | Fully qualified type name (e.g. System.InvalidOperationException) |
Message | string | Exception message |
StackTrace | IReadOnlyList<HermesStackFrame> | Parsed stack frames |
InnerException | HermesExceptionInfo? | Recursive inner exception |
HermesStackFrame
| Property | Type | Description |
|---|---|---|
FileName | string? | Source file path |
MethodName | string? | Method name |
TypeName | string? | Declaring type |
LineNumber | int? | Line number |
ColumnNumber | int? | Column number |
HermesPlatformInfo
| Property | Type | Description |
|---|---|---|
ProductName | string | From HermesCrashInterceptor.ProductName |
ProductVersion | string | From HermesCrashInterceptor.ProductVersion |
OperatingSystem | string | e.g. macOS, Windows, Linux |
OsVersion | string | OS version string |
Architecture | string | e.g. arm64, x64 |
DotNetVersion | string? | .NET runtime version |
DeviceModel | string? | Platform-specific device model |
Crash Sources
| Source | Trigger | Notes |
|---|---|---|
UnhandledException | AppDomain.UnhandledException | Unhandled exception on any thread |
UnobservedTask | TaskScheduler.UnobservedTaskException | Unawaited task that throws |
WebViewCrash | Platform WebView process failure | WebView2, WKWebView, or WebKit2GTK crash |
NativeSignal | Native 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.
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:
- Use synchronous HTTP — the process is dying, so async calls may never complete
- Never throw — exceptions in the crash handler would mask the original crash
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():
| Property | Type | Description |
|---|---|---|
ProductName | string? | Included in HermesPlatformInfo. Defaults to "Unknown" |
ProductVersion | string? | Included in HermesPlatformInfo. Defaults to "0.0.0" |
AnonymousSessionId | string? | Included in crash context for session correlation |
Important Notes
- Enable/Disable are idempotent — calling
Enable()multiple times is safe. CallDisable()to remove all handlers. - AOT builds — when compiled with AOT and
StackTraceSupport=false,HermesExceptionInfo.StackTracewill be an empty list. EnableStackTraceSupportin your project if you need stack frames. - Callback safety — exceptions thrown inside
OnCrashare 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.
