Aspire

Sends .NET Aspire crash reports to either a locally running Raygun portal or the Raygun cloud service.


You'll want to add the Raygun4Aspire NuGet package to both the Aspire orchestration project (AppHost), and any .NET projects within your Aspire app where you want to collect crash reports from. Either use the NuGet package management GUI in the IDE you use, OR use the below dotnet command.

dotnet add package Raygun4Aspire

In Program.cs of the AppHost project, add a Raygun4Aspire using statement, then call AddRaygun on the builder (after the builder is initialized and before it is used to build and run).

using Raygun4Aspire;

// The distributed application builder is created here

builder.AddRaygun();

// The builder is used to build and run the app somewhere down here

In each of the .NET projects in your Aspire app where you intend to log crash reports from, add a RaygunSettings section to the appsettings file.

"RaygunSettings": {
  "ApiKey": "paste_your_api_key_here"
}

Then, in Program.cs, add a using statement, call AddRaygun on the WebApplicationBuilder, followed by calling UseRaygun on the created application.

using Raygun4Aspire;

// The WebApplicationBuilder is created somewhere here

builder.AddRaygun();

// The builder is used to create the application a little later on

app.UseRaygun();

// Then at the end of the file, the app is commanded to run

Manually sending exceptions

It's best practice to log all exceptions that occur within try/catch blocks (that aren't subsequently thrown to be caught by unhandled exception hooks). To do this with Raygun, inject the RaygunClient into any place where you wish to manually send exceptions from. Then use the SendInBackground method of that Raygun client.

Below is an example of doing this in a razor page:

@inject Raygun4Aspire.RaygunClient raygunClient

<!-- some html elements would typically be here -->

@code {
  private void Function()
  {
    try
    {
      // whatever code this function is doing
    }
    catch (Exception ex)
    {
      raygunClient.SendInBackground(ex);
    }
  }
}

The SendInBackground method also contains optional parameters to send tags, custom data, user details and the HttpContext.

Tags is a list of strings that could be used to categorize crash reports.

Custom data is a dictionary of key value pairs for logging richer contextual information that can help further understand the cause of an exception specific to your code.

User details is a RaygunIdentifierMessage object that can be used to capture details about a user impacted by the crash report. Make sure to abide by any privacy policies that your company follows when logging such information. The identifier you set here could instead be an internal database Id, or even just a unique guid to at least gauge how many users are impacted by an exception.

HttpContext will cause request and response information to be included in the crash report where applicable.


Custom user provider

By default Raygun4Aspire ships with a DefaultRaygunUserProvider which will attempt to get the user information from the HttpContext.User object. This is Opt-In which can be added as follows:

// The WebApplicationBuilder is created somewhere here

// AddRaygun is called here

builder.AddRaygunUserProvider();

Alternatively, if you want to provide your own implementation of the IRaygunUserProvider, you can do so by creating a class that implements that interface and then registering it via the generic AddRaygunUserProvider overload as seen in the example below.

public class ExampleUserProvider : IRaygunUserProvider
{
  private readonly IHttpContextAccessor _contextAccessor;
  
  public ExampleUserProvider(IHttpContextAccessor httpContextAccessor)
  {
    _contextAccessor = httpContextAccessor;
  }
  
  public RaygunIdentifierMessage? GetUser()
  {
    var ctx = _contextAccessor.HttpContext;
    
    if (ctx == null)
    {
      return null;
    }

    var identity = ctx.User.Identity as ClaimsIdentity;
    
    if (identity?.IsAuthenticated == true)
    {
      return new RaygunIdentifierMessage(identity.Name)
      {
        IsAnonymous = false
      };
    }
    
    return null;
  }
}

This can be registered in the services during configuration like so:

// The WebApplicationBuilder is created somewhere here

// AddRaygun is called here

builder.AddRaygunUserProvider<ExampleUserProvider>();

Port

When running in the local development environment, crash reports are sent to the locally running Raygun portal via port 24605.


Additional configuration options

The following options can be configured in an appsettings file or in code.

For example, in the appsettings.json file:

{
  "RaygunSettings": {
    "ApiKey": "paste_your_api_key_here",
    "ImaginaryOption": true,
    ...
  }
}

The equivalent to doing this in C# is done in the Program.cs file of an application where you are logging exceptions from. Amend the line where you AddRaygun to the WebApplicationBuilder:

builder.AddRaygun(settings =>
{
  settings.ApiKey = "paste_your_api_key_here";
  settings.ImaginaryOption = true;
  ...
});

Examples below are shown in appsettings.json format.

