From 865c5bcfd59ed4962aeb04a5b69703404375044f Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 22 May 2026 17:49:01 +0800 Subject: [PATCH] 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 --- ...bpAspNetCoreComponentsWebAssemblyModule.cs | 3 +- .../AbpExceptionHandlingLogger_Tests.cs | 62 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo/Abp/AspNetCore/Components/Web/ExceptionHandling/AbpExceptionHandlingLogger_Tests.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs index b4ea3cb50c..958278226f 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly/Volo/Abp/AspNetCore/Components/WebAssembly/AbpAspNetCoreComponentsWebAssemblyModule.cs +++ b/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 .GetHostBuilder().Logging - .AddProvider(new AbpExceptionHandlingLoggerProvider(context.Services)); + .AddProvider(new AbpExceptionHandlingLoggerProvider(context.Services)) + .AddFilter(typeof(UserExceptionInformer).FullName, LogLevel.None); if (!context.Services.ExecutePreConfiguredActions().IsBlazorWebApp) { diff --git a/framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo/Abp/AspNetCore/Components/Web/ExceptionHandling/AbpExceptionHandlingLogger_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo/Abp/AspNetCore/Components/Web/ExceptionHandling/AbpExceptionHandlingLogger_Tests.cs new file mode 100644 index 0000000000..b010483f28 --- /dev/null +++ b/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(informer); + + var provider = new AbpExceptionHandlingLoggerProvider(services); + services.AddLogging(builder => builder + .AddProvider(provider) + .AddFilter(typeof(UserExceptionInformer).FullName, LogLevel.None)); + + var accessor = services.AddObjectAccessor(); + var sp = services.BuildServiceProvider(); + accessor.Value = sp; + + informer.Logger = sp.GetRequiredService() + .CreateLogger(typeof(UserExceptionInformer).FullName!); + + var entryLogger = sp.GetRequiredService>(); + 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; + } + } +}