Raygun provider for .NET Aspire projects. Collects crash reports from .NET code and displays them in a locally running Raygun portal. Optionally can be configured to send crash reports to the Raygun cloud service from your production environment. Tested to work with .NET 8.0 Aspire Preview 5.
Install the Raygun.Aspire.Hosting.Raygun NuGet package into your Aspire orchestration project (AppHost). Either use the NuGet package management GUI in the IDE you use, OR use the below dotnet command.
dotnet add package Raygun.Aspire.Hosting.Raygun
In Program.cs
of the AppHost project, call AddRaygun
on the builder (after the builder is initialized and before it is used to build and run).
// The distributed application builder is created here
builder.AddRaygun();
// The builder is used to build and run the app somewhere down here
The steps so far will cause a Raygun resource to be listed in the orchestration app. Clicking on the URL of that resource will open a local Raygun portal in a new tab where you'll later be able to view crash reports captured in your local development environment. Find out everything you need to know about using this local Raygun portal in the documentation here.
In each of the projects in your Aspire app where you'll be capturing exceptions from, install the Raygun4Aspire NuGet package.
Then, in Program.cs
, add a using Raygun4Aspire;
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
Those are the minimal steps to get Raygun4Aspire capturing unhandled exceptions that occur during web requests in your local development environment. See further down how to also log exceptions from try/catch blocks and other features.
In production appsettings
files of each of your .NET projects, add the below RaygunSettings
section. Substitute in your application API key that Raygun provides when you create a new Application in Raygun.
"RaygunSettings": {
"ApiKey": "YOUR_APP_API_KEY"
}
Get AI suggestions to resolve exceptions from a locally running LLM!
Install the Raygun.Aspire.Hosting.Ollama NuGet package into the Aspire orchestration project (AppHost). Either use the NuGet package management GUI in the IDE you use, OR the below dotnet command.
dotnet add package Raygun.Aspire.Hosting.Ollama
In Program.cs
of the AppHost project, right above where you added the builder.AddRaygun();
line in Step 2 of the standard installation, you'll need to add the following line to add the Ollama container.
var ollama = builder.AddOllama();
Finally, you will need to modify the builder.AddRaygun();
line to add a reference to Ollama. Your final code should look like this.
// The distributed application builder is created here
var ollama = builder.AddOllama();
builder.AddRaygun().WithReference(ollama);
// The builder is used to build and run the app somewhere down here
Now, when you view an exception that's been captured in the locally running Raygun portal, click the "AI Error Respolution" button in the top right corner to get AI suggestions on what to do about it. The first time you use this feature, the LLM will need to be downloaded. Keep the Aspire orchestration app open until this has completed. AI responses are not stored, and so they restart everytime you drill in to view an exception and click the button. Find out more details about how to use this feature here.
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.
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>();
When running in the local development environment, crash reports are sent to the locally running Raygun portal via port 24605
.
The following options can be configured in an appsettings file or in code.
For example, in the appsettings.json
file:
{
"RaygunSettings": {
"ApiKey": "YOUR_APP_API_KEY",
"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 = "YOUR_APP_API_KEY";
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": "YOUR_APP_API_KEY",
"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:
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": "YOUR_APP_API_KEY",
"IsRawDataIgnored": true
}
Alternatively, 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": "YOUR_APP_API_KEY",
"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": "YOUR_APP_API_KEY",
"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": "YOUR_APP_API_KEY",
"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": "YOUR_APP_API_KEY",
"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": "YOUR_APP_API_KEY",
"ThrowOnError": true
}
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.