You can exclude errors by their HTTP status code by providing an array of status codes to ignore in the configuration. For example if you wanted to exclude errors that return the I'm a teapot response code, you could use the configuration below.

"RaygunSettings": {
  "ApiKey": "paste_your_api_key_here",
  "ExcludedStatusCodes": [418]
}

If you have sensitive data in an HTTP request that you wish to prevent being transmitted to Raygun, you can provide lists of possible keys (names) to remove. The available options are:

  • IgnoreQueryParameterNames
  • IgnoreFormFieldNames
  • IgnoreHeaderNames
  • IgnoreCookieNames

These can each be set to an array of keys to ignore. Setting an option as * will indicate that all the keys will not be sent to Raygun. Placing * before, after or at both ends of a key will perform an ends-with, starts-with or contains operation respectively. For example, IgnoreFormFieldNames: ["*password*"] will cause Raygun to ignore all form fields that contain "password" anywhere in the name. These options are not case sensitive.

Note: There is also a special IgnoreSensitiveFieldNames property which allows you to set common filter lists that apply to all query-parameters, form-fields, headers and cookies. (This setting is also used for filtering the raw request payload as explained further below).

By default, crash reports in Raygun will include the entire raw request payload where it has access to the HttpContext. If you want to avoid Raygun capturing the raw request payload entirely, then set the IsRawDataIgnored option to true:

"RaygunSettings": {
  "ApiKey": "paste_your_api_key_here",
  "IsRawDataIgnored": true
}

If you have any request payloads formatted as key-value pairs (e.g. key1=value1&key2=value2), then you can set UseKeyValuePairRawDataFilter to true and then any fields listed in the IgnoreSensitiveFieldNames option will not be sent to Raygun.

"RaygunSettings": {
  "ApiKey": "paste_your_api_key_here",
  "UseKeyValuePairRawDataFilter": true,
  "IgnoreSensitiveFieldNames": ["key1"]
}

Similarly, if you have any request payloads formatted as XML, then you can set UseXmlRawDataFilter to true and then any element names listed in the IgnoreSensitiveFieldNames option will not be sent to Raygun.

"RaygunSettings": {
  "ApiKey": "paste_your_api_key_here",
  "UseXmlRawDataFilter": true,
  "IgnoreSensitiveFieldNames": ["Password"]
}

You can also implement your own IRaygunDataFilter to suit your own situations and then register one or more of these custom filters on the RaygunSettings object (a code example for this can be seen after the below RaygunJsonDataFilter implementation example). If the filtering fails, e.g. due to an exception, then null should be returned to indicate this.

Here's an example of a custom raw request data filter for the JSON data structure:

using System;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Mindscape.Raygun4Net.Filters;

public class RaygunJsonDataFilter : IRaygunDataFilter
{
  private const string FILTERED_VALUE = "[FILTERED]";

  public bool CanParse(string data)
  {
    if (!string.IsNullOrEmpty(data))
    {
      int index = data.TakeWhile(c => char.IsWhiteSpace(c)).Count();
      if (index < data.Length)
      {
        if (data.ElementAt(index).Equals('{'))
        {
          return true;
        }
      }
    }
    return false;
  }

  public string Filter(string data, IList<string> ignoredKeys)
  {
    try
    {
      JObject jObject = JObject.Parse(data);

      FilterTokensRecursive(jObject.Children(), ignoredKeys);

      return jObject.ToString(Formatting.None, null);
    }
    catch
    {
      return null;
    }
  }

  private void FilterTokensRecursive(IEnumerable<JToken> tokens, IList<string> ignoredKeys)
  {
    foreach (JToken token in tokens)
    {
      if (token is JProperty)
      {
        var property = token as JProperty;

        if (ShouldIgnore(property, ignoredKeys))
        {
          property.Value = FILTERED_VALUE;
        }
        else if (property.Value.Type == JTokenType.Object)
        {
          FilterTokensRecursive(property.Value.Children(), ignoredKeys);
        }
      }
    }
  }

  private bool ShouldIgnore(JProperty property, IList<string> ignoredKeys)
  {
    bool hasValue = property.Value.Type != JTokenType.Null;

    if (property.Value.Type == JTokenType.String)
    {
      hasValue = !string.IsNullOrEmpty(property.Value.ToString());
    }

    return hasValue && !string.IsNullOrEmpty(property.Name) && ignoredKeys.Any(f => f.Equals(property.Name, StringComparison.OrdinalIgnoreCase));
  }
}

To get the RaygunClient to use a custom raw data filter, Change the AddRaygun statement to use the overload that takes a lambda where you can modify the RaygunSettings object:

builder.AddRaygun((settings) => settings.RawDataFilters.Add(new RaygunJsonDataFilter()));

In the case that the filter operation failed (i.e. the filter returned null), then the original raw request data will be included in the Raygun crash report by default. If you instead want a filter failure to cause the entire raw request payload to be excluded from the Raygun crash report, then you can set the IsRawDataIgnoredWhenFilteringFailed option to true.

"RaygunSettings": {
  "ApiKey": "paste_your_api_key_here",
  "IsRawDataIgnoredWhenFilteringFailed": true
}

Raygun4Aspire will attempt to get the version of your application from the running assembly, and include that version number in each crash report. If this is not the version number you would like, you can overwrite it via the ApplicationVersion option:

"RaygunSettings": {
  "ApiKey": "paste_your_api_key_here",
  "ApplicationVersion": "Avacado"
}

This option can help debug issues within Raygun4Aspire itself. It's highly recommended to not set this option in your production environment. By default, the Raygun4Aspire client will swallow any exceptions that it encounters. Setting ThrowOnError to true will cause said errors to be rethrown instead, which can be useful for troubleshooting Raygun4Aspire.

"RaygunSettings": {
  "ApiKey": "paste_your_api_key_here",
  "ThrowOnError": true
}

Additional features

The following features are set by using the registered RaygunClient singleton. To do this, fetch the RaygunClient singleton from the Services list some time after the builder has been used to build the app.

For example, in Program.cs of a .NET app where crash reports will be sent from:

// The WebApplicationBuilder is used to build the app somewhere up here

var raygunClient = app.Services.GetService<RaygunClient>();

// Use the raygunClient to set any features here

On the RaygunClient singleton, attach an event handler to the SendingMessage event. This event handler will be called just before the RaygunClient sends any crash report to Raygun. The event arguments provide the RaygunMessage object that is about to be sent. One use for this event handler is to add or modify any information on the RaygunMessage.

The following example uses the SendingMessage event to set a global tag that will be included on all crash reports.

var raygunClient = app.Services.GetService<RaygunClient>();

raygunClient.SendingMessage += (sender, eventArgs) =>
{
  eventArgs.Message.Details.Tags.Add("Web API");
};

On the RaygunClient singleton, attach an event handler to the SendingMessage event. This event handler will be called just before the RaygunClient sends any crash report to Raygun. Here you can analyze the crash report that's about to be sent to Raygun and use conditional logic to decide if that crash report is something you know that you don't need logged, so cancel the delivery. Set eventArgs.Cancel to true to avoid sending that crash report to Raygun.

Below is a simple example to cancel sending specific exceptions. Be sure to add null checks for properties and bounds checks when looking into arrays.

var raygunClient = app.Services.GetService<RaygunClient>();

raygunClient.SendingMessage += (sender, eventArgs) =>
{
  if (eventArgs.Message.Details.Error.Message.Contains("Test exception"))
  {
    eventArgs.Cancel = true;
  }
};

If you have common outer exceptions that wrap a valuable inner exception, you can specify these by using the multi-parameter method:

var raygunClient = app.Services.GetService<RaygunClient>();

raygunClient.AddWrapperExceptions(typeof(CustomWrapperException));

In this case, if a CustomWrapperException occurs, it will be removed and replaced with the actual InnerException that was the cause. Note that TargetInvocationException is already added to the wrapper exception list. This method is useful if you have your own custom wrapper exceptions, or a framework is throwing exceptions using its own wrapper.


Locally running Raygun portal

Integrating Raygun into the orchestration project (AppHost) of your Aspire application (as described in Step 2 of the setup instructions) will cause a Raygun resource to be listed as highlighted below.

Raygun Resource

Clicking on the Endpoint link of that Raygun resource will open up a locally running Raygun portal in a new tab. The home page displays a list of the most recently captured crash reports from your Aspire projects.

Raygun Resource

Click on a crash report of interest to view more details. The Summary tab shows information about the exception itself, including the stacktrace. Any custom data and tags you've set will be displayed here too. An Http tab will be available when an HttpContext was present when the exception was observed. There you will be able to view properties of the web request and response. The Raw data tab shows the json payload that was sent between your Aspire projects and the local Raygun portal.

Raygun Resource

The crash report data display in the local Raygun portal app is stored in a Docker volume called "raygun-data". This will store up to 1,000 crash report instances. When new crash reports are ingested beyond this threshold, then the oldest crash reports will be deleted to maintain that limit.


This client is open source and available at the Raygun4Aspire repository.