From 5f916e64dbbf58a2dac20609bf15a4f2bffded7c Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Tue, 10 Feb 2026 12:24:12 -0600 Subject: [PATCH] Fix WithDefaultLogMessageFormatter rejecting built-in formatters (#703) SemanticLogMessageFormatter and DefaultLogMessageFormatter have private constructors (they use singleton Instance properties). The validation in LoggerConfigBuilder.LogMessageFormatter incorrectly rejected them because GetConstructor([]) only finds public constructors. Core Akka.NET's Settings.cs already special-cases these types at runtime. Skip the constructor check for built-in formatter types. Also deprecate WithDefaultLogMessageFormatter() since SemanticLogMessageFormatter is now the default as of Akka.NET 1.5.58. --- .../CoreApiSpec.ApproveCore.verified.txt | 4 +- .../Logging/LogMessageFormatterSpec.cs | 40 +++++++++++++++++++ .../Logging/SerilogLoggerEnd2EndSpecs.cs | 2 + src/Akka.Hosting/LoggerConfigBuilder.cs | 21 +++++++--- 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/Akka.Hosting.API.Tests/verify/CoreApiSpec.ApproveCore.verified.txt b/src/Akka.Hosting.API.Tests/verify/CoreApiSpec.ApproveCore.verified.txt index e54f8bb0..32820961 100644 --- a/src/Akka.Hosting.API.Tests/verify/CoreApiSpec.ApproveCore.verified.txt +++ b/src/Akka.Hosting.API.Tests/verify/CoreApiSpec.ApproveCore.verified.txt @@ -194,6 +194,8 @@ namespace Akka.Hosting public Akka.Hosting.LoggerConfigBuilder AddLogger() where T : Akka.Dispatch.IRequiresMessageQueue { } public Akka.Hosting.LoggerConfigBuilder ClearLoggers() { } + [System.Obsolete("SemanticLogMessageFormatter is now the default. Only use this method if you have " + + "a custom ILogMessageFormatter implementation.")] public Akka.Hosting.LoggerConfigBuilder WithDefaultLogMessageFormatter() where T : Akka.Event.ILogMessageFormatter { } public Akka.Hosting.LoggerConfigBuilder WithLogFilter(System.Action filterBuilder) { } @@ -273,4 +275,4 @@ namespace Akka.Hosting.Logging public LoggerFactorySetup(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) { } public Microsoft.Extensions.Logging.ILoggerFactory LoggerFactory { get; } } -} +} \ No newline at end of file diff --git a/src/Akka.Hosting.Tests/Logging/LogMessageFormatterSpec.cs b/src/Akka.Hosting.Tests/Logging/LogMessageFormatterSpec.cs index dfd2adb7..2884b0d0 100644 --- a/src/Akka.Hosting.Tests/Logging/LogMessageFormatterSpec.cs +++ b/src/Akka.Hosting.Tests/Logging/LogMessageFormatterSpec.cs @@ -63,6 +63,44 @@ await Awaiting(async () => await SetupHost()) .Should().ThrowAsync().WithMessage("*must have an empty constructor*"); } + [Fact(DisplayName = "SemanticLogMessageFormatter should be accepted (GitHub issue #703)")] + public async Task SemanticLogMessageFormatterShouldBeAcceptedTest() + { + // SemanticLogMessageFormatter has a private constructor - verify it doesn't throw +#pragma warning disable CS0618 + using var host = await SetupHost(); +#pragma warning restore CS0618 + + try + { + var sys = host.Services.GetRequiredService(); + sys.Settings.LogFormatter.Should().BeOfType(); + } + finally + { + await host.StopAsync(); + } + } + + [Fact(DisplayName = "DefaultLogMessageFormatter should be accepted")] + public async Task DefaultLogMessageFormatterShouldBeAcceptedTest() + { + // DefaultLogMessageFormatter has a private constructor - verify it doesn't throw +#pragma warning disable CS0618 + using var host = await SetupHost(); +#pragma warning restore CS0618 + + try + { + var sys = host.Services.GetRequiredService(); + sys.Settings.LogFormatter.Should().BeOfType(); + } + finally + { + await host.StopAsync(); + } + } + private async Task SetupHost() where TFormatter : ILogMessageFormatter { var host = new HostBuilder() @@ -79,7 +117,9 @@ private async Task SetupHost() where TFormatter : ILogMessage { setup.LogLevel = Event.LogLevel.DebugLevel; setup.AddLoggerFactory(); +#pragma warning disable CS0618 setup.WithDefaultLogMessageFormatter(); +#pragma warning restore CS0618 }); }); }).Build(); diff --git a/src/Akka.Hosting.Tests/Logging/SerilogLoggerEnd2EndSpecs.cs b/src/Akka.Hosting.Tests/Logging/SerilogLoggerEnd2EndSpecs.cs index 34908125..d01843bb 100644 --- a/src/Akka.Hosting.Tests/Logging/SerilogLoggerEnd2EndSpecs.cs +++ b/src/Akka.Hosting.Tests/Logging/SerilogLoggerEnd2EndSpecs.cs @@ -69,7 +69,9 @@ protected override void ConfigureAkka(AkkaConfigurationBuilder builder, IService setup.ClearLoggers(); setup.AddLogger(); setup.LogLevel = Event.LogLevel.DebugLevel; +#pragma warning disable CS0618 setup.WithDefaultLogMessageFormatter(); +#pragma warning restore CS0618 }); } diff --git a/src/Akka.Hosting/LoggerConfigBuilder.cs b/src/Akka.Hosting/LoggerConfigBuilder.cs index 8fe36585..fd7a4434 100644 --- a/src/Akka.Hosting/LoggerConfigBuilder.cs +++ b/src/Akka.Hosting/LoggerConfigBuilder.cs @@ -57,10 +57,15 @@ public Type LogMessageFormatter if (!typeof(ILogMessageFormatter).IsAssignableFrom(value)) throw new ConfigurationException($"{nameof(LogMessageFormatter)} must implement {nameof(ILogMessageFormatter)}"); - var ctor = value.GetConstructor([]); - if (ctor is null) - throw new ConfigurationException($"{nameof(LogMessageFormatter)} Type must have an empty constructor"); - + // Built-in formatters use private constructors with singleton Instance properties; + // Akka.NET's Settings.cs handles these as special cases at runtime. + if (value != typeof(SemanticLogMessageFormatter) && value != typeof(DefaultLogMessageFormatter)) + { + var ctor = value.GetConstructor([]); + if (ctor is null) + throw new ConfigurationException($"{nameof(LogMessageFormatter)} Type must have an empty constructor"); + } + _logMessageFormatter = value; } } @@ -87,8 +92,14 @@ public LoggerConfigBuilder AddLogger() where T: IRequiresMessageQueue - /// Sets the formatter used by the logger + /// Sets the formatter used by the logger. /// + /// + /// As of Akka.NET 1.5.58, is the default formatter + /// and is configured automatically. This method is only needed if you have a custom + /// implementation. + /// + [Obsolete("SemanticLogMessageFormatter is now the default. Only use this method if you have a custom ILogMessageFormatter implementation.")] public LoggerConfigBuilder WithDefaultLogMessageFormatter() where T: ILogMessageFormatter { #pragma warning disable CS0618 // Type or member is obsolete