refactor(configmanager): convert CLI to structured logging with Serilog

Replace Console.WriteLine calls with ILogger usage across all CLI commands.
Serilog is configured via DI with clean message-only output suitable for
CLI tooling. Log levels map to --quiet (Warning), default (Information),
and --verbose (Debug) flags.

- Add Serilog packages and configure in Program.cs
- Convert all 7 command files to use ILoggerFactory from DI
- Add BeginScope with context properties (Command, ConfigPath, etc.)
- Create logging_style.md documenting patterns and best practices
- Update tests with TestLoggingHelper for Serilog test configuration
This commit is contained in:
Joseph Doherty
2026-01-28 15:53:08 -05:00
parent 61694ca50b
commit 6f3e12b3b4
18 changed files with 1684 additions and 1220 deletions
@@ -23,6 +23,7 @@ public class BackupCommandsTests
var services = new ServiceCollection();
services.AddSingleton(_backupService);
services.AddSingleton(_autoDiscoveryService);
services.AddTestLogging();
_serviceProvider = services.BuildServiceProvider();
_configPathOption = new Option<string?>(["--config-path", "-c"]);
@@ -272,10 +273,10 @@ public class BackupCommandsTests
rootCommand.AddGlobalOption(_verboseOption);
rootCommand.AddGlobalOption(_quietOption);
// Capture console error output
var originalErr = Console.Error;
// Capture console output (Serilog writes all output to stdout)
var originalOut = Console.Out;
using var writer = new StringWriter();
Console.SetError(writer);
Console.SetOut(writer);
try
{
@@ -288,7 +289,7 @@ public class BackupCommandsTests
}
finally
{
Console.SetError(originalErr);
Console.SetOut(originalOut);
}
}
finally
@@ -27,6 +27,7 @@ public class ConfigCommandsTests
services.AddSingleton(_configFileService);
services.AddSingleton(_autoDiscoveryService);
services.AddSingleton(_backupService);
services.AddTestLogging();
_serviceProvider = services.BuildServiceProvider();
_configPathOption = new Option<string?>(["--config-path", "-c"]);
@@ -366,10 +367,10 @@ public class ConfigCommandsTests
rootCommand.AddGlobalOption(_verboseOption);
rootCommand.AddGlobalOption(_quietOption);
// Capture console error output
var originalErr = Console.Error;
// Capture console output (Serilog writes all output to stdout)
var originalOut = Console.Out;
using var writer = new StringWriter();
Console.SetError(writer);
Console.SetOut(writer);
try
{
@@ -382,7 +383,7 @@ public class ConfigCommandsTests
}
finally
{
Console.SetError(originalErr);
Console.SetOut(originalOut);
}
}
finally
@@ -24,6 +24,7 @@ public class ConnectionCommandsTests
var services = new ServiceCollection();
services.AddSingleton(_configFileService);
services.AddSingleton(_autoDiscoveryService);
services.AddTestLogging();
_serviceProvider = services.BuildServiceProvider();
_configPathOption = new Option<string?>(["--config-path", "-c"]);
@@ -219,10 +220,10 @@ public class ConnectionCommandsTests
rootCommand.AddGlobalOption(_verboseOption);
rootCommand.AddGlobalOption(_quietOption);
// Capture console error output
var originalErr = Console.Error;
// Capture console output (Serilog writes all output to stdout)
var originalOut = Console.Out;
using var writer = new StringWriter();
Console.SetError(writer);
Console.SetOut(writer);
try
{
@@ -235,7 +236,7 @@ public class ConnectionCommandsTests
}
finally
{
Console.SetError(originalErr);
Console.SetOut(originalOut);
}
}
finally
@@ -331,10 +332,10 @@ public class ConnectionCommandsTests
rootCommand.AddGlobalOption(_verboseOption);
rootCommand.AddGlobalOption(_quietOption);
// Capture console error output
var originalErr = Console.Error;
// Capture console output (Serilog writes all output to stdout)
var originalOut = Console.Out;
using var writer = new StringWriter();
Console.SetError(writer);
Console.SetOut(writer);
try
{
@@ -347,7 +348,7 @@ public class ConnectionCommandsTests
}
finally
{
Console.SetError(originalErr);
Console.SetOut(originalOut);
}
}
finally
@@ -393,10 +394,10 @@ public class ConnectionCommandsTests
rootCommand.AddGlobalOption(_verboseOption);
rootCommand.AddGlobalOption(_quietOption);
// Capture console error output
var originalErr = Console.Error;
// Capture console output (Serilog writes all output to stdout)
var originalOut = Console.Out;
using var writer = new StringWriter();
Console.SetError(writer);
Console.SetOut(writer);
try
{
@@ -409,7 +410,7 @@ public class ConnectionCommandsTests
}
finally
{
Console.SetError(originalErr);
Console.SetOut(originalOut);
}
}
finally
@@ -478,10 +479,10 @@ public class ConnectionCommandsTests
rootCommand.AddGlobalOption(_verboseOption);
rootCommand.AddGlobalOption(_quietOption);
// Capture console error output
var originalErr = Console.Error;
// Capture console output (Serilog writes all output to stdout)
var originalOut = Console.Out;
using var writer = new StringWriter();
Console.SetError(writer);
Console.SetOut(writer);
try
{
@@ -494,7 +495,7 @@ public class ConnectionCommandsTests
}
finally
{
Console.SetError(originalErr);
Console.SetOut(originalOut);
}
}
finally
@@ -24,6 +24,7 @@ public class PipelineCommandsTests
var services = new ServiceCollection();
services.AddSingleton(_configFileService);
services.AddSingleton(_autoDiscoveryService);
services.AddTestLogging();
_serviceProvider = services.BuildServiceProvider();
_configPathOption = new Option<string?>(["--config-path", "-c"]);
@@ -28,6 +28,7 @@ public class SecretCommandsTests
services.AddSingleton(_configFileService);
services.AddSingleton(_autoDiscoveryService);
services.AddSingleton(_secureStoreManager);
services.AddTestLogging();
_serviceProvider = services.BuildServiceProvider();
_configPathOption = new Option<string?>(["--config-path", "-c"]);
@@ -169,10 +170,10 @@ public class SecretCommandsTests
rootCommand.AddGlobalOption(_verboseOption);
rootCommand.AddGlobalOption(_quietOption);
// Capture console error output
var originalErr = Console.Error;
// Capture console output (Serilog writes all output to stdout)
var originalOut = Console.Out;
using var writer = new StringWriter();
Console.SetError(writer);
Console.SetOut(writer);
try
{
@@ -185,7 +186,7 @@ public class SecretCommandsTests
}
finally
{
Console.SetError(originalErr);
Console.SetOut(originalOut);
}
}
finally
@@ -27,6 +27,7 @@ public class TestConnectionCommandTests
services.AddSingleton(_configFileService);
services.AddSingleton(_autoDiscoveryService);
services.AddSingleton(_connectionTestService);
services.AddTestLogging();
_serviceProvider = services.BuildServiceProvider();
_configPathOption = new Option<string?>(["--config-path", "-c"]);
@@ -243,10 +244,10 @@ public class TestConnectionCommandTests
rootCommand.AddGlobalOption(_verboseOption);
rootCommand.AddGlobalOption(_quietOption);
// Capture console error output
var originalErr = Console.Error;
// Capture console output (Serilog writes all output to stdout)
var originalOut = Console.Out;
using var writer = new StringWriter();
Console.SetError(writer);
Console.SetOut(writer);
try
{
@@ -259,7 +260,7 @@ public class TestConnectionCommandTests
}
finally
{
Console.SetError(originalErr);
Console.SetOut(originalOut);
}
}
finally
@@ -31,6 +31,7 @@ public class ValidateCommandTests
services.AddSingleton(_autoDiscoveryService);
services.AddSingleton(_validationService);
services.AddSingleton(_runtimeValidationService);
services.AddTestLogging();
_serviceProvider = services.BuildServiceProvider();
_configPathOption = new Option<string?>(["--config-path", "-c"]);
@@ -193,10 +194,10 @@ public class ValidateCommandTests
rootCommand.AddGlobalOption(_verboseOption);
rootCommand.AddGlobalOption(_quietOption);
// Capture console error output
var originalErr = Console.Error;
// Capture console output (Serilog writes all output to stdout)
var originalOut = Console.Out;
using var writer = new StringWriter();
Console.SetError(writer);
Console.SetOut(writer);
try
{
@@ -205,11 +206,11 @@ public class ValidateCommandTests
// Assert
var output = writer.ToString();
output.ShouldContain("Error");
output.ShouldContain("Connection string 'LocalCache' is required");
}
finally
{
Console.SetError(originalErr);
Console.SetOut(originalOut);
}
}
finally
@@ -0,0 +1,27 @@
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Serilog.Events;
namespace JdeScoping.ConfigManager.Cli.Tests;
/// <summary>
/// Helper for setting up logging in CLI tests.
/// </summary>
public static class TestLoggingHelper
{
/// <summary>
/// Adds Serilog logging to a service collection configured to write to Console.
/// This allows tests to capture log output via Console.SetOut.
/// </summary>
public static IServiceCollection AddTestLogging(this IServiceCollection services, LogEventLevel level = LogEventLevel.Information)
{
var logger = new LoggerConfiguration()
.MinimumLevel.Is(level)
.WriteTo.Console(outputTemplate: "{Message:lj}{NewLine}{Exception}")
.CreateLogger();
services.AddLogging(builder => builder.AddSerilog(logger, dispose: true));
return services;
}
}