Introduction

A production application can fail in a way that leaves very little useful information behind.

An exception may escape the application's normal error-handling pipeline, a native failure may terminate the process, or the operating system may stop the process before your normal logging code can finish writing a useful record.

For these cases, ordinary application logging is not always enough.

.NET provides several layers for diagnosing process failures, including unhandled-exception events, crash dumps, EventPipe diagnostics, and, in .NET 11, runtime-generated JSON crash reports. The crash-report capability is configured through runtime environment variables and provides thread and stack-frame information when the process crashes.

The important production principle is:

Do not depend on one error-handling mechanism to diagnose every type of application failure.

Instead, combine application-level logging with runtime crash diagnostics.

Why Normal Exception Handling Is Not Enough

Most application errors can be handled normally:

try
{
    await ProcessOrderAsync();
}
catch (Exception ex)
{
    logger.LogError(ex, "Order processing failed");
}

This works when the exception reaches the catch block.

But some failures happen outside that path.

For example:

Application Code
      |
      v
Exception
      |
      +---- try/catch
      |       |
      |       v
      |     Logged
      |
      +---- No handler
              |
              v
        Process termination

A process-level failure needs a different diagnostic strategy.

Microsoft's exception guidance also distinguishes handling expected application exceptions from failures that leave the process in an undefined state.

What Is a Crash Report?

A crash report is diagnostic information generated when the runtime encounters a process crash.

In .NET 11, the runtime can generate a JSON-formatted crash report when DOTNET_EnableCrashReport is enabled. The report contains information about threads and stack frames associated with the crashing application.

Conceptually:

Application
    |
    X
Process Crash
    |
    v
.NET Runtime
    |
    +---- Crash Report
    |
    +---- Crash Dump

These outputs serve different purposes.

A crash report is relatively focused diagnostic information.

A dump is a snapshot of the process state and can contain much more information, including application memory.

Enable Crash Reports in .NET 11

The runtime setting is:

DOTNET_EnableCrashReport=1

When enabled, .NET generates a JSON crash report when the runtime supports the crash-report path.

The generated filename is based on the dump path or dump name with:

.crashreport.json

appended.

For example:

export DOTNET_EnableCrashReport=1

Then start the application normally.

The setting can be configured through the deployment environment rather than hard-coded into application source code.

Combine Crash Reports With Crash Dumps

A crash report is useful, but a dump provides a much deeper diagnostic snapshot.

.NET supports automatic crash dump collection through environment variables such as:

DOTNET_DbgEnableMiniDump=1

You can also specify the dump type:

DOTNET_DbgMiniDumpType=2

The documented dump types include:

Value

Type

Purpose

1

Mini

Smaller diagnostic dump

2

Heap

More comprehensive process information

3

Triage

Smaller dump with reduced personal information

4

Full

Full process memory

The default dump type is Heap.

For many production incidents, a heap dump provides substantially more information than a small crash report.

However, larger dumps also have larger storage and security implications.

Configure the Dump Location

The dump path can be controlled with:

DOTNET_DbgMiniDumpName=/var/log/myapp/crash-%p-%t.dmp

.NET supports template values in dump filenames.

For example:

%p = Process ID
%e = Executable name
%h = Host name
%t = Timestamp

This is useful when several instances write diagnostic files to the same directory.

A containerized deployment might use a writable diagnostic directory:

/diagnostics/
    crash-1234-1720000000.dmp
    crash-1234-1720000000.dmp.crashreport.json

The directory must be writable by the process account.

A Practical Production Configuration

A Linux deployment could use:

DOTNET_EnableCrashReport=1
DOTNET_DbgEnableMiniDump=1
DOTNET_DbgMiniDumpType=2
DOTNET_DbgMiniDumpName=/diagnostics/crash-%p-%t.dmp

This provides two diagnostic artifacts:

Crash
  |
  +---- .crashreport.json
  |
  +---- .dmp

The JSON report gives quick information.

The dump can be used for deeper investigation.

Why You Should Not Rely Only on AppDomain.UnhandledException

.NET provides:

AppDomain.CurrentDomain.UnhandledException

It can be useful for logging an exception that has escaped normal exception handling.

For example:

AppDomain.CurrentDomain.UnhandledException += (_, args) =>
{
    if (args.ExceptionObject is Exception ex)
    {
        Console.Error.WriteLine(ex);
    }
};

However, this should not be treated as a universal crash-capture mechanism.

