Browse Source

Avoid stack overflow when logging exceptions in Blazor WebAssembly

Filter out UserExceptionInformer category from AbpExceptionHandlingLoggerProvider so logs written by the informer do not re-enter the same provider, which previously caused infinite recursion and a browser stack overflow.

Fixes #25463
pull/25464/head
maliming 5 months ago
parent
commit
865c5bcfd5
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 3
      framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs
  2. 62
      framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo/Abp/AspNetCore/Components/Web/ExceptionHandling/AbpExceptionHandlingLogger_Tests.cs

3
framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs

@ -48,7 +48,8 @@ public class AbpAspNetCoreComponentsWebAssemblyModule : AbpModule
context.Services.AddHttpClient(); context.Services.AddHttpClient();
context.Services context.Services
.GetHostBuilder().Logging .GetHostBuilder().Logging
.AddProvider(new AbpExceptionHandlingLoggerProvider(context.Services)); .AddProvider(new AbpExceptionHandlingLoggerProvider(context.Services))
.AddFilter<AbpExceptionHandlingLoggerProvider>(typeof(UserExceptionInformer).FullName, LogLevel.None);
if (!context.Services.ExecutePreConfiguredActions<AbpAspNetCoreComponentsWebOptions>().IsBlazorWebApp) if (!context.Services.ExecutePreConfiguredActions<AbpAspNetCoreComponentsWebOptions>().IsBlazorWebApp)
{ {

62
framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo/Abp/AspNetCore/Components/Web/ExceptionHandling/AbpExceptionHandlingLogger_Tests.cs

@ -0,0 +1,62 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Shouldly;
using Volo.Abp.AspNetCore.Components.ExceptionHandling;
using Xunit;
namespace Volo.Abp.AspNetCore.Components.Web.ExceptionHandling;
public class AbpExceptionHandlingLogger_Tests
{
[Fact]
public void Should_Not_Recurse_When_Informer_Writes_To_Same_Logger_Pipeline()
{
var services = new ServiceCollection();
var informer = new RecordingUserExceptionInformer();
services.AddSingleton<IUserExceptionInformer>(informer);
var provider = new AbpExceptionHandlingLoggerProvider(services);
services.AddLogging(builder => builder
.AddProvider(provider)
.AddFilter<AbpExceptionHandlingLoggerProvider>(typeof(UserExceptionInformer).FullName, LogLevel.None));
var accessor = services.AddObjectAccessor<IServiceProvider>();
var sp = services.BuildServiceProvider();
accessor.Value = sp;
informer.Logger = sp.GetRequiredService<ILoggerFactory>()
.CreateLogger(typeof(UserExceptionInformer).FullName!);
var entryLogger = sp.GetRequiredService<ILogger<AbpExceptionHandlingLogger_Tests>>();
entryLogger.LogError(new InvalidOperationException("boom"), "entry");
informer.InformCalls.ShouldBe(1);
}
private sealed class RecordingUserExceptionInformer : IUserExceptionInformer
{
private const int RecursionCircuitBreaker = 50;
public int InformCalls;
public ILogger? Logger;
public void Inform(UserExceptionInformerContext context)
{
InformCalls++;
if (InformCalls > RecursionCircuitBreaker)
{
return;
}
Logger?.LogError(context.Exception, context.Exception.Message);
}
public Task InformAsync(UserExceptionInformerContext context)
{
Inform(context);
return Task.CompletedTask;
}
}
}
Loading…
Cancel
Save