diff --git a/README.md b/README.md
index 7b2ca778..63d5f844 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# Exceptionless .NET Clients
-[](https://github.com/Exceptionless/Exceptionless.Net/actions)
-[](https://github.com/Exceptionless/Exceptionless.Net/actions)
-[](https://github.com/Exceptionless/Exceptionless.Net/actions)
+[](https://github.com/Exceptionless/Exceptionless.Net/actions/workflows/build-windows.yml)
+[](https://github.com/Exceptionless/Exceptionless.Net/actions/workflows/build-osx.yml)
+[](https://github.com/Exceptionless/Exceptionless.Net/actions/workflows/build-linux.yml)
[](https://www.nuget.org/packages/Exceptionless/)
[](https://discord.gg/6HxgFCx)
@@ -29,8 +29,8 @@ editor design surfaces are available.
1. You will need to install:
1. [Visual Studio 2022](https://visualstudio.microsoft.com/vs/community/)
- 2. [.NET Core 6.x & 8.x SDK with VS Tooling](https://dotnet.microsoft.com/download)
- 3. [.NET Framework 4.6.2 Developer Pack](https://dotnet.microsoft.com/download/dotnet-framework/net462)
+ 2. [.NET 10 SDK with Visual Studio tooling](https://dotnet.microsoft.com/download)
+ 3. [.NET Framework 4.7.2 Developer Pack](https://dotnet.microsoft.com/download/dotnet-framework/net472)
2. Open the `Exceptionless.Net.slnx` Visual Studio solution file.
3. Select `Exceptionless.SampleConsole` as the startup project.
4. Run the project by pressing `F5` to start the console.
@@ -43,7 +43,7 @@ build windows specific packages.
1. You will need to install:
1. [Visual Studio Code](https://code.visualstudio.com)
- 2. [.NET Core 6.x & 8.x SDK with VS Tooling](https://dotnet.microsoft.com/download)
+ 2. [.NET 10 SDK](https://dotnet.microsoft.com/download)
2. Open the cloned Exceptionless.Net folder.
3. Run the `Exceptionless.SampleConsole` project by pressing `F5` to start the console.
diff --git a/build/common.props b/build/common.props
index 76e6fddb..eea9d0dd 100644
--- a/build/common.props
+++ b/build/common.props
@@ -17,7 +17,7 @@
$(SolutionDir)artifacts
exceptionless-icon.png
Exceptionless;Error;Error-Handling;Error-Handler;Error-Reporting;Error-Management;Error-Monitoring;Handling;Management;Monitoring;Report;Reporting;Crash-Reporting;Exception;Exception-Handling;Exception-Handler;Exception-Reporting;Exceptions;Log;Logs;Logging;Unhandled;Unhandled-Exceptions;Feature;Configuration;Debug;FeatureToggle;Metrics;ELMAH
- APACHE-2.0
+ Apache-2.0
$(PackageProjectUrl)
true
true
@@ -41,9 +41,9 @@
-
-
-
+
+
+
diff --git a/samples/Exceptionless.SampleAspNetCore/Controllers/ValuesController.cs b/samples/Exceptionless.SampleAspNetCore/Controllers/ValuesController.cs
index 964450dc..3ed9dab9 100644
--- a/samples/Exceptionless.SampleAspNetCore/Controllers/ValuesController.cs
+++ b/samples/Exceptionless.SampleAspNetCore/Controllers/ValuesController.cs
@@ -10,7 +10,7 @@ public class ValuesController : Controller {
private readonly ILogger _logger;
public ValuesController(ExceptionlessClient exceptionlessClient, ILogger logger) {
- // ExceptionlessClient instance from DI that was registered with the AddExceptionless call in Startup.ConfigureServices
+ // ExceptionlessClient instance from DI that was registered with the builder.AddExceptionless call in Program.cs.
_exceptionlessClient = exceptionlessClient;
_logger = logger;
}
@@ -21,7 +21,7 @@ public Dictionary Get() {
// Submit a feature usage event directly using the client instance that is injected from the DI container.
_exceptionlessClient.SubmitFeatureUsage("ValuesController_Get");
- // This log message will get sent to Exceptionless since Exceptionless has be added to the logging system in Program.cs.
+ // This log message will get sent to Exceptionless since Exceptionless has been added to the logging system in Program.cs.
_logger.LogWarning("Test warning message");
try {
@@ -42,7 +42,8 @@ public Dictionary Get() {
handledException.ToExceptionless().Submit();
}
- // Unhandled exceptions will get reported since called UseExceptionless in the Startup.cs which registers a listener for unhandled exceptions.
+ // Unhandled exceptions will get reported because Program.cs enables the built-in exception handler pipeline
+ // and wires Exceptionless into both ASP.NET Core diagnostics and middleware hooks.
throw new Exception($"Unhandled Exception: {Guid.NewGuid()}");
}
}
diff --git a/samples/Exceptionless.SampleAspNetCore/Exceptionless.SampleAspNetCore.csproj b/samples/Exceptionless.SampleAspNetCore/Exceptionless.SampleAspNetCore.csproj
index 89d90666..3b47adbc 100644
--- a/samples/Exceptionless.SampleAspNetCore/Exceptionless.SampleAspNetCore.csproj
+++ b/samples/Exceptionless.SampleAspNetCore/Exceptionless.SampleAspNetCore.csproj
@@ -1,10 +1,10 @@
- net8.0
+ net10.0
-
\ No newline at end of file
+
diff --git a/samples/Exceptionless.SampleAspNetCore/Program.cs b/samples/Exceptionless.SampleAspNetCore/Program.cs
index 2b82e8cb..629d1a3f 100644
--- a/samples/Exceptionless.SampleAspNetCore/Program.cs
+++ b/samples/Exceptionless.SampleAspNetCore/Program.cs
@@ -1,21 +1,37 @@
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Hosting;
-
-namespace Exceptionless.SampleAspNetCore {
- public class Program {
- public static void Main(string[] args) {
- CreateHostBuilder(args).Build().Run();
- }
-
- public static IHostBuilder CreateHostBuilder(string[] args) =>
- Host.CreateDefaultBuilder(args)
- .ConfigureLogging(b => {
- // By default sends warning and error log messages to Exceptionless.
- // Log levels can be controlled remotely per log source from the Exceptionless app in near real-time.
- b.AddExceptionless();
- })
- .ConfigureWebHostDefaults(webBuilder => {
- webBuilder.UseStartup();
- });
- }
-}
\ No newline at end of file
+using Exceptionless;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// By default sends warning and error log messages to Exceptionless.
+// Log levels can be controlled remotely per log source from the Exceptionless app in near real-time.
+builder.Logging.AddExceptionless();
+
+// Reads settings from IConfiguration then adds additional configuration from this lambda.
+// This also configures ExceptionlessClient.Default and host shutdown queue flushing.
+builder.AddExceptionless(c => c.DefaultData["Startup"] = "heyyy");
+// OR
+// builder.AddExceptionless();
+// OR
+// builder.AddExceptionless("API_KEY_HERE");
+
+// Adds ASP.NET Core request/unhandled exception hooks and standard exception handling services.
+builder.Services.AddExceptionless();
+builder.Services.AddProblemDetails();
+
+// This is normal ASP.NET Core code.
+builder.Services.AddControllers();
+
+var app = builder.Build();
+
+// Uses the built-in exception handler pipeline, with Exceptionless capturing via IExceptionHandler.
+app.UseExceptionHandler();
+
+// Adds Exceptionless middleware for diagnostics, 404 tracking, and queue processing.
+app.UseExceptionless();
+
+app.MapControllers();
+
+app.Run();
diff --git a/samples/Exceptionless.SampleAspNetCore/Startup.cs b/samples/Exceptionless.SampleAspNetCore/Startup.cs
deleted file mode 100644
index 2f17c47a..00000000
--- a/samples/Exceptionless.SampleAspNetCore/Startup.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Exceptionless.SampleAspNetCore {
- public class Startup {
- public Startup(IConfiguration configuration) {
- Configuration = configuration;
- }
-
- public IConfiguration Configuration { get; }
-
- public void ConfigureServices(IServiceCollection services) {
- // Reads settings from IConfiguration then adds additional configuration from this lambda.
- // This also configures ExceptionlessClient.Default
- services.AddExceptionless(c => c.DefaultData["Startup"] = "heyyy");
- // OR
- // services.AddExceptionless();
- // OR
- // services.AddExceptionless("API_KEY_HERE");
-
- // This enables Exceptionless to gather more detailed information about unhandled exceptions and other events
- services.AddHttpContextAccessor();
-
- // This is normal ASP.NET code
- services.AddControllers();
- }
-
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
- // Adds Exceptionless middleware to listen for unhandled exceptions
- app.UseExceptionless();
-
- // This is normal ASP.NET code
- app.UseRouting();
- app.UseEndpoints(endpoints => {
- endpoints.MapControllers();
- });
- }
- }
-}
\ No newline at end of file
diff --git a/samples/Exceptionless.SampleBlazorWebAssemblyApp/Exceptionless.SampleBlazorWebAssemblyApp.csproj b/samples/Exceptionless.SampleBlazorWebAssemblyApp/Exceptionless.SampleBlazorWebAssemblyApp.csproj
index 13a6ec13..b675cb51 100644
--- a/samples/Exceptionless.SampleBlazorWebAssemblyApp/Exceptionless.SampleBlazorWebAssemblyApp.csproj
+++ b/samples/Exceptionless.SampleBlazorWebAssemblyApp/Exceptionless.SampleBlazorWebAssemblyApp.csproj
@@ -1,14 +1,14 @@
- net8.0
+ net10.0
enable
enable
-
-
+
+
diff --git a/samples/Exceptionless.SampleConsole/Exceptionless.SampleConsole.csproj b/samples/Exceptionless.SampleConsole/Exceptionless.SampleConsole.csproj
index 579f3535..5b7c7b38 100644
--- a/samples/Exceptionless.SampleConsole/Exceptionless.SampleConsole.csproj
+++ b/samples/Exceptionless.SampleConsole/Exceptionless.SampleConsole.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
Exceptionless.SampleConsole
Exe
$(DefineConstants);NETSTANDARD;NETSTANDARD2_0
@@ -20,11 +20,11 @@
-
-
+
+
-
\ No newline at end of file
+
diff --git a/samples/Exceptionless.SampleHosting/Exceptionless.SampleHosting.csproj b/samples/Exceptionless.SampleHosting/Exceptionless.SampleHosting.csproj
index a0088dca..92f35cf6 100644
--- a/samples/Exceptionless.SampleHosting/Exceptionless.SampleHosting.csproj
+++ b/samples/Exceptionless.SampleHosting/Exceptionless.SampleHosting.csproj
@@ -1,10 +1,10 @@
- net8.0
+ net10.0
-
\ No newline at end of file
+
diff --git a/samples/Exceptionless.SampleHosting/Program.cs b/samples/Exceptionless.SampleHosting/Program.cs
index 157eaca2..03a668d7 100644
--- a/samples/Exceptionless.SampleHosting/Program.cs
+++ b/samples/Exceptionless.SampleHosting/Program.cs
@@ -18,7 +18,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
// Log levels can be controlled remotely per log source from the Exceptionless app in near real-time.
builder.AddExceptionless();
})
- .UseExceptionless() // listens for host shutdown and
+ .UseExceptionless() // initializes the client and flushes the queue during host shutdown
.ConfigureServices(services => {
// Reads settings from IConfiguration then adds additional configuration from this lambda.
// This also configures ExceptionlessClient.Default
@@ -64,11 +64,13 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
handledException.ToExceptionless().Submit();
}
- // Unhandled exceptions will get reported since called UseExceptionless in the Startup.cs which registers a listener for unhandled exceptions.
+ // This simulates an unhandled exception. Host-level Exceptionless integration reports
+ // host/AppDomain-level unhandled exceptions; ASP.NET Core request-pipeline exceptions
+ // require the ASP.NET Core integration and UseExceptionHandler.
throw new Exception($"Unhandled Exception: {Guid.NewGuid()}");
});
});
});
});
}
-}
\ No newline at end of file
+}
diff --git a/samples/Exceptionless.SampleLambda/Exceptionless.SampleLambda.csproj b/samples/Exceptionless.SampleLambda/Exceptionless.SampleLambda.csproj
index 74381c7a..d4c37ad0 100644
--- a/samples/Exceptionless.SampleLambda/Exceptionless.SampleLambda.csproj
+++ b/samples/Exceptionless.SampleLambda/Exceptionless.SampleLambda.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0
true
Lambda
@@ -8,11 +8,11 @@
true
-
-
+
+
-
\ No newline at end of file
+
diff --git a/samples/Exceptionless.SampleLambdaAspNetCore/Exceptionless.SampleLambdaAspNetCore.csproj b/samples/Exceptionless.SampleLambdaAspNetCore/Exceptionless.SampleLambdaAspNetCore.csproj
index eb67a1e9..152843da 100644
--- a/samples/Exceptionless.SampleLambdaAspNetCore/Exceptionless.SampleLambdaAspNetCore.csproj
+++ b/samples/Exceptionless.SampleLambdaAspNetCore/Exceptionless.SampleLambdaAspNetCore.csproj
@@ -1,15 +1,15 @@
- net8.0
+ net10.0
true
Lambda
-
-
+
+
-
\ No newline at end of file
+
diff --git a/samples/Exceptionless.SampleMvc/Exceptionless.SampleMvc.csproj b/samples/Exceptionless.SampleMvc/Exceptionless.SampleMvc.csproj
index 77d8f63f..a9b6446d 100644
--- a/samples/Exceptionless.SampleMvc/Exceptionless.SampleMvc.csproj
+++ b/samples/Exceptionless.SampleMvc/Exceptionless.SampleMvc.csproj
@@ -49,35 +49,35 @@
- ..\..\packages\Microsoft.Web.Infrastructure.2.0.0\lib\net40\Microsoft.Web.Infrastructure.dll
+ ..\..\packages\Microsoft.Web.Infrastructure.2.0.1\lib\net40\Microsoft.Web.Infrastructure.dll
- ..\..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll
+ ..\..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll
- ..\..\packages\Newtonsoft.Json.Bson.1.0.2\lib\net45\Newtonsoft.Json.Bson.dll
+ ..\..\packages\Newtonsoft.Json.Bson.1.0.3\lib\net45\Newtonsoft.Json.Bson.dll
- ..\..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll
+ ..\..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll
- ..\..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll
+ ..\..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll
..\..\packages\Microsoft.AspNet.WebApi.Client.6.0.0\lib\net45\System.Net.Http.Formatting.dll
- ..\..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll
+ ..\..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll
- ..\..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll
+ ..\..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll
- ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll
+ ..\..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll
@@ -284,4 +284,4 @@
-->
-
\ No newline at end of file
+
diff --git a/samples/Exceptionless.SampleMvc/packages.config b/samples/Exceptionless.SampleMvc/packages.config
index 50c1e96d..70a3f3fb 100644
--- a/samples/Exceptionless.SampleMvc/packages.config
+++ b/samples/Exceptionless.SampleMvc/packages.config
@@ -1,9 +1,9 @@
-
+
-
+
@@ -15,14 +15,14 @@
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/samples/Exceptionless.SampleWebApi/Exceptionless.SampleWebApi.csproj b/samples/Exceptionless.SampleWebApi/Exceptionless.SampleWebApi.csproj
index b8120945..57aa935b 100644
--- a/samples/Exceptionless.SampleWebApi/Exceptionless.SampleWebApi.csproj
+++ b/samples/Exceptionless.SampleWebApi/Exceptionless.SampleWebApi.csproj
@@ -24,11 +24,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/samples/Exceptionless.SampleWindows/Exceptionless.SampleWindows.csproj b/samples/Exceptionless.SampleWindows/Exceptionless.SampleWindows.csproj
index 309f1e57..f08db0b7 100644
--- a/samples/Exceptionless.SampleWindows/Exceptionless.SampleWindows.csproj
+++ b/samples/Exceptionless.SampleWindows/Exceptionless.SampleWindows.csproj
@@ -1,6 +1,6 @@
-net8.0-windows;net462
+net10.0-windows;net462
@@ -10,7 +10,7 @@
true
-
+
$(DefineConstants);NETSTANDARD;NETSTANDARD2_0
@@ -26,4 +26,4 @@
-
\ No newline at end of file
+
diff --git a/samples/Exceptionless.SampleWpf/Exceptionless.SampleWpf.csproj b/samples/Exceptionless.SampleWpf/Exceptionless.SampleWpf.csproj
index b31bac97..745b6475 100644
--- a/samples/Exceptionless.SampleWpf/Exceptionless.SampleWpf.csproj
+++ b/samples/Exceptionless.SampleWpf/Exceptionless.SampleWpf.csproj
@@ -1,7 +1,7 @@
- net8.0-windows;net462
+ net10.0-windows;net462
@@ -10,7 +10,7 @@
Exceptionless
-
+
$(DefineConstants);NETSTANDARD;NETSTANDARD2_0
@@ -22,4 +22,4 @@
-
\ No newline at end of file
+
diff --git a/src/Exceptionless/Exceptionless.csproj b/src/Exceptionless/Exceptionless.csproj
index 6e6ffa24..ec945334 100644
--- a/src/Exceptionless/Exceptionless.csproj
+++ b/src/Exceptionless/Exceptionless.csproj
@@ -34,13 +34,13 @@
-
+
-
-
+
+
@@ -60,4 +60,4 @@
-
\ No newline at end of file
+
diff --git a/src/Platforms/Exceptionless.AspNetCore/Exceptionless.AspNetCore.csproj b/src/Platforms/Exceptionless.AspNetCore/Exceptionless.AspNetCore.csproj
index 7d9fe1bd..2e1510a0 100644
--- a/src/Platforms/Exceptionless.AspNetCore/Exceptionless.AspNetCore.csproj
+++ b/src/Platforms/Exceptionless.AspNetCore/Exceptionless.AspNetCore.csproj
@@ -2,7 +2,7 @@
- netstandard2.0
+ net8.0;net9.0;net10.0
@@ -22,15 +22,7 @@
-
-
-
-
-
-
+
+
-
-
- $(DefineConstants);NETSTANDARD2_0
-
-
\ No newline at end of file
+
diff --git a/src/Platforms/Exceptionless.AspNetCore/ExceptionlessDiagnosticListener.cs b/src/Platforms/Exceptionless.AspNetCore/ExceptionlessDiagnosticListener.cs
index 5108b513..88ed5688 100644
--- a/src/Platforms/Exceptionless.AspNetCore/ExceptionlessDiagnosticListener.cs
+++ b/src/Platforms/Exceptionless.AspNetCore/ExceptionlessDiagnosticListener.cs
@@ -1,49 +1,78 @@
-using System;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
using Exceptionless.Plugins;
using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.DiagnosticAdapter;
namespace Exceptionless.AspNetCore {
- public sealed class ExceptionlessDiagnosticListener {
+ public sealed class ExceptionlessDiagnosticListener : IObserver> {
+ private const string HandledExceptionEvent = "Microsoft.AspNetCore.Diagnostics.HandledException";
+ private const string DiagnosticsUnhandledExceptionEvent = "Microsoft.AspNetCore.Diagnostics.UnhandledException";
+ private const string HostingUnhandledExceptionEvent = "Microsoft.AspNetCore.Hosting.UnhandledException";
+ private const string HostingDiagnosticsUnhandledExceptionEvent = "Microsoft.AspNetCore.Hosting.Diagnostics.UnhandledException";
+ private const string MiddlewareExceptionEvent = "Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException";
private readonly ExceptionlessClient _client;
public ExceptionlessDiagnosticListener(ExceptionlessClient client) {
_client = client;
}
- [DiagnosticName("Microsoft.AspNetCore.Diagnostics.HandledException")]
- public void OnDiagnosticHandledException(HttpContext httpContext, Exception exception) {
- var contextData = new ContextData();
- contextData.SetSubmissionMethod("Microsoft.AspNetCore.Diagnostics.HandledException");
+ public void OnCompleted() { }
- exception.ToExceptionless(contextData, _client).SetHttpContext(httpContext).Submit();
+ public void OnError(Exception error) { }
+
+ internal static bool IsRelevantEvent(string eventName) {
+ return String.Equals(eventName, HandledExceptionEvent, StringComparison.Ordinal) ||
+ String.Equals(eventName, DiagnosticsUnhandledExceptionEvent, StringComparison.Ordinal) ||
+ String.Equals(eventName, HostingUnhandledExceptionEvent, StringComparison.Ordinal) ||
+ String.Equals(eventName, HostingDiagnosticsUnhandledExceptionEvent, StringComparison.Ordinal) ||
+ String.Equals(eventName, MiddlewareExceptionEvent, StringComparison.Ordinal);
}
- [DiagnosticName("Microsoft.AspNetCore.Diagnostics.UnhandledException")]
- public void OnDiagnosticUnhandledException(HttpContext httpContext, Exception exception) {
- var contextData = new ContextData();
- contextData.MarkAsUnhandledError();
- contextData.SetSubmissionMethod("Microsoft.AspNetCore.Diagnostics.UnhandledException");
+ public void OnNext(KeyValuePair diagnosticEvent) {
+ switch (diagnosticEvent.Key) {
+ case HandledExceptionEvent:
+ SubmitException(diagnosticEvent.Value, diagnosticEvent.Key, false);
+ break;
+ case DiagnosticsUnhandledExceptionEvent:
+ case HostingUnhandledExceptionEvent:
+ case HostingDiagnosticsUnhandledExceptionEvent:
+ SubmitException(diagnosticEvent.Value, diagnosticEvent.Key, true);
+ break;
+ case MiddlewareExceptionEvent:
+ if (diagnosticEvent.Value is null)
+ break;
- exception.ToExceptionless(contextData, _client).SetHttpContext(httpContext).Submit();
+ string middlewareName = GetPropertyValue(diagnosticEvent.Value, "name") as string;
+ SubmitException(diagnosticEvent.Value, middlewareName ?? diagnosticEvent.Key, true);
+ break;
+ }
}
- [DiagnosticName("Microsoft.AspNetCore.Hosting.UnhandledException")]
- public void OnHostingUnhandledException(HttpContext httpContext, Exception exception) {
+ private void SubmitException(object payload, string submissionMethod, bool isUnhandledError) {
+ if (payload is null)
+ return;
+
+ var httpContext = GetPropertyValue(payload, "httpContext") as HttpContext;
+ var exception = GetPropertyValue(payload, "exception") as Exception;
+ if (httpContext == null || exception == null)
+ return;
+
var contextData = new ContextData();
- contextData.MarkAsUnhandledError();
- contextData.SetSubmissionMethod("Microsoft.AspNetCore.Hosting.UnhandledException");
+ if (isUnhandledError)
+ contextData.MarkAsUnhandledError();
+ contextData.SetSubmissionMethod(submissionMethod);
exception.ToExceptionless(contextData, _client).SetHttpContext(httpContext).Submit();
}
- [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")]
- public void OnMiddlewareException(HttpContext httpContext, Exception exception, string name) {
- var contextData = new ContextData();
- contextData.MarkAsUnhandledError();
- contextData.SetSubmissionMethod(name ?? "Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException");
+ private static object GetPropertyValue(object payload, string propertyName) {
+ if (payload is null)
+ return null;
- exception.ToExceptionless(contextData, _client).SetHttpContext(httpContext).Submit();
+ return payload.GetType()
+ .GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase)?
+ .GetValue(payload);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Platforms/Exceptionless.AspNetCore/ExceptionlessExceptionHandler.cs b/src/Platforms/Exceptionless.AspNetCore/ExceptionlessExceptionHandler.cs
new file mode 100644
index 00000000..716d8d95
--- /dev/null
+++ b/src/Platforms/Exceptionless.AspNetCore/ExceptionlessExceptionHandler.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Exceptionless.Plugins;
+using Microsoft.AspNetCore.Diagnostics;
+using Microsoft.AspNetCore.Http;
+
+namespace Exceptionless.AspNetCore {
+ public sealed class ExceptionlessExceptionHandler : IExceptionHandler {
+ private readonly ExceptionlessClient _client;
+
+ public ExceptionlessExceptionHandler(ExceptionlessClient client) {
+ _client = client ?? ExceptionlessClient.Default;
+ }
+
+ public ValueTask TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) {
+ if (cancellationToken.IsCancellationRequested)
+ return ValueTask.FromResult(false);
+
+ var contextData = new ContextData();
+ contextData.MarkAsUnhandledError();
+ contextData.SetSubmissionMethod(nameof(ExceptionlessExceptionHandler));
+
+ exception.ToExceptionless(contextData, _client).SetHttpContext(httpContext).Submit();
+
+ return ValueTask.FromResult(false);
+ }
+ }
+}
diff --git a/src/Platforms/Exceptionless.AspNetCore/ExceptionlessExtensions.cs b/src/Platforms/Exceptionless.AspNetCore/ExceptionlessExtensions.cs
index cbe6f0c9..e59bae4e 100644
--- a/src/Platforms/Exceptionless.AspNetCore/ExceptionlessExtensions.cs
+++ b/src/Platforms/Exceptionless.AspNetCore/ExceptionlessExtensions.cs
@@ -1,25 +1,36 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Exceptionless.AspNetCore;
using Exceptionless.Models;
using Exceptionless.Models.Data;
using Exceptionless.Plugins.Default;
-using Microsoft.Extensions.Configuration;
+using Microsoft.AspNetCore.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Exceptionless {
public static class ExceptionlessExtensions {
///
- /// Adds the Exceptionless middleware for capturing unhandled exceptions and ensures that the Exceptionless pending queue is processed before the host shuts down.
+ /// Registers the Exceptionless and required ASP.NET Core services
+ /// for capturing unhandled exceptions. Call this in your service configuration alongside app.UseExceptionHandler().
+ ///
+ public static IServiceCollection AddExceptionless(this IServiceCollection services) {
+ services.AddHttpContextAccessor();
+ if (!services.Any(descriptor =>
+ descriptor.ServiceType == typeof(IExceptionHandler) &&
+ descriptor.ImplementationType == typeof(ExceptionlessExceptionHandler)))
+ services.AddExceptionHandler();
+ return services;
+ }
+
+ ///
+ /// Adds the Exceptionless middleware for 404 tracking and queue processing,
+ /// subscribes to diagnostic events, and configures ASP.NET Core plugins.
///
- /// The target to add Exceptionless to.
- /// Optional pre-configured instance to use. If not specified (recommended), the
- /// instance registered in the services collection will be used.
- ///
public static IApplicationBuilder UseExceptionless(this IApplicationBuilder app, ExceptionlessClient client = null) {
if (client == null)
client = app.ApplicationServices.GetService() ?? ExceptionlessClient.Default;
@@ -34,7 +45,9 @@ public static IApplicationBuilder UseExceptionless(this IApplicationBuilder app,
//client.Configuration.Resolver.Register();
var diagnosticListener = app.ApplicationServices.GetRequiredService();
- diagnosticListener?.SubscribeWithAdapter(new ExceptionlessDiagnosticListener(client));
+ diagnosticListener?.Subscribe(
+ new ExceptionlessDiagnosticListener(client),
+ eventName => ExceptionlessDiagnosticListener.IsRelevantEvent(eventName));
var lifetime = app.ApplicationServices.GetRequiredService();
lifetime.ApplicationStopping.Register(() => client.ProcessQueueAsync().ConfigureAwait(false).GetAwaiter().GetResult());
@@ -42,27 +55,6 @@ public static IApplicationBuilder UseExceptionless(this IApplicationBuilder app,
return app.UseMiddleware(client);
}
- [Obsolete("UseExceptionless should be called without an overload, ExceptionlessClient should be configured when adding to services collection using AddExceptionless")]
- public static IApplicationBuilder UseExceptionless(this IApplicationBuilder app, Action configure) {
- var client = app.ApplicationServices.GetService() ?? ExceptionlessClient.Default;
- configure?.Invoke(client.Configuration);
- return app.UseExceptionless(client);
- }
-
- [Obsolete("UseExceptionless should be called without an overload, ExceptionlessClient should be configured when adding to services collection using AddExceptionless")]
- public static IApplicationBuilder UseExceptionless(this IApplicationBuilder app, IConfiguration configuration) {
- var client = app.ApplicationServices.GetService() ?? ExceptionlessClient.Default;
- client.Configuration.ReadFromConfiguration(configuration);
- return app.UseExceptionless(client);
- }
-
- [Obsolete("UseExceptionless should be called without an overload, ExceptionlessClient should be configured when adding to services collection using AddExceptionless")]
- public static IApplicationBuilder UseExceptionless(this IApplicationBuilder app, string apiKey) {
- var client = app.ApplicationServices.GetService() ?? ExceptionlessClient.Default;
- client.Configuration.ApiKey = apiKey;
- return app.UseExceptionless(client);
- }
-
///
/// Adds the current request info.
///
diff --git a/src/Platforms/Exceptionless.AspNetCore/ExceptionlessMiddleware.cs b/src/Platforms/Exceptionless.AspNetCore/ExceptionlessMiddleware.cs
index 79f72170..7a74ccc3 100644
--- a/src/Platforms/Exceptionless.AspNetCore/ExceptionlessMiddleware.cs
+++ b/src/Platforms/Exceptionless.AspNetCore/ExceptionlessMiddleware.cs
@@ -1,7 +1,5 @@
-using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
-using Exceptionless.Plugins;
namespace Exceptionless.AspNetCore {
public class ExceptionlessMiddleware {
@@ -20,19 +18,7 @@ public async Task Invoke(HttpContext context) {
});
}
- try {
- await _next(context);
- } catch (Exception ex) {
- if (context.RequestAborted.IsCancellationRequested)
- throw;
-
- var contextData = new ContextData();
- contextData.MarkAsUnhandledError();
- contextData.SetSubmissionMethod(nameof(ExceptionlessMiddleware));
-
- ex.ToExceptionless(contextData, _client).SetHttpContext(context).Submit();
- throw;
- }
+ await _next(context);
if (context.Response?.StatusCode == 404) {
string path = context.Request.Path.HasValue ? context.Request.Path.Value : "/";
@@ -40,4 +26,4 @@ public async Task Invoke(HttpContext context) {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Platforms/Exceptionless.AspNetCore/RequestInfoCollector.cs b/src/Platforms/Exceptionless.AspNetCore/RequestInfoCollector.cs
index 146ec89a..9973ac09 100644
--- a/src/Platforms/Exceptionless.AspNetCore/RequestInfoCollector.cs
+++ b/src/Platforms/Exceptionless.AspNetCore/RequestInfoCollector.cs
@@ -133,8 +133,6 @@ private static object GetPostData(HttpContext context, ExceptionlessConfiguratio
HeaderNames.Authorization,
HeaderNames.Cookie,
HeaderNames.Host,
- HeaderNames.Method,
- HeaderNames.Path,
HeaderNames.ProxyAuthorization,
HeaderNames.Referer,
HeaderNames.UserAgent
@@ -176,7 +174,7 @@ private static Dictionary ToDictionary(this IRequestCookieCollec
if (kvp.Value == null || kvp.Value.Length >= MAX_DATA_ITEM_LENGTH)
continue;
-
+
d[kvp.Key] = kvp.Value;
}
@@ -194,7 +192,7 @@ private static Dictionary ToDictionary(this IEnumerable= MAX_DATA_ITEM_LENGTH)
continue;
-
+
d[kvp.Key] = value;
} catch (Exception ex) {
d[kvp.Key] = $"EXCEPTION: {ex.Message}";
diff --git a/src/Platforms/Exceptionless.AspNetCore/readme.txt b/src/Platforms/Exceptionless.AspNetCore/readme.txt
index 6364a53d..40961fc4 100644
--- a/src/Platforms/Exceptionless.AspNetCore/readme.txt
+++ b/src/Platforms/Exceptionless.AspNetCore/readme.txt
@@ -1,4 +1,4 @@
--------------------------------------
+-------------------------------------
Exceptionless Readme
-------------------------------------
Exceptionless provides real-time error reporting for your apps. It organizes the
@@ -31,15 +31,18 @@ You must import the "Exceptionless" namespace and add the following code to regi
using Exceptionless;
var builder = WebApplication.CreateBuilder(args);
-builder.Services.AddExceptionless("API_KEY_HERE");
+builder.AddExceptionless(c => c.ApiKey = "API_KEY_HERE");
+builder.Services.AddExceptionless();
+builder.Services.AddProblemDetails();
In order to start gathering unhandled exceptions, you will need to register the Exceptionless middleware in your application
like this after building your application:
var app = builder.Build();
+app.UseExceptionHandler();
app.UseExceptionless();
-Alternatively, you can use different overloads of the AddExceptionless method for other configuration options.
+Alternatively, you can use different overloads of the host builder AddExceptionless method for other configuration options.
Please visit the documentation at https://exceptionless.com/docs/clients/dotnet/sending-events/ for additional examples
and guidance on sending events to Exceptionless.
diff --git a/src/Platforms/Exceptionless.Extensions.Hosting/Exceptionless.Extensions.Hosting.csproj b/src/Platforms/Exceptionless.Extensions.Hosting/Exceptionless.Extensions.Hosting.csproj
index 007d21b0..471209f8 100644
--- a/src/Platforms/Exceptionless.Extensions.Hosting/Exceptionless.Extensions.Hosting.csproj
+++ b/src/Platforms/Exceptionless.Extensions.Hosting/Exceptionless.Extensions.Hosting.csproj
@@ -7,15 +7,23 @@
Exceptionless provider for Microsoft.Extensions.Hosting
Exceptionless provider for Microsoft.Extensions.Hosting. $(Description)
$(PackageTags);Microsoft.Extensions.Hosting;Hosting
- netstandard2.0
+ net8.0;net9.0;net10.0
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Platforms/Exceptionless.Extensions.Hosting/ExceptionlessExtensions.cs b/src/Platforms/Exceptionless.Extensions.Hosting/ExceptionlessExtensions.cs
index 0f400ca2..b224111f 100644
--- a/src/Platforms/Exceptionless.Extensions.Hosting/ExceptionlessExtensions.cs
+++ b/src/Platforms/Exceptionless.Extensions.Hosting/ExceptionlessExtensions.cs
@@ -3,6 +3,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using System.Linq;
namespace Exceptionless {
public static class ExceptionlessExtensions {
@@ -13,10 +14,45 @@ public static class ExceptionlessExtensions {
///
public static IHostBuilder UseExceptionless(this IHostBuilder hostBuilder) {
return hostBuilder.ConfigureServices(delegate (HostBuilderContext context, IServiceCollection collection) {
- collection.AddSingleton();
+ collection.AddExceptionlessLifetimeService();
});
}
+ ///
+ /// Ensures that the Exceptionless pending queue is processed before the host shuts down using the .NET 8+ builder API.
+ ///
+ public static IHostApplicationBuilder UseExceptionless(this IHostApplicationBuilder builder) {
+ builder.Services.AddExceptionlessLifetimeService();
+ return builder;
+ }
+
+ ///
+ /// Adds the given pre-configured to the host builder and registers lifecycle hooks.
+ ///
+ public static IHostApplicationBuilder AddExceptionless(this IHostApplicationBuilder builder, ExceptionlessClient client) {
+ builder.Services.AddExceptionless(client);
+ builder.Services.AddExceptionlessLifetimeService();
+ return builder;
+ }
+
+ ///
+ /// Adds an to the host builder and registers lifecycle hooks.
+ ///
+ public static IHostApplicationBuilder AddExceptionless(this IHostApplicationBuilder builder, string apiKey) {
+ builder.Services.AddExceptionless(apiKey);
+ builder.Services.AddExceptionlessLifetimeService();
+ return builder;
+ }
+
+ ///
+ /// Adds an to the host builder using the builder configuration and registers lifecycle hooks.
+ ///
+ public static IHostApplicationBuilder AddExceptionless(this IHostApplicationBuilder builder, Action configure = null) {
+ builder.Services.AddExceptionless(builder.Configuration, configure);
+ builder.Services.AddExceptionlessLifetimeService();
+ return builder;
+ }
+
///
/// Adds the given pre-configured to the services collection as a singleton.
///
@@ -78,5 +114,16 @@ public static IServiceCollection AddExceptionless(this IServiceCollection servic
return client;
});
}
+
+ private static IServiceCollection AddExceptionlessLifetimeService(this IServiceCollection services) {
+ if (services.Any(descriptor => descriptor.ServiceType == typeof(ExceptionlessLifetimeService)))
+ return services;
+
+ services.AddSingleton();
+ services.AddSingleton(sp => sp.GetRequiredService());
+ services.AddSingleton(sp => sp.GetRequiredService());
+
+ return services;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/Platforms/Exceptionless.Extensions.Hosting/ExceptionlessLifetimeService.cs b/src/Platforms/Exceptionless.Extensions.Hosting/ExceptionlessLifetimeService.cs
index f7ea3a7d..5eaeffbc 100644
--- a/src/Platforms/Exceptionless.Extensions.Hosting/ExceptionlessLifetimeService.cs
+++ b/src/Platforms/Exceptionless.Extensions.Hosting/ExceptionlessLifetimeService.cs
@@ -3,23 +3,46 @@
using Microsoft.Extensions.Hosting;
namespace Exceptionless.Extensions.Hosting {
- public class ExceptionlessLifetimeService : IHostedService {
+ public sealed class ExceptionlessLifetimeService : IHostedLifecycleService {
private readonly ExceptionlessClient _exceptionlessClient;
+ private int _started;
- public ExceptionlessLifetimeService(ExceptionlessClient client, IHostApplicationLifetime appLifetime) {
+ public ExceptionlessLifetimeService(ExceptionlessClient client) {
_exceptionlessClient = client;
-
+ }
+
+ public Task StartingAsync(CancellationToken cancellationToken) {
+ if (Interlocked.Exchange(ref _started, 1) == 1)
+ return Task.CompletedTask;
+
_exceptionlessClient.RegisterAppDomainUnhandledExceptionHandler();
_exceptionlessClient.RegisterTaskSchedulerUnobservedTaskExceptionHandler();
-
- appLifetime.ApplicationStopping.Register(() => _exceptionlessClient.ProcessQueueAsync().ConfigureAwait(false).GetAwaiter().GetResult());
+ return Task.CompletedTask;
}
public Task StartAsync(CancellationToken cancellationToken) {
return Task.CompletedTask;
}
+ public Task StartedAsync(CancellationToken cancellationToken) {
+ return Task.CompletedTask;
+ }
+
+ public Task StoppingAsync(CancellationToken cancellationToken) {
+ if (Volatile.Read(ref _started) == 0)
+ return Task.CompletedTask;
+
+ return _exceptionlessClient.ProcessQueueAsync();
+ }
+
public Task StopAsync(CancellationToken cancellationToken) {
+ if (Interlocked.Exchange(ref _started, 0) == 0)
+ return Task.CompletedTask;
+
+ return _exceptionlessClient.ShutdownAsync();
+ }
+
+ public Task StoppedAsync(CancellationToken cancellationToken) {
return Task.CompletedTask;
}
}
diff --git a/src/Platforms/Exceptionless.Extensions.Hosting/readme.txt b/src/Platforms/Exceptionless.Extensions.Hosting/readme.txt
index 30d508a3..93c4b150 100644
--- a/src/Platforms/Exceptionless.Extensions.Hosting/readme.txt
+++ b/src/Platforms/Exceptionless.Extensions.Hosting/readme.txt
@@ -24,15 +24,17 @@ Please visit the documentation https://exceptionless.com/docs/clients/dotnet/pri
for detailed information on how to configure the client to meet your requirements.
-------------------------------------
-Microsoft.Extensions.Logging Integration
+Microsoft.Extensions.Hosting Integration
-------------------------------------
-You must import the "Exceptionless" namespace and call the following line
-of code to start reporting log messages.
+You must import the "Exceptionless" namespace and register Exceptionless on the
+host builder.
-loggerFactory.AddExceptionless("API_KEY_HERE");
+var builder = Host.CreateApplicationBuilder(args);
+builder.AddExceptionless(c => c.ApiKey = "API_KEY_HERE");
+builder.UseExceptionless();
-Alternatively, you can also use the different overloads of the AddExceptionless method
-for different configuration options.
+`AddExceptionless(...)` configures the client, and `UseExceptionless()` ensures
+the pending queue is flushed during host shutdown.
Please visit the documentation https://exceptionless.com/docs/clients/dotnet/sending-events/
for examples on sending events to Exceptionless.
@@ -40,4 +42,4 @@ for examples on sending events to Exceptionless.
-------------------------------------
Documentation and Support
-------------------------------------
-Please visit http://exceptionless.io for documentation and support.
\ No newline at end of file
+Please visit http://exceptionless.io for documentation and support.
diff --git a/src/Platforms/Exceptionless.Extensions.Logging/Exceptionless.Extensions.Logging.csproj b/src/Platforms/Exceptionless.Extensions.Logging/Exceptionless.Extensions.Logging.csproj
index bc48b3f6..71c28a68 100644
--- a/src/Platforms/Exceptionless.Extensions.Logging/Exceptionless.Extensions.Logging.csproj
+++ b/src/Platforms/Exceptionless.Extensions.Logging/Exceptionless.Extensions.Logging.csproj
@@ -7,15 +7,23 @@
Exceptionless provider for Microsoft.Extensions.Logging
Exceptionless provider for Microsoft.Extensions.Logging. $(Description)
$(PackageTags);Microsoft.Extensions.Logging
- netstandard2.0
+ net8.0;net9.0;net10.0
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Platforms/Exceptionless.Extensions.Logging/ExceptionlessLoggerExtensions.cs b/src/Platforms/Exceptionless.Extensions.Logging/ExceptionlessLoggerExtensions.cs
index e7212ae5..2446ffd1 100644
--- a/src/Platforms/Exceptionless.Extensions.Logging/ExceptionlessLoggerExtensions.cs
+++ b/src/Platforms/Exceptionless.Extensions.Logging/ExceptionlessLoggerExtensions.cs
@@ -1,8 +1,8 @@
using System;
using Exceptionless.Extensions.Logging;
using ExceptionlessLogLevel = Exceptionless.Logging.LogLevel;
-using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
namespace Exceptionless {
public static class ExceptionlessLoggerExtensions {
@@ -83,52 +83,5 @@ public static ILoggingBuilder AddExceptionless(this ILoggingBuilder builder, Act
return builder;
}
- ///
- /// Adds Exceptionless to the logging pipeline using the .
- ///
- /// The .
- /// If a client is not specified then the will be used.
- /// The .
- [Obsolete("Use ExceptionlessLoggerExtensions.AddExceptionless(ILoggingBuilder, ExceptionlessClient) instead.")]
- public static ILoggerFactory AddExceptionless(this ILoggerFactory factory, ExceptionlessClient client = null) {
- factory.AddProvider(new ExceptionlessLoggerProvider(client ?? ExceptionlessClient.Default));
- return factory;
- }
-
- ///
- /// Adds Exceptionless to the logging pipeline using a new client with the provided api key.
- ///
- /// The .
- /// The project api key.
- /// The Server Url
- /// The .
- [Obsolete("Use ExceptionlessLoggerExtensions.AddExceptionless(ILoggingBuilder, string, string) instead.")]
- public static ILoggerFactory AddExceptionless(this ILoggerFactory factory, string apiKey, string serverUrl = null) {
- if (String.IsNullOrEmpty(apiKey) && String.IsNullOrEmpty(serverUrl))
- return factory.AddExceptionless();
-
- factory.AddProvider(new ExceptionlessLoggerProvider(config => {
- if (!String.IsNullOrEmpty(apiKey) && apiKey != "API_KEY_HERE")
- config.ApiKey = apiKey;
- if (!String.IsNullOrEmpty(serverUrl))
- config.ServerUrl = serverUrl;
-
- config.UseInMemoryStorage();
- }));
-
- return factory;
- }
-
- ///
- /// Adds Exceptionless to the logging pipeline using a new client configured with the provided action.
- ///
- /// The .
- /// An that applies additional settings and plugins. The project api key must be specified.
- /// The .
- [Obsolete("Use ExceptionlessLoggerExtensions.AddExceptionless(ILoggingBuilder, Action) instead.")]
- public static ILoggerFactory AddExceptionless(this ILoggerFactory factory, Action configure) {
- factory.AddProvider(new ExceptionlessLoggerProvider(configure));
- return factory;
- }
}
}
\ No newline at end of file
diff --git a/src/Platforms/Exceptionless.Extensions.Logging/readme.txt b/src/Platforms/Exceptionless.Extensions.Logging/readme.txt
index 30d508a3..b695b6ab 100644
--- a/src/Platforms/Exceptionless.Extensions.Logging/readme.txt
+++ b/src/Platforms/Exceptionless.Extensions.Logging/readme.txt
@@ -26,13 +26,16 @@ for detailed information on how to configure the client to meet your requirement
-------------------------------------
Microsoft.Extensions.Logging Integration
-------------------------------------
-You must import the "Exceptionless" namespace and call the following line
-of code to start reporting log messages.
+You must import the "Exceptionless" namespace and add Exceptionless to the
+logging builder.
-loggerFactory.AddExceptionless("API_KEY_HERE");
+var builder = Host.CreateApplicationBuilder(args);
+builder.Logging.AddExceptionless();
-Alternatively, you can also use the different overloads of the AddExceptionless method
-for different configuration options.
+If you want to configure the client in the same app, pair this with one of the
+Exceptionless hosting registration overloads such as:
+
+builder.AddExceptionless(c => c.ApiKey = "API_KEY_HERE");
Please visit the documentation https://exceptionless.com/docs/clients/dotnet/sending-events/
for examples on sending events to Exceptionless.
@@ -40,4 +43,4 @@ for examples on sending events to Exceptionless.
-------------------------------------
Documentation and Support
-------------------------------------
-Please visit http://exceptionless.io for documentation and support.
\ No newline at end of file
+Please visit http://exceptionless.io for documentation and support.
diff --git a/src/Platforms/Exceptionless.Log4net/Exceptionless.Log4net.csproj b/src/Platforms/Exceptionless.Log4net/Exceptionless.Log4net.csproj
index 0619fe5b..36852aa6 100644
--- a/src/Platforms/Exceptionless.Log4net/Exceptionless.Log4net.csproj
+++ b/src/Platforms/Exceptionless.Log4net/Exceptionless.Log4net.csproj
@@ -25,7 +25,7 @@
-
+
@@ -41,4 +41,4 @@
-
\ No newline at end of file
+
diff --git a/src/Platforms/Exceptionless.MessagePack/DataDictionaryFormatter.cs b/src/Platforms/Exceptionless.MessagePack/DataDictionaryFormatter.cs
index ee064046..e31fd03f 100644
--- a/src/Platforms/Exceptionless.MessagePack/DataDictionaryFormatter.cs
+++ b/src/Platforms/Exceptionless.MessagePack/DataDictionaryFormatter.cs
@@ -5,8 +5,8 @@
using MessagePack.Formatters;
namespace Exceptionless.MessagePack {
- internal class DataDictionaryFormatter : IMessagePackFormatter {
- public void Serialize(ref MessagePackWriter writer, DataDictionary value, MessagePackSerializerOptions options) {
+ internal class DataDictionaryFormatter : IMessagePackFormatter {
+ public void Serialize(ref MessagePackWriter writer, DataDictionary? value, MessagePackSerializerOptions options) {
if (value == null) {
writer.WriteNil();
return;
@@ -65,7 +65,7 @@ public void Serialize(ref MessagePackWriter writer, DataDictionary value, Messag
}
}
- public DataDictionary Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) {
+ public DataDictionary? Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) {
if (reader.IsNil)
return null;
diff --git a/src/Platforms/Exceptionless.MessagePack/Exceptionless.MessagePack.csproj b/src/Platforms/Exceptionless.MessagePack/Exceptionless.MessagePack.csproj
index 0fb0c46f..4d1572db 100644
--- a/src/Platforms/Exceptionless.MessagePack/Exceptionless.MessagePack.csproj
+++ b/src/Platforms/Exceptionless.MessagePack/Exceptionless.MessagePack.csproj
@@ -25,6 +25,6 @@
-
+
diff --git a/src/Platforms/Exceptionless.MessagePack/PersistedDictionaryFormatter.cs b/src/Platforms/Exceptionless.MessagePack/PersistedDictionaryFormatter.cs
index faa096cf..38fae764 100644
--- a/src/Platforms/Exceptionless.MessagePack/PersistedDictionaryFormatter.cs
+++ b/src/Platforms/Exceptionless.MessagePack/PersistedDictionaryFormatter.cs
@@ -7,6 +7,8 @@ namespace Exceptionless.MessagePack {
internal class PersistedDictionaryFormatter : DictionaryFormatterBase {
private readonly IDependencyResolver _resolver;
+ internal PersistedDictionaryFormatter() : this(DependencyResolver.Default) { }
+
public PersistedDictionaryFormatter(IDependencyResolver resolver) {
_resolver = resolver;
}
diff --git a/src/Platforms/Exceptionless.NLog/Exceptionless.NLog.csproj b/src/Platforms/Exceptionless.NLog/Exceptionless.NLog.csproj
index 40c52d5a..33727c35 100644
--- a/src/Platforms/Exceptionless.NLog/Exceptionless.NLog.csproj
+++ b/src/Platforms/Exceptionless.NLog/Exceptionless.NLog.csproj
@@ -25,7 +25,7 @@
-
+
@@ -40,4 +40,4 @@
-
\ No newline at end of file
+
diff --git a/src/Platforms/Exceptionless.NLog/ExceptionlessField.cs b/src/Platforms/Exceptionless.NLog/ExceptionlessField.cs
index a5eecdd9..3ac848e4 100644
--- a/src/Platforms/Exceptionless.NLog/ExceptionlessField.cs
+++ b/src/Platforms/Exceptionless.NLog/ExceptionlessField.cs
@@ -4,10 +4,8 @@
namespace Exceptionless.NLog {
[NLogConfigurationItem]
public class ExceptionlessField {
- [RequiredParameter]
public string Name { get; set; }
- [RequiredParameter]
public Layout Layout { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Platforms/Exceptionless.NLog/ExceptionlessTarget.cs b/src/Platforms/Exceptionless.NLog/ExceptionlessTarget.cs
index 83c7128c..e0a085de 100644
--- a/src/Platforms/Exceptionless.NLog/ExceptionlessTarget.cs
+++ b/src/Platforms/Exceptionless.NLog/ExceptionlessTarget.cs
@@ -28,6 +28,19 @@ public ExceptionlessTarget() {
protected override void InitializeTarget() {
base.InitializeTarget();
+ if (Fields != null) {
+ foreach (var field in Fields) {
+ if (field == null)
+ throw new NLogConfigurationException("Exceptionless field configuration cannot be null.");
+
+ if (String.IsNullOrWhiteSpace(field.Name))
+ throw new NLogConfigurationException("Exceptionless field name is required.");
+
+ if (field.Layout == null)
+ throw new NLogConfigurationException($"Exceptionless field '{field.Name}' must define a layout.");
+ }
+ }
+
string apiKey = RenderLogEvent(ApiKey, LogEventInfo.CreateNullEvent());
string serverUrl = RenderLogEvent(ServerUrl, LogEventInfo.CreateNullEvent());
@@ -69,10 +82,12 @@ protected override void Write(LogEventInfo logEvent) {
string userIdentityName = RenderLogEvent(UserIdentityName, logEvent);
builder.Target.SetUserIdentity(userIdentity, userIdentityName);
- foreach (var field in Fields) {
- string renderedField = RenderLogEvent(field.Layout, logEvent);
- if (!String.IsNullOrWhiteSpace(renderedField))
- builder.AddObject(renderedField, field.Name);
+ if (Fields != null) {
+ foreach (var field in Fields) {
+ string renderedField = RenderLogEvent(field.Layout, logEvent);
+ if (!String.IsNullOrWhiteSpace(renderedField))
+ builder.AddObject(renderedField, field.Name);
+ }
}
builder.Submit();
diff --git a/src/Platforms/Exceptionless.Web/RequestInfoCollector.cs b/src/Platforms/Exceptionless.Web/RequestInfoCollector.cs
index dd864dd8..575447dc 100644
--- a/src/Platforms/Exceptionless.Web/RequestInfoCollector.cs
+++ b/src/Platforms/Exceptionless.Web/RequestInfoCollector.cs
@@ -54,7 +54,7 @@ public static RequestInfo Collect(HttpContextBase context, ExceptionlessConfigur
if (config.IncludeCookies)
info.Cookies = context.Request.Cookies.ToDictionary(exclusionList);
-
+
if (config.IncludeQueryString) {
try {
info.QueryString = context.Request.QueryString.ToDictionary(exclusionList);
@@ -124,7 +124,7 @@ private static object GetPostData(HttpContextBase context, ExceptionlessConfigur
int numRead;
int bufferSize = Math.Min(1024, maxDataToRead);
-
+
char[] buffer = new char[bufferSize];
while ((numRead = inputStream.ReadBlock(buffer, 0, bufferSize)) > 0 && (sb.Length + numRead) < maxDataToRead) {
sb.Append(buffer, 0, numRead);
@@ -210,7 +210,7 @@ private static Dictionary ToDictionary(this HttpCookieCollection
private static Dictionary ToDictionary(this NameValueCollection values, string[] exclusions) {
var d = new Dictionary();
-
+
foreach (string key in values.AllKeys) {
if (String.IsNullOrEmpty(key) || key.AnyWildcardMatches(_ignoredFormFields) || key.AnyWildcardMatches(exclusions))
continue;
@@ -237,4 +237,4 @@ private static string GetUserIpAddress(HttpContextBase context) {
return clientIp;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Platforms/Exceptionless.WebApi/RequestInfoCollector.cs b/src/Platforms/Exceptionless.WebApi/RequestInfoCollector.cs
index b3f5b521..324a31a9 100644
--- a/src/Platforms/Exceptionless.WebApi/RequestInfoCollector.cs
+++ b/src/Platforms/Exceptionless.WebApi/RequestInfoCollector.cs
@@ -78,7 +78,7 @@ public static RequestInfo Collect(HttpActionContext context, ExceptionlessConfig
private static readonly List _ignoredFormFields = new List {
"__*"
};
-
+
private static Dictionary ToDictionary(this HttpRequestHeaders headers, string[] exclusions) {
var d = new Dictionary();
@@ -119,7 +119,7 @@ private static Dictionary ToDictionary(this IEnumerable ToDictionary(this NameValueCollection values, string[] exclusions) {
var d = new Dictionary();
-
+
foreach (string key in values.AllKeys) {
if (String.IsNullOrEmpty(key) || key.AnyWildcardMatches(_ignoredFormFields) || key.AnyWildcardMatches(exclusions))
continue;
diff --git a/test/Exceptionless.MessagePack.Tests/Exceptionless.MessagePack.Tests.csproj b/test/Exceptionless.MessagePack.Tests/Exceptionless.MessagePack.Tests.csproj
index e5f125f4..19d5566e 100644
--- a/test/Exceptionless.MessagePack.Tests/Exceptionless.MessagePack.Tests.csproj
+++ b/test/Exceptionless.MessagePack.Tests/Exceptionless.MessagePack.Tests.csproj
@@ -2,22 +2,24 @@
-net8.0
+net10.0
-net8.0;net462
+net10.0;net472
false
true
+ Exe
true
Exceptionless.MessagePack.Tests
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -29,21 +31,21 @@
-
+
$(DefineConstants);NETSTANDARD;NETSTANDARD2_0
-
+
$(DefineConstants);NET45
-
+
-
-
+
+
-
\ No newline at end of file
+
diff --git a/test/Exceptionless.TestHarness/Exceptionless.TestHarness.csproj b/test/Exceptionless.TestHarness/Exceptionless.TestHarness.csproj
index 0c752883..e54a4a95 100644
--- a/test/Exceptionless.TestHarness/Exceptionless.TestHarness.csproj
+++ b/test/Exceptionless.TestHarness/Exceptionless.TestHarness.csproj
@@ -2,10 +2,10 @@
- netstandard2.0
+ net10.0
- netstandard2.0;net462
+ net10.0;net472
@@ -16,15 +16,22 @@
+
+
+
+
+
+
+
+
-
-
+
$(DefineConstants);NETSTANDARD;NETSTANDARD2_0
-
+
$(DefineConstants);NET45
diff --git a/test/Exceptionless.Tests/Configuration/ConfigurationTests.cs b/test/Exceptionless.Tests/Configuration/ConfigurationTests.cs
index 3cd35cb4..8a338502 100644
--- a/test/Exceptionless.Tests/Configuration/ConfigurationTests.cs
+++ b/test/Exceptionless.Tests/Configuration/ConfigurationTests.cs
@@ -13,7 +13,6 @@
using Exceptionless.Tests.Utility;
using Moq;
using Xunit;
-using Xunit.Abstractions;
[assembly: Exceptionless("LhhP1C9gijpSKCslHHCvwdSIz298twx271nTest", ServerUrl = "http://localhost:5200")]
[assembly: ExceptionlessSetting("testing", "configuration")]
diff --git a/test/Exceptionless.Tests/EventBuilderTests.cs b/test/Exceptionless.Tests/EventBuilderTests.cs
index dcda2119..713b938e 100644
--- a/test/Exceptionless.Tests/EventBuilderTests.cs
+++ b/test/Exceptionless.Tests/EventBuilderTests.cs
@@ -2,7 +2,6 @@
using Exceptionless.Tests.Log;
using Exceptionless.Tests.Utility;
using Xunit;
-using Xunit.Abstractions;
using LogLevel = Exceptionless.Logging.LogLevel;
namespace Exceptionless.Tests {
@@ -38,4 +37,4 @@ public void CanCreateEventWithNoDuplicateTags() {
Assert.Equal(2, builder.Target.Tags.Count);
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Exceptionless.Tests.csproj b/test/Exceptionless.Tests/Exceptionless.Tests.csproj
index f4f37a35..d89c6884 100644
--- a/test/Exceptionless.Tests/Exceptionless.Tests.csproj
+++ b/test/Exceptionless.Tests/Exceptionless.Tests.csproj
@@ -1,16 +1,17 @@
-
+
net10.0
-net10.0;net462
+net10.0;net472
false
true
+ Exe
true
Exceptionless.Tests
@@ -23,28 +24,31 @@
$(DefineConstants);NETSTANDARD;NETSTANDARD2_0
-
+
$(DefineConstants);NET45
-
+
+
+
+
-
-
-
+
+
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
@@ -56,4 +60,4 @@
PreserveNewest
-
+
\ No newline at end of file
diff --git a/test/Exceptionless.Tests/ExceptionlessClientTests.cs b/test/Exceptionless.Tests/ExceptionlessClientTests.cs
index 8d03536d..d88a1b6d 100644
--- a/test/Exceptionless.Tests/ExceptionlessClientTests.cs
+++ b/test/Exceptionless.Tests/ExceptionlessClientTests.cs
@@ -12,7 +12,6 @@
using Exceptionless.Tests.Log;
using Exceptionless.Tests.Utility;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests {
public class ExceptionlessClientTests {
diff --git a/test/Exceptionless.Tests/Platforms/AspNetCoreExceptionCaptureTests.cs b/test/Exceptionless.Tests/Platforms/AspNetCoreExceptionCaptureTests.cs
new file mode 100644
index 00000000..c8fb21f6
--- /dev/null
+++ b/test/Exceptionless.Tests/Platforms/AspNetCoreExceptionCaptureTests.cs
@@ -0,0 +1,167 @@
+#if NET10_0_OR_GREATER
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Exceptionless.AspNetCore;
+using Exceptionless.Models;
+using Exceptionless.Models.Data;
+using Microsoft.AspNetCore.Http;
+using Xunit;
+
+namespace Exceptionless.Tests.Platforms {
+ public class AspNetCoreExceptionCaptureTests {
+ [Fact]
+ public async Task TryHandleAsync_WhenRequestIsActive_ReturnsFalseAndCapturesUnhandledException() {
+ // Arrange
+ var submittingEvents = new List();
+ var client = CreateClient(submittingEvents);
+ var context = CreateHttpContext();
+ var exception = new InvalidOperationException("unhandled");
+ var handler = new ExceptionlessExceptionHandler(client);
+
+ // Act
+ var result = await handler.TryHandleAsync(context, exception, CancellationToken.None);
+
+ // Assert
+ Assert.False(result);
+ var submission = Assert.Single(submittingEvents);
+ Assert.True(submission.IsUnhandledError);
+ Assert.Equal(nameof(ExceptionlessExceptionHandler), submission.Event.Data[Event.KnownDataKeys.SubmissionMethod]);
+ }
+
+ [Fact]
+ public async Task TryHandleAsync_WhenCancellationIsRequested_ReturnsFalseWithoutCapturingException() {
+ // Arrange
+ var submittingEvents = new List();
+ var client = CreateClient(submittingEvents);
+ var cts = new CancellationTokenSource();
+ var context = CreateHttpContext();
+ cts.Cancel();
+ var handler = new ExceptionlessExceptionHandler(client);
+
+ // Act
+ var result = await handler.TryHandleAsync(context, new InvalidOperationException(), cts.Token);
+
+ // Assert
+ Assert.False(result);
+ Assert.Empty(submittingEvents);
+ }
+
+ [Fact]
+ public void OnNext_WhenUnhandledExceptionEventIsPublished_CapturesUnhandledException() {
+ // Arrange
+ var submittingEvents = new List();
+ var client = CreateClient(submittingEvents);
+ var context = CreateHttpContext();
+ var exception = new InvalidOperationException("unhandled");
+ var listener = new ExceptionlessDiagnosticListener(client);
+
+ // Act
+ listener.OnNext(new KeyValuePair("Microsoft.AspNetCore.Hosting.UnhandledException", new {
+ httpContext = context,
+ exception
+ }));
+
+ // Assert
+ var submission = Assert.Single(submittingEvents);
+ Assert.True(submission.IsUnhandledError);
+ }
+
+ [Fact]
+ public void OnNext_WhenHostingDiagnosticsUnhandledExceptionEventIsPublished_CapturesUnhandledException() {
+ // Arrange
+ var submittingEvents = new List();
+ var client = CreateClient(submittingEvents);
+ var context = CreateHttpContext();
+ var exception = new InvalidOperationException("unhandled");
+ var listener = new ExceptionlessDiagnosticListener(client);
+
+ // Act
+ listener.OnNext(new KeyValuePair("Microsoft.AspNetCore.Hosting.Diagnostics.UnhandledException", new {
+ httpContext = context,
+ exception
+ }));
+
+ // Assert
+ var submission = Assert.Single(submittingEvents);
+ Assert.True(submission.IsUnhandledError);
+ }
+
+ [Fact]
+ public void OnNext_WhenMiddlewareExceptionPayloadIsNull_DoesNotThrowOrCaptureException() {
+ // Arrange
+ var submittingEvents = new List();
+ var client = CreateClient(submittingEvents);
+ var listener = new ExceptionlessDiagnosticListener(client);
+
+ // Act
+ listener.OnNext(new KeyValuePair("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException", null));
+
+ // Assert
+ Assert.Empty(submittingEvents);
+ }
+
+ [Fact]
+ public async Task Invoke_WhenResponseStatusIsNotFound_SubmitsNotFoundEvent() {
+ // Arrange
+ var submittingEvents = new List();
+ var client = CreateClient(submittingEvents);
+ var context = CreateHttpContext();
+ var middleware = new ExceptionlessMiddleware(currentContext => {
+ currentContext.Response.StatusCode = 404;
+ return Task.CompletedTask;
+ }, client);
+
+ // Act
+ await middleware.Invoke(context);
+
+ // Assert
+ var submission = Assert.Single(submittingEvents);
+ Assert.Equal(Event.KnownTypes.NotFound, submission.Event.Type);
+ }
+
+ [Fact]
+ public async Task Invoke_WhenNextDelegateThrows_RethrowsExceptionWithoutSubmittingEvent() {
+ // Arrange
+ var submittingEvents = new List();
+ var client = CreateClient(submittingEvents);
+ var context = CreateHttpContext();
+ var middleware = new ExceptionlessMiddleware(_ => throw new InvalidOperationException("boom"), client);
+
+ // Act
+ await Assert.ThrowsAsync(() => middleware.Invoke(context));
+
+ // Assert
+ Assert.Empty(submittingEvents);
+ }
+
+ private static ExceptionlessClient CreateClient(ICollection submittingEvents) {
+ var client = new ExceptionlessClient(configuration => {
+ configuration.ApiKey = "test-api-key";
+ configuration.ServerUrl = "http://localhost:5200";
+ configuration.UpdateSettingsWhenIdleInterval = TimeSpan.Zero;
+ configuration.UseInMemoryStorage();
+ });
+
+ client.Configuration.AddPlugin(new ExceptionlessAspNetCorePlugin(null));
+ client.SubmittingEvent += (_, args) => submittingEvents.Add(args);
+
+ return client;
+ }
+
+ private static DefaultHttpContext CreateHttpContext() {
+ const string body = "{\"hello\":\"world\"}";
+ var bodyBytes = System.Text.Encoding.UTF8.GetBytes(body);
+ var context = new DefaultHttpContext();
+ context.Request.Method = HttpMethods.Post;
+ context.Request.ContentType = "application/json";
+ context.Request.ContentLength = bodyBytes.Length;
+ context.Request.Body = new MemoryStream(bodyBytes);
+ context.Response.Body = new MemoryStream();
+ return context;
+ }
+ }
+}
+#endif
diff --git a/test/Exceptionless.Tests/Platforms/AspNetCoreExtensionsTests.cs b/test/Exceptionless.Tests/Platforms/AspNetCoreExtensionsTests.cs
new file mode 100644
index 00000000..b3a064f3
--- /dev/null
+++ b/test/Exceptionless.Tests/Platforms/AspNetCoreExtensionsTests.cs
@@ -0,0 +1,43 @@
+#if NET10_0_OR_GREATER
+using Exceptionless;
+using Exceptionless.AspNetCore;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Diagnostics;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Exceptionless.Tests.Platforms {
+ public class AspNetCoreExtensionsTests {
+ [Fact]
+ public void AddExceptionless_WhenCalled_RegistersAspNetCoreServices() {
+ // Arrange
+ var builder = WebApplication.CreateBuilder();
+
+ // Act
+ builder.Services.AddExceptionless();
+
+ // Assert
+ Assert.Contains(builder.Services, descriptor => descriptor.ServiceType == typeof(IHttpContextAccessor));
+ Assert.Contains(builder.Services, descriptor =>
+ descriptor.ServiceType == typeof(IExceptionHandler) &&
+ descriptor.ImplementationType == typeof(ExceptionlessExceptionHandler));
+ }
+
+ [Fact]
+ public void AddExceptionless_WhenCalledTwice_DoesNotRegisterDuplicateExceptionHandlers() {
+ // Arrange
+ var builder = WebApplication.CreateBuilder();
+
+ // Act
+ builder.Services.AddExceptionless();
+ builder.Services.AddExceptionless();
+
+ // Assert
+ Assert.Single(builder.Services, descriptor =>
+ descriptor.ServiceType == typeof(IExceptionHandler) &&
+ descriptor.ImplementationType == typeof(ExceptionlessExceptionHandler));
+ }
+ }
+}
+#endif
diff --git a/test/Exceptionless.Tests/Platforms/AspNetCoreRequestInfoTests.cs b/test/Exceptionless.Tests/Platforms/AspNetCoreRequestInfoTests.cs
index 599c87db..40ada7f3 100644
--- a/test/Exceptionless.Tests/Platforms/AspNetCoreRequestInfoTests.cs
+++ b/test/Exceptionless.Tests/Platforms/AspNetCoreRequestInfoTests.cs
@@ -1,3 +1,4 @@
+#if NET10_0_OR_GREATER
using System.Collections.Generic;
using System.IO;
using System.Text;
@@ -10,7 +11,7 @@
namespace Exceptionless.Tests.Platforms {
public class AspNetCoreRequestInfoTests {
[Fact]
- public void GetRequestInfo_DoesNotReadPostData_ForHandledErrors() {
+ public void GetRequestInfo_WhenErrorIsHandled_DoesNotReadPostData() {
// Arrange
var context = CreateHttpContext("hello=world");
var config = new ExceptionlessConfiguration(DependencyResolver.CreateDefault());
@@ -25,7 +26,7 @@ public void GetRequestInfo_DoesNotReadPostData_ForHandledErrors() {
}
[Fact]
- public void GetRequestInfo_ReadsAndRestoresPostData_ForUnhandledErrors() {
+ public void GetRequestInfo_WhenErrorIsUnhandled_ReadsAndRestoresPostData() {
// Arrange
const string body = "{\"hello\":\"world\"}";
var context = CreateHttpContext(body);
@@ -43,7 +44,7 @@ public void GetRequestInfo_ReadsAndRestoresPostData_ForUnhandledErrors() {
}
[Fact]
- public void GetRequestInfo_ReadsFormData_ForUnhandledErrors() {
+ public void GetRequestInfo_WhenUnhandledRequestContainsFormData_ReadsFormData() {
// Arrange
var context = CreateFormHttpContext();
var config = new ExceptionlessConfiguration(DependencyResolver.CreateDefault());
@@ -82,3 +83,4 @@ private static DefaultHttpContext CreateFormHttpContext() {
}
}
}
+#endif
diff --git a/test/Exceptionless.Tests/Platforms/HostingExtensionsTests.cs b/test/Exceptionless.Tests/Platforms/HostingExtensionsTests.cs
new file mode 100644
index 00000000..6a783446
--- /dev/null
+++ b/test/Exceptionless.Tests/Platforms/HostingExtensionsTests.cs
@@ -0,0 +1,49 @@
+#if NET10_0_OR_GREATER
+using System.Linq;
+using Exceptionless.Extensions.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+
+namespace Exceptionless.Tests.Platforms {
+ public class HostingExtensionsTests {
+ [Fact]
+ public void AddExceptionless_WhenCalled_RegistersClientAndLifetimeService() {
+ // Arrange
+ var builder = Host.CreateApplicationBuilder();
+
+ // Act
+ builder.AddExceptionless(configuration => configuration.ApiKey = "test-api-key");
+
+ // Assert
+ Assert.Contains(builder.Services, descriptor => descriptor.ServiceType == typeof(ExceptionlessClient));
+ Assert.Single(builder.Services, descriptor => descriptor.ServiceType == typeof(ExceptionlessLifetimeService));
+ Assert.Single(builder.Services, descriptor => descriptor.ServiceType == typeof(IHostedService));
+ Assert.Single(builder.Services, descriptor => descriptor.ServiceType == typeof(IHostedLifecycleService));
+
+ using var serviceProvider = builder.Services.BuildServiceProvider();
+ Assert.Same(
+ serviceProvider.GetRequiredService(),
+ serviceProvider.GetRequiredService());
+ Assert.Same(
+ serviceProvider.GetRequiredService(),
+ serviceProvider.GetRequiredService());
+ }
+
+ [Fact]
+ public void UseExceptionless_WhenCalledTwice_DoesNotRegisterDuplicateLifetimeServices() {
+ // Arrange
+ var builder = Host.CreateApplicationBuilder();
+
+ // Act
+ builder.UseExceptionless();
+ builder.UseExceptionless();
+
+ // Assert
+ Assert.Single(builder.Services, descriptor => descriptor.ServiceType == typeof(ExceptionlessLifetimeService));
+ Assert.Single(builder.Services, descriptor => descriptor.ServiceType == typeof(IHostedService));
+ Assert.Single(builder.Services, descriptor => descriptor.ServiceType == typeof(IHostedLifecycleService));
+ }
+ }
+}
+#endif
diff --git a/test/Exceptionless.Tests/Plugins/005_HandleAggregateExceptionsPluginTests.cs b/test/Exceptionless.Tests/Plugins/005_HandleAggregateExceptionsPluginTests.cs
index 05f9d670..0f05689c 100644
--- a/test/Exceptionless.Tests/Plugins/005_HandleAggregateExceptionsPluginTests.cs
+++ b/test/Exceptionless.Tests/Plugins/005_HandleAggregateExceptionsPluginTests.cs
@@ -7,7 +7,6 @@
using Exceptionless.Submission;
using Exceptionless.Tests.Utility;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Plugins {
public class HandleAggregateExceptionsPluginTests : PluginTestBase {
@@ -57,4 +56,4 @@ public async Task MultipleInnerException() {
Assert.Equal(2, submissionClient.Events.Count);
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Plugins/010_EventExclusionPluginTests.cs b/test/Exceptionless.Tests/Plugins/010_EventExclusionPluginTests.cs
index 5701f501..8c23072e 100644
--- a/test/Exceptionless.Tests/Plugins/010_EventExclusionPluginTests.cs
+++ b/test/Exceptionless.Tests/Plugins/010_EventExclusionPluginTests.cs
@@ -3,7 +3,6 @@
using Exceptionless.Plugins.Default;
using Exceptionless.Models;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Plugins {
public class EventExclusionPluginTests : PluginTestBase {
@@ -138,4 +137,4 @@ public void ExceptionType(string? settingKey, bool cancelled) {
Assert.Equal(cancelled, context.Cancel);
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Plugins/015_ConfigurationDefaultsPluginTests.cs b/test/Exceptionless.Tests/Plugins/015_ConfigurationDefaultsPluginTests.cs
index 890d61b8..5d6da120 100644
--- a/test/Exceptionless.Tests/Plugins/015_ConfigurationDefaultsPluginTests.cs
+++ b/test/Exceptionless.Tests/Plugins/015_ConfigurationDefaultsPluginTests.cs
@@ -5,7 +5,6 @@
using Exceptionless.Models;
using Exceptionless.Models.Data;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Plugins {
public class ConfigurationDefaultsPluginTests : PluginTestBase {
@@ -100,4 +99,4 @@ public void SerializedProperties() {
Assert.Equal("blake", context.Event.GetUserIdentity(serializer).Identity);
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Plugins/016_SetEnvironmentUserPluginTests.cs b/test/Exceptionless.Tests/Plugins/016_SetEnvironmentUserPluginTests.cs
index 2f497196..d97ec1b7 100644
--- a/test/Exceptionless.Tests/Plugins/016_SetEnvironmentUserPluginTests.cs
+++ b/test/Exceptionless.Tests/Plugins/016_SetEnvironmentUserPluginTests.cs
@@ -3,7 +3,6 @@
using Exceptionless.Plugins.Default;
using Exceptionless.Models;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Plugins {
public class SetEnvironmentUserPluginTests : PluginTestBase {
@@ -47,4 +46,4 @@ public void WillNotUpdateIdentity() {
Assert.Equal("Blake", user.Name);
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Plugins/020_ErrorPluginTests.cs b/test/Exceptionless.Tests/Plugins/020_ErrorPluginTests.cs
index a453e75a..e0610a50 100644
--- a/test/Exceptionless.Tests/Plugins/020_ErrorPluginTests.cs
+++ b/test/Exceptionless.Tests/Plugins/020_ErrorPluginTests.cs
@@ -6,7 +6,6 @@
using Exceptionless.Models;
using Exceptionless.Models.Data;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Plugins {
public class ErrorPluginTests : PluginTestBase {
@@ -307,4 +306,4 @@ public void VerifyExceptionHResultIsMappedToErrorCode() {
Assert.Equal(unchecked((int)0x8007000E).ToString(), error.Code);
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Plugins/040_ReferenceIdPluginTests.cs b/test/Exceptionless.Tests/Plugins/040_ReferenceIdPluginTests.cs
index 3af6401a..207ed1c3 100644
--- a/test/Exceptionless.Tests/Plugins/040_ReferenceIdPluginTests.cs
+++ b/test/Exceptionless.Tests/Plugins/040_ReferenceIdPluginTests.cs
@@ -1,7 +1,6 @@
using Exceptionless.Plugins;
using Exceptionless.Models;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Plugins {
public class ReferenceIdPluginTests : PluginTestBase {
@@ -23,4 +22,4 @@ public void ShouldUseReferenceIds() {
Assert.NotNull(context.Event.ReferenceId);
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Plugins/050_EnvironmentInfoPluginTests.cs b/test/Exceptionless.Tests/Plugins/050_EnvironmentInfoPluginTests.cs
index 61f9f8b3..e2cf4197 100644
--- a/test/Exceptionless.Tests/Plugins/050_EnvironmentInfoPluginTests.cs
+++ b/test/Exceptionless.Tests/Plugins/050_EnvironmentInfoPluginTests.cs
@@ -3,7 +3,6 @@
using Exceptionless.Plugins.Default;
using Exceptionless.Models;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Plugins {
public class EnvironmentInfoPluginTests : PluginTestBase {
@@ -34,4 +33,4 @@ public void ShouldAddSessionStart() {
Assert.NotNull(context.Event.Data[Event.KnownDataKeys.EnvironmentInfo]);
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Plugins/110_IgnoreUserAgentPluginTests.cs b/test/Exceptionless.Tests/Plugins/110_IgnoreUserAgentPluginTests.cs
index 2986cf2e..e5e35587 100644
--- a/test/Exceptionless.Tests/Plugins/110_IgnoreUserAgentPluginTests.cs
+++ b/test/Exceptionless.Tests/Plugins/110_IgnoreUserAgentPluginTests.cs
@@ -3,7 +3,6 @@
using Exceptionless.Models;
using Exceptionless.Models.Data;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Plugins {
public class IgnoreUserAgentPluginTests : PluginTestBase {
@@ -31,4 +30,4 @@ public void DiscardBot() {
Assert.True(context.Cancel);
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Plugins/900_CancelSessionsWithNoUserPluginTests.cs b/test/Exceptionless.Tests/Plugins/900_CancelSessionsWithNoUserPluginTests.cs
index a7800582..eff4c446 100644
--- a/test/Exceptionless.Tests/Plugins/900_CancelSessionsWithNoUserPluginTests.cs
+++ b/test/Exceptionless.Tests/Plugins/900_CancelSessionsWithNoUserPluginTests.cs
@@ -2,7 +2,6 @@
using Exceptionless.Plugins.Default;
using Exceptionless.Models;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Plugins {
public class CancelSessionsWithNoUserPluginTests : PluginTestBase {
@@ -25,4 +24,4 @@ public void CancelSessionsWithNoUserTest(string eventType, string? identity, boo
Assert.Equal(cancelled, context.Cancel);
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Plugins/910_DuplicateCheckerPluginTests.cs b/test/Exceptionless.Tests/Plugins/910_DuplicateCheckerPluginTests.cs
index 04245e54..ef479fc0 100644
--- a/test/Exceptionless.Tests/Plugins/910_DuplicateCheckerPluginTests.cs
+++ b/test/Exceptionless.Tests/Plugins/910_DuplicateCheckerPluginTests.cs
@@ -8,7 +8,6 @@
using Exceptionless.Models;
using Exceptionless.Tests.Utility;
using Xunit;
-using Xunit.Abstractions;
using Exceptionless.Json;
using Exceptionless.Extensions;
@@ -137,4 +136,4 @@ public void VerifyDeduplicationFromFiles() {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Plugins/PluginTestBase.cs b/test/Exceptionless.Tests/Plugins/PluginTestBase.cs
index 1e2de303..5f8f48f8 100644
--- a/test/Exceptionless.Tests/Plugins/PluginTestBase.cs
+++ b/test/Exceptionless.Tests/Plugins/PluginTestBase.cs
@@ -3,7 +3,7 @@
using Exceptionless.Logging;
using Exceptionless.Tests.Log;
using Exceptionless.Tests.Utility;
-using Xunit.Abstractions;
+using Xunit;
namespace Exceptionless.Tests.Plugins
{
@@ -91,4 +91,4 @@ public enum ErrorCategory {
SecondErrorBucket,
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Plugins/PluginTests.cs b/test/Exceptionless.Tests/Plugins/PluginTests.cs
index aa3a2dd2..d9f00ccc 100644
--- a/test/Exceptionless.Tests/Plugins/PluginTests.cs
+++ b/test/Exceptionless.Tests/Plugins/PluginTests.cs
@@ -7,7 +7,6 @@
using Exceptionless.Plugins.Default;
using Exceptionless.Models;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Plugins {
public class PluginTests : PluginTestBase {
@@ -163,4 +162,4 @@ private class PluginWithPriority11 : IEventPlugin {
public void Run(EventPluginContext context) {}
}
}
-}
\ No newline at end of file
+}
diff --git a/test/Exceptionless.Tests/Serializer/JsonSerializerTests.cs b/test/Exceptionless.Tests/Serializer/JsonSerializerTests.cs
index e33a81fa..7994b7bf 100644
--- a/test/Exceptionless.Tests/Serializer/JsonSerializerTests.cs
+++ b/test/Exceptionless.Tests/Serializer/JsonSerializerTests.cs
@@ -10,7 +10,6 @@
using Exceptionless.Serializer;
using Exceptionless.Tests.Log;
using Exceptionless.Tests.Utility;
-using Xunit.Abstractions;
using LogLevel = Exceptionless.Logging.LogLevel;
using Module = Exceptionless.Models.Data.Module;
diff --git a/test/Exceptionless.Tests/Storage/FileStorageTestsBase.cs b/test/Exceptionless.Tests/Storage/FileStorageTestsBase.cs
index 4fbf88fa..a4b6bbc1 100644
--- a/test/Exceptionless.Tests/Storage/FileStorageTestsBase.cs
+++ b/test/Exceptionless.Tests/Storage/FileStorageTestsBase.cs
@@ -15,7 +15,6 @@
using Exceptionless.Tests.Log;
using Exceptionless.Tests.Utility;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Storage {
public abstract class FileStorageTestsBase {
diff --git a/test/Exceptionless.Tests/Storage/FolderFileStorageTests.cs b/test/Exceptionless.Tests/Storage/FolderFileStorageTests.cs
index 95d10821..a2dfcb2d 100644
--- a/test/Exceptionless.Tests/Storage/FolderFileStorageTests.cs
+++ b/test/Exceptionless.Tests/Storage/FolderFileStorageTests.cs
@@ -5,7 +5,6 @@
using Exceptionless.Logging;
using Exceptionless.Serializer;
using Exceptionless.Storage;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Storage {
public class FolderFileStorageTests : FileStorageTestsBase {
diff --git a/test/Exceptionless.Tests/Storage/InMemoryFileStorageTests.cs b/test/Exceptionless.Tests/Storage/InMemoryFileStorageTests.cs
index 8030ef29..2ef1d90c 100644
--- a/test/Exceptionless.Tests/Storage/InMemoryFileStorageTests.cs
+++ b/test/Exceptionless.Tests/Storage/InMemoryFileStorageTests.cs
@@ -1,6 +1,5 @@
using Exceptionless.Storage;
using Xunit;
-using Xunit.Abstractions;
namespace Exceptionless.Tests.Storage {
public class InMemoryFileStorageTests : FileStorageTestsBase {
diff --git a/test/Exceptionless.Tests/Storage/IsolatedStorageFileStorageTests.cs b/test/Exceptionless.Tests/Storage/IsolatedStorageFileStorageTests.cs
index c5699e6c..2430269f 100644
--- a/test/Exceptionless.Tests/Storage/IsolatedStorageFileStorageTests.cs
+++ b/test/Exceptionless.Tests/Storage/IsolatedStorageFileStorageTests.cs
@@ -3,7 +3,7 @@
using Exceptionless.Dependency;
using Exceptionless.Serializer;
using Exceptionless.Storage;
-using Xunit.Abstractions;
+using Xunit;
namespace Exceptionless.Tests.Storage {
public class IsolatedStorageFileStorageTests : FileStorageTestsBase {
@@ -17,4 +17,4 @@ protected override IObjectStorage GetStorage() {
}
}
}
-#endif
\ No newline at end of file
+#endif
diff --git a/test/Exceptionless.Tests/Utility/TestOutputWriter.cs b/test/Exceptionless.Tests/Utility/TestOutputWriter.cs
index 08d77134..6066b62c 100644
--- a/test/Exceptionless.Tests/Utility/TestOutputWriter.cs
+++ b/test/Exceptionless.Tests/Utility/TestOutputWriter.cs
@@ -2,7 +2,7 @@
using System.Diagnostics;
using System.IO;
using System.Text;
-using Xunit.Abstractions;
+using Xunit;
namespace Exceptionless.Tests.Utility {
public class TestOutputWriter : TextWriter {
@@ -28,4 +28,4 @@ public override void WriteLine() {
WriteLine(String.Empty);
}
}
-}
\ No newline at end of file
+}