The event is a notification for uncaught exceptions. Microsoft also cautions that the handler runs during application termination and should avoid operations that can block or introduce additional failures.

A logging call that depends on a failing database, network connection, or another unhealthy subsystem may therefore be unreliable during a crash.

Keep Crash Handlers Simple

A crash-path handler should do as little work as possible.

Avoid:

AppDomain.CurrentDomain.UnhandledException += async (_, args) =>
{
    await SendToDatabaseAsync(args);
    await NotifyExternalServiceAsync(args);
};

This introduces additional dependencies into the failure path.

Prefer a simple local diagnostic operation:

AppDomain.CurrentDomain.UnhandledException += (_, args) =>
{
    Console.Error.WriteLine(
        $"Unhandled exception: {args.ExceptionObject}");
};

The runtime-level crash report or dump should provide the deeper diagnostic information.

ASP.NET Core Errors Are a Different Layer

ASP.NET Core applications have their own request-level error-handling pipeline.

For example:

HTTP Request
     |
     v
Middleware
     |
     +---- Exception
     |
     v
Error Handler
     |
     v
HTTP 500

A request exception does not necessarily mean that the entire process has crashed.

The ASP.NET Core developer exception page and exception-handling middleware operate at the HTTP pipeline level.

Therefore, an ASP.NET Core production application should typically have:

Request Errors
     |
     v
Application Logging


Process Crashes
     |
     +---- Crash Report
     |
     +---- Crash Dump

These mechanisms solve different problems.

What About Background Services?

Background workers are another important source of failures.

Consider:

public class Worker : BackgroundService
{
    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await ProcessAsync(stoppingToken);
        }
    }
}

A failure inside the worker needs to be handled according to the intended lifecycle of that service.

For recoverable operation failures:

try
{
    await ProcessAsync(stoppingToken);
}
catch (Exception ex)
{
    logger.LogError(ex, "Worker operation failed");
}

For a failure that makes the process unsafe to continue, allowing the process to terminate may be preferable to leaving the application in an unknown state.

The crash diagnostics then provide evidence for investigating the failure.

Native Crashes Need Different Thinking

Not every process termination is a managed exception.

A native dependency can cause a process-level failure without passing through a normal C# catch block.

For example:

C# Application
      |
      v
Native Library
      |
      X
Native Crash
      |
      v
Process Terminates

This is where crash dumps become especially useful.

A dump contains a snapshot of process state that can be analyzed after the process has terminated.

Use dotnet-dump for Manual Investigation

Automatic crash dumps are useful for unexpected failures, but sometimes the application is still alive and unhealthy.

For example:

High CPU
Deadlock
Memory pressure
Thread starvation
Unresponsive application

In these situations, use dotnet-dump to collect a dump from the running process.

A basic command is:

dotnet-dump collect -p <process-id>

Microsoft documents dotnet-dump as a cross-platform diagnostic tool for collecting and analyzing .NET process dumps.

This is different from automatic crash collection.

Automatic crash
       |
       v
Runtime creates dump


Suspected unhealthy process
       |
       v
Engineer collects dump

Both approaches are useful.

Be Careful With Containers

Crash dumps can be large.

Microsoft notes that collecting full or heap dumps can cause substantial virtual memory to be paged into the process and, in a memory-constrained container, this additional memory pressure can cause the container to be terminated.

Therefore, test dump collection under realistic resource limits.

For example:

Container
  |
  +---- Application Memory
  |
  +---- Runtime
  |
  +---- Dump Collection
          |
          v
      Extra Memory

Do not discover this behavior for the first time during a production incident.

Protect Crash Dumps

A dump can contain sensitive information because it may include process memory.

That can include:

Microsoft explicitly warns that dumps may contain sensitive information and should be handled according to the application's security requirements.

Treat crash dumps as sensitive production artifacts.

Use:

Do not upload production dumps to an unrestricted shared location.

Add Crash Correlation

A useful production design combines normal logs with crash artifacts.

For example:

Request
   |
   v
Trace ID
   |
   +---- Application Logs
   |
   +---- Metrics
   |
   +---- Crash Report
   |
   +---- Crash Dump

If the process crashes, the surrounding logs can provide the operational context while the crash artifacts provide process-level information.

Where possible, include application version and deployment information in your logs:

Version: 2026.09.28.4
Environment: Production
Instance: app-07

This helps determine whether the crash corresponds to a particular deployment.

Test the Crash Pipeline

Do not wait for a real crash to discover that the dump directory is not writable.

Create a controlled test environment and deliberately trigger a process failure.

For example:

Environment.FailFast(
    "Crash diagnostics test");

This should be done only in a controlled test environment.

Then verify:

[ ] Process terminates
[ ] Crash report appears
[ ] Dump appears
[ ] Filename is correct
[ ] File permissions are correct
[ ] Diagnostic artifact is readable
[ ] Logs identify the deployment
[ ] Artifact retention works

This validates the entire pipeline.

Common Mistakes

Depending Only on try/catch

A process-level failure may never reach the application's exception handler.

Depending Only on UnhandledException

The event provides notification, but it is not equivalent to a complete process dump.

Performing Network Operations During a Crash

The failing component may be the network, database, or another dependency.

Creating Full Dumps Automatically Without Capacity Planning

Large dumps can consume significant storage and resources.

Storing Dumps Indefinitely

Dumps can contain sensitive memory contents.

Exposing Diagnostic Endpoints Publicly

Diagnostic interfaces should be protected.

Never Testing Crash Collection

A configuration that has never been tested is not a reliable incident-response mechanism.

Best Practices

  1. Keep normal exception handling for recoverable application errors.

  2. Use runtime crash reports for process-level diagnostic information.

  3. Enable automatic crash dumps for services where post-crash investigation is important.

  4. Select the smallest dump type that provides the required diagnostic information.

  5. Protect crash artifacts as sensitive data.

  6. Use dotnet-dump for manual investigation of unhealthy but still-running processes.

  7. Test crash collection in the same deployment environment used in production.

  8. Verify container memory and storage limits before enabling automatic dumps.

  9. Keep crash-path logging simple and local.

  10. Correlate crash artifacts with application version and deployment information.

  11. Establish retention and deletion policies for dumps.

  12. Document who can access production diagnostic artifacts.

Crash Report vs Crash Dump

Capability

Crash Report

Crash Dump

JSON format

Yes

No

Thread information

Yes

Yes

Stack information

Yes

Yes

Process memory

No

Depending on dump type

Easy to inspect

Yes

Requires diagnostic tools

Storage size

Relatively small

Can be large

Deep investigation

Limited

Stronger

Sensitive data exposure

Lower

Potentially high

The appropriate choice depends on the incident.

A crash report may be enough to identify the failing thread or stack.

A dump is more useful when you need to inspect application state in depth.

Production Configuration Checklist

Crash Reporting
[ ] DOTNET_EnableCrashReport configured where supported
[ ] Crash report location is writable
[ ] Crash report retention is defined

Crash Dumps
[ ] DOTNET_DbgEnableMiniDump configured where required
[ ] Dump type selected intentionally
[ ] Dump location has sufficient storage
[ ] Container memory limits tested
[ ] Dump permissions are restricted

Application Logging
[ ] Unhandled exceptions are logged
[ ] Background worker failures are logged
[ ] Deployment/version information is available
[ ] Logs are correlated with instance information

Security
[ ] Crash artifacts are treated as sensitive
[ ] Access is restricted
[ ] Storage is encrypted
[ ] Retention policy exists
[ ] Automatic cleanup is configured

Testing
[ ] Controlled crash has been tested
[ ] Crash report was generated
[ ] Dump was generated
[ ] Dump can be analyzed
[ ] Recovery behavior was verified

Summary

.NET 11 provides another useful layer for diagnosing process failures through runtime-generated crash reports. Setting DOTNET_EnableCrashReport=1 enables a JSON-formatted crash report containing thread and stack-frame information for supported crash scenarios.

For deeper investigation, automatic crash dumps can capture substantially more process state. .NET supports configuring these through environment variables such as DOTNET_DbgEnableMiniDump, DOTNET_DbgMiniDumpType, and DOTNET_DbgMiniDumpName.

The practical architecture is:

Expected Application Error
        |
        v
try/catch + Application Logging


Unexpected Process Failure
        |
        +---- Crash Report
        |
        +---- Crash Dump
        |
        v
Post-Crash Investigation

The important part is not simply enabling a runtime variable. A production-ready crash-diagnostic system also needs secure storage, controlled retention, sufficient disk and memory capacity, and a tested investigation workflow.

When the application exits unexpectedly, the goal is to preserve enough evidence to answer three questions:

What failed?
Where did it fail?
What was the process doing when it failed?

With application logging, crash reports, and appropriately configured dumps working together, those questions become much easier to answer.

Meta Description

SEO/GEO Keywords

Category

.NET