From caa943444ef2bb6c4781a91a8073109e61a25c4c Mon Sep 17 00:00:00 2001 From: Christopher Wolf <4867233+HaGGi13@users.noreply.github.com> Date: Sat, 7 Feb 2026 19:35:11 +0100 Subject: [PATCH] Optimize by fixing Fix CA1873 Roslyn suggestion Replaced inline log messages with `LoggerMessage` attribute based logging methods across classes. For readability they were extracted into separate files following the name schema `.logger.cs`. This was done to prevent a log execution validation of not execute log actions, because the log level not being met or because the logger is disabled in general. As all command paths are hot path, this was worth in regards of performance. --- .../Cli/Config/SetConfigCommand.cs | 4 +-- .../Cli/Config/SetConfigCommand.logger.cs | 9 ++++++ .../ConsoleCancellationTokenSource.cs | 22 ++++++------- .../ConsoleCancellationTokenSource.logger.cs | 16 ++++++++++ src/ConnyConsole/Services/LogService.cs | 31 +++++-------------- .../Services/LogService.logger.cs | 9 ++++++ .../Services/LogServiceTests.cs | 2 +- 7 files changed, 55 insertions(+), 38 deletions(-) create mode 100644 src/ConnyConsole/Cli/Config/SetConfigCommand.logger.cs create mode 100644 src/ConnyConsole/Infrastructure/ConsoleCancellationTokenSource.logger.cs create mode 100644 src/ConnyConsole/Services/LogService.logger.cs diff --git a/src/ConnyConsole/Cli/Config/SetConfigCommand.cs b/src/ConnyConsole/Cli/Config/SetConfigCommand.cs index 1ac9af1..408a0ee 100644 --- a/src/ConnyConsole/Cli/Config/SetConfigCommand.cs +++ b/src/ConnyConsole/Cli/Config/SetConfigCommand.cs @@ -6,7 +6,7 @@ namespace ConnyConsole.Cli.Config; -public sealed class SetConfigCommand : Command +public sealed partial class SetConfigCommand : Command { private readonly IConfigurationEditor _configurationEditor; private readonly ILogger _logger; @@ -64,7 +64,7 @@ private void Handle(ParseResult parseResult, SettingKeyArgument keyArgument, Set var scope = GetConfigurationScope(parseResult); var resultMessage = _configurationEditor.SetValue(key, value, scope); - _logger.LogInformation("Set setting result: {Message}", resultMessage); + LogSetSettingResultMessage(resultMessage); } catch (Exception exception) { diff --git a/src/ConnyConsole/Cli/Config/SetConfigCommand.logger.cs b/src/ConnyConsole/Cli/Config/SetConfigCommand.logger.cs new file mode 100644 index 0000000..6a3d9a9 --- /dev/null +++ b/src/ConnyConsole/Cli/Config/SetConfigCommand.logger.cs @@ -0,0 +1,9 @@ +using Microsoft.Extensions.Logging; + +namespace ConnyConsole.Cli.Config; + +public sealed partial class SetConfigCommand +{ + [LoggerMessage(LogLevel.Information, "Set setting result: {message}")] + private partial void LogSetSettingResultMessage(string message); +} diff --git a/src/ConnyConsole/Infrastructure/ConsoleCancellationTokenSource.cs b/src/ConnyConsole/Infrastructure/ConsoleCancellationTokenSource.cs index 0a3ceef..86ba140 100644 --- a/src/ConnyConsole/Infrastructure/ConsoleCancellationTokenSource.cs +++ b/src/ConnyConsole/Infrastructure/ConsoleCancellationTokenSource.cs @@ -3,7 +3,7 @@ namespace ConnyConsole.Infrastructure; /// -public class ConsoleCancellationTokenSource( +public partial class ConsoleCancellationTokenSource( ILogger logger, IEnvironmentProvider environmentProvider) : CancellationTokenSource @@ -31,9 +31,7 @@ public ConsoleCancelEventHandler CreateCancellationHandler(TimeSpan timeout) { if (_isGracefulCancelled) { - logger.LogInformation( - "Received interrupt signal, attempting to shut down gracefully but will force-close in {Seconds} seconds. Send again to immediately force-close.", - timeout.TotalSeconds); + LogGracefulShutdownInitiated(timeout.TotalSeconds); Cancel(); cancelEvent.Cancel = true; @@ -43,7 +41,7 @@ public ConsoleCancelEventHandler CreateCancellationHandler(TimeSpan timeout) } else { - logger.LogInformation("Second interrupt received, force-closing the app"); + LogForceShutdownInitiated(); ExitApplication(); } }; @@ -54,12 +52,6 @@ public ConsoleCancelEventHandler CreateCancellationHandler(TimeSpan timeout) /// private void EnforceExitAfterTimeout(int timeoutInMilliseconds) { - void LogAndExit() - { - logger.LogInformation("Timeout reached, force-closing app."); - ExitApplication(); - } - if (timeoutInMilliseconds > 0) { _ = new Timer(_ => LogAndExit(), @@ -71,6 +63,14 @@ void LogAndExit() { LogAndExit(); } + + return; + + void LogAndExit() + { + LogForceShutdownAfterTimeout(); + ExitApplication(); + } } /// diff --git a/src/ConnyConsole/Infrastructure/ConsoleCancellationTokenSource.logger.cs b/src/ConnyConsole/Infrastructure/ConsoleCancellationTokenSource.logger.cs new file mode 100644 index 0000000..a5c3a4a --- /dev/null +++ b/src/ConnyConsole/Infrastructure/ConsoleCancellationTokenSource.logger.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.Logging; + +namespace ConnyConsole.Infrastructure; + +public sealed partial class ConsoleCancellationTokenSource +{ + [LoggerMessage(LogLevel.Information, + "Received interrupt signal, attempting to shut down gracefully but will force-close in {seconds} seconds. Send again to immediately force-close.")] + private partial void LogGracefulShutdownInitiated(double seconds); + + [LoggerMessage(LogLevel.Information, "Second interrupt received, force-closing the app")] + private partial void LogForceShutdownInitiated(); + + [LoggerMessage(LogLevel.Information, "Timeout reached, force-closing app.")] + private partial void LogForceShutdownAfterTimeout(); +} diff --git a/src/ConnyConsole/Services/LogService.cs b/src/ConnyConsole/Services/LogService.cs index 69990b7..00dc382 100644 --- a/src/ConnyConsole/Services/LogService.cs +++ b/src/ConnyConsole/Services/LogService.cs @@ -3,35 +3,18 @@ namespace ConnyConsole.Services; /// -public sealed class LogService(ILogger logger) : ILogService +public sealed partial class LogService(ILogger logger) : ILogService { - private const string MessageTemplate = "{Message}"; + private const string MessageTemplate = "{message}"; public void Log(LogLevel level, string? message) { var logMessage = message ?? string.Empty; - // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - is marked by SonarQube rule csharpsquid:S3458 - switch (level) - { - case LogLevel.Critical: - logger.LogCritical(MessageTemplate, logMessage); - break; - case LogLevel.Error: - logger.LogError(MessageTemplate, logMessage); - break; - case LogLevel.Warning: - logger.LogWarning(MessageTemplate, logMessage); - break; - case LogLevel.Debug: - logger.LogDebug(MessageTemplate, logMessage); - break; - case LogLevel.Trace: - logger.LogTrace(MessageTemplate, logMessage); - break; - default: - logger.LogInformation(MessageTemplate, logMessage); - break; - } + var logLevel = level == LogLevel.None + ? LogLevel.Information + : level; + + LogMessage(logLevel, logMessage); } } diff --git a/src/ConnyConsole/Services/LogService.logger.cs b/src/ConnyConsole/Services/LogService.logger.cs new file mode 100644 index 0000000..3b370e0 --- /dev/null +++ b/src/ConnyConsole/Services/LogService.logger.cs @@ -0,0 +1,9 @@ +using Microsoft.Extensions.Logging; + +namespace ConnyConsole.Services; + +public sealed partial class LogService +{ + [LoggerMessage(MessageTemplate)] + private partial void LogMessage(LogLevel logLevel, string message); +} diff --git a/tests/ConnyConsole.Tests/Services/LogServiceTests.cs b/tests/ConnyConsole.Tests/Services/LogServiceTests.cs index b02a2ae..7451de7 100644 --- a/tests/ConnyConsole.Tests/Services/LogServiceTests.cs +++ b/tests/ConnyConsole.Tests/Services/LogServiceTests.cs @@ -226,7 +226,7 @@ public void Log_LeadingAndTrailingWhitespacesMessage_LogsMessageWithLeadingAndTr { // Arrange var logService = new LogService(_logger); - var message = $" {TestMessage} "; + const string message = $" {TestMessage} "; // Act logService.Log(LogLevel.Information, message);