diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index fab607e2f..61573bb59 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -291,6 +291,7 @@ namespace BTCPayServer.Hosting }); services.TryAddSingleton(); + services.AddExceptionHandler(); services.TryAddSingleton(); services.AddTransient(); services.AddSingleton(); diff --git a/BTCPayServer/Hosting/Startup.cs b/BTCPayServer/Hosting/Startup.cs index e4303b3bc..1e1e7767d 100644 --- a/BTCPayServer/Hosting/Startup.cs +++ b/BTCPayServer/Hosting/Startup.cs @@ -293,6 +293,7 @@ namespace BTCPayServer.Hosting app.UseStatusCodePagesWithReExecute("/errors/{0}"); + app.UseExceptionHandler("/errors/{0}"); app.UsePayServer(); app.UseRouting(); app.UseCors(); diff --git a/BTCPayServer/Plugins/PluginExceptionHandler.cs b/BTCPayServer/Plugins/PluginExceptionHandler.cs new file mode 100644 index 000000000..93cd049c7 --- /dev/null +++ b/BTCPayServer/Plugins/PluginExceptionHandler.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using BTCPayServer.Configuration; +using BTCPayServer.Logging; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Hosting.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace BTCPayServer.Plugins +{ + public class PluginExceptionHandler : IExceptionHandler + { + readonly string _pluginDir; + readonly IHostApplicationLifetime _applicationLifetime; + private readonly Logs _logs; + + public PluginExceptionHandler(IOptions options, IHostApplicationLifetime applicationLifetime, Logs logs) + { + _applicationLifetime = applicationLifetime; + _logs = logs; + _pluginDir = options.Value.PluginDir; + } + public ValueTask TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) + { + if (!GetDisablePluginIfCrash(httpContext) || + !PluginManager.IsExceptionByPlugin(exception, out var pluginName)) + return ValueTask.FromResult(false); + _logs.Configuration.LogError(exception, $"Unhandled exception caused by plugin '{pluginName}', disabling it and restarting..."); + _ = Task.Delay(3000).ContinueWith((t) => _applicationLifetime.StopApplication()); + // Returning true here means we will see Error 500 error message. + // Returning false means that the user will see a stacktrace. + return ValueTask.FromResult(false); + } + + internal static bool GetDisablePluginIfCrash(HttpContext httpContext) + { + return httpContext.Items.TryGetValue("DisablePluginIfCrash", out object renderingDashboard) || + renderingDashboard is not true; + } + internal static void SetDisablePluginIfCrash(HttpContext httpContext) + { + httpContext.Items.TryAdd("DisablePluginIfCrash", true); + } + } +} diff --git a/BTCPayServer/Views/UIServer/ListPlugins.cshtml b/BTCPayServer/Views/UIServer/ListPlugins.cshtml index a0ceccd20..aa426a978 100644 --- a/BTCPayServer/Views/UIServer/ListPlugins.cshtml +++ b/BTCPayServer/Views/UIServer/ListPlugins.cshtml @@ -84,7 +84,7 @@ @plugin @if (version != null) { - ({version}) + (@version) }
diff --git a/BTCPayServer/Views/UIStores/Dashboard.cshtml b/BTCPayServer/Views/UIStores/Dashboard.cshtml index 66095e1b3..c5fc15935 100644 --- a/BTCPayServer/Views/UIStores/Dashboard.cshtml +++ b/BTCPayServer/Views/UIStores/Dashboard.cshtml @@ -9,7 +9,8 @@ @using BTCPayServer.Client @model StoreDashboardViewModel @{ - ViewData.SetActivePage(StoreNavPages.Dashboard, Model.StoreName, Model.StoreId); + BTCPayServer.Plugins.PluginExceptionHandler.SetDisablePluginIfCrash(Context); + ViewData.SetActivePage(StoreNavPages.Dashboard, Model.StoreName, Model.StoreId); var store = ViewContext.HttpContext.GetStoreData(); }