有没有办法全局捕获 Blazor 单页应用程序中所有未处理的错误?

Tro*_*sGP 10 error-handling unhandled-exception blazor

我希望能够在构建 Blazor 单页应用程序的一个地方捕获所有未处理的异常。就像在 WPF 应用程序中使用“Current.DispatcherUnhandledException”一样。

这个问题完全是关于客户端(webassembly)异常处理的。我正在使用 Blazor 版本 3.0.0-preview8.19405.7

我一直在寻找解决方案,但似乎它不存在。在 Microsoft 的文档 ( https://docs.microsoft.com/en-us/aspnet/core/blazor/handle-errors?view=aspnetcore-3.0 ) 上,有一个可能发生错误的地方列表以及有关如何操作的演练处理每一个。它相信必须有一种更防弹的方法来捕获所有。

Sco*_*son 9

这适用于 v3.2+

using Microsoft.Extensions.Logging;
using System;

namespace UnhandledExceptions.Client
{
    public interface IUnhandledExceptionSender
    {
        event EventHandler<Exception> UnhandledExceptionThrown;
    }

    public class UnhandledExceptionSender : ILogger, IUnhandledExceptionSender
    {

        public event EventHandler<Exception> UnhandledExceptionThrown;

        public IDisposable BeginScope<TState>(TState state)
        {
            return null;
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            return true;
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
            Exception exception, Func<TState, Exception, string> formatter)
        {
            if (exception != null)
            {
                UnhandledExceptionThrown?.Invoke(this, exception);
            }
        }
    }

    public class UnhandledExceptionProvider : ILoggerProvider
    {
        UnhandledExceptionSender _unhandledExceptionSender;

 
        public UnhandledExceptionProvider(UnhandledExceptionSender unhandledExceptionSender)
        {
            _unhandledExceptionSender = unhandledExceptionSender;
        }

        public ILogger CreateLogger(string categoryName)
        {
            return new UnhandledExceptionLogger(categoryName, _unhandledExceptionSender);
        }

        public void Dispose()
        {            
        }

        public class UnhandledExceptionLogger : ILogger
        {
            private readonly string _categoryName;
            private readonly UnhandledExceptionSender _unhandeledExceptionSender;

            public UnhandledExceptionLogger(string categoryName, UnhandledExceptionSender unhandledExceptionSender)
            {
                _unhandeledExceptionSender = unhandledExceptionSender;
                _categoryName = categoryName;
            }

            public bool IsEnabled(LogLevel logLevel)
            {
                return true;
            }

            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
            {
                // Unhandled exceptions will call this method
                // Blazor already logs unhandled exceptions to the browser console
                // but, one could pass the exception to the server to log, this is easily done with serilog
                Serilog.Log.Fatal(exception, exception.Message);                             
            }

            public IDisposable BeginScope<TState>(TState state)
            {
                return new NoopDisposable();
            }

            private class NoopDisposable : IDisposable
            {
                public void Dispose()
                {  
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

将此添加到 Program.cs

var unhandledExceptionSender = new UnhandledExceptionSender();
var unhandledExceptionProvider = new UnhandledExceptionProvider(unhandledExceptionSender);
builder.Logging.AddProvider(unhandledExceptionProvider);
builder.Services.AddSingleton<IUnhandledExceptionSender>(unhandledExceptionSender);
Run Code Online (Sandbox Code Playgroud)

是实现此解决方案的示例项目。

  • 谢谢,斯科蒂!对于那些希望具有自定义 UI 显示而不是依赖于 index.html 中的“blazor-error-ui”的人,IJSRuntime 也可以通过依赖项注入传递到 UnhandledExceptionSender,然后传递到 UnhandledExceptionLogger。这允许使用 javascript 来显示自定义错误显示。 (3认同)
  • 现在已经是 2022 年了,.NET 7 即将上线,还有更好的方法来处理这个问题吗? (3认同)

Ala*_*bra 8

.NET 6ErrorBoundary即将到来。

<ErrorBoundary>
   @Body
</ErrorBoundary>
Run Code Online (Sandbox Code Playgroud)

对于全局异常处理,我将其视为一个选项:创建CustomErrorBoundary(继承ErrorBoundary)并覆盖OnErrorAsync(Exception exception).

是 的示例CustomErrorBoundary

有用的链接

  • 看起来 ErrorBoundry 有一些限制,据我所知,这些限制没有得到很好的记录。就像 @clamchoda 提到的那样,异步方法也不会被捕获。更多信息原因:https://github.com/dotnet/aspnetcore/issues/27716#issuecomment-732115003 (4认同)
  • 如果异常是从服务的构造函数引发的,这对我不起作用。 (3认同)
  • `Context="ex"` 是我所缺少的部分,因为它允许您访问异常详细信息。MS 文档目前缺少该信息。谢谢! (3认同)

Dem*_*mir 7

对于 .NET 5 Blazor服务器端,这篇文章Create Your Own Logging Provider to Log to Text Files in .NET Core对我有用。对于我的情况,我对此进行了调整以捕获未处理的异常以写入 Azure 存储表。

public class ExceptionLoggerOptions
{
    public virtual bool Enabled { get; set; }
}

[ProviderAlias("ExceptionLogger")]
public class ExceptionLoggerProvider : ILoggerProvider
{
    public readonly ExceptionLoggerOptions Options;

    public ExceptionLoggerProvider(IOptions<ExceptionLoggerOptions> _options)
    {
        Options = _options.Value;
    }

    public ILogger CreateLogger(string categoryName)
    {
        return new ExceptionLogger(this);
    }

    public void Dispose()
    {
    }
}

public class ExceptionLogger : ILogger
{
    protected readonly ExceptionLoggerProvider _exceptionLoggerProvider;

    public ExceptionLogger([NotNull] ExceptionLoggerProvider exceptionLoggerProvider)
    {
        _exceptionLoggerProvider = exceptionLoggerProvider;
    }

    public IDisposable BeginScope<TState>(TState state)
    {
        return null;
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return logLevel == LogLevel.Error;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if (false == _exceptionLoggerProvider.Options.Enabled) return;

        if (null == exception) return;

        if (false == IsEnabled(logLevel)) return;

        var record = $"{exception.Message}"; // string.Format("{0} {1} {2}",  logLevel.ToString(), formatter(state, exception), exception?.StackTrace);

        // Record exception into Azure Table
    }
}

public static class ExceptionLoggerExtensions
{
    public static ILoggingBuilder AddExceptionLogger(this ILoggingBuilder builder, Action<ExceptionLoggerOptions> configure)
    {
        builder.Services.AddSingleton<ILoggerProvider, ExceptionLoggerProvider>();
        builder.Services.Configure(configure);
        return builder;
    }
}

    public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseStaticWebAssets().UseStartup<Startup>();
    }).ConfigureLogging((hostBuilderContext, logging) =>
    {
        logging.AddExceptionLogger(options => { options.Enabled = true; });
    });
Run Code Online (Sandbox Code Playgroud)

  • 这不起作用 - 我仍然有“ServerApp.dll 中发生类型‘System.Exception’的异常,但未在用户代码中处理”的异常 (3认同)

Pos*_*rte 6

目前没有中央位置来捕获和处理客户端异常。

这是史蒂夫桑德森的一段话:

所以总的来说,每个组件都必须处理自己的错误。如果你愿意,你可以创建你自己的 ErrorHandlingComponentBase 来继承,并在所有生命周期方法周围放置一个 try/catch,并拥有你自己的逻辑,用于在出现任何问题时在该组件上显示“哦,亲爱的,对不起,我死了”UI。但这不是今天框架的功能。

我希望这会在未来改变,我相信应该支持框架。

  • 如何在基类中的子类的生命周期方法周围放置 try/catch ? (3认同)
  • 这方面有什么动静吗?看起来这是一个非常好的能力。 (2认同)
  • 你从哪里得到这句话? (2认同)