diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs index 690098384b..9c65d2a58d 100644 --- a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs +++ b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs @@ -311,7 +311,7 @@ private void HandleListTools(JsonElement? id) /// /// Log level precedence (highest to lowest): /// 1. MCP logging/setLevel (Agent) - always wins, overrides CLI and Config. - /// 2. CLI --LogLevel flag. + /// 2. CLI --log-level flag. /// 3. Config runtime.telemetry.log-level. /// 4. Default: None for MCP stdio mode (silent by default to keep stdout clean for JSON-RPC), /// Error in Production, Debug in Development. @@ -329,7 +329,7 @@ private void HandleListTools(JsonElement? id) /// hot-reloads do not overwrite the agent's choice. /// 3. Restore to the real stderr stream when logging is enabled, /// in case startup redirected it to (default for - /// --mcp-stdio or --LogLevel none). + /// --mcp-stdio or --log-level none). /// private void HandleSetLogLevel(JsonElement? id, JsonElement root) { @@ -388,7 +388,7 @@ private void HandleSetLogLevel(JsonElement? id, JsonElement root) bool updated = logLevelController.UpdateFromMcp(level); // Restore stderr if the agent successfully turned logging on. When `--mcp-stdio` (or - // `--LogLevel none`) was the startup default, stderr was redirected to TextWriter.Null; + // `--log-level none`) was the startup default, stderr was redirected to TextWriter.Null; // re-enable it now so subsequent logs flow. if (updated && isLoggingEnabled) { diff --git a/src/Cli.Tests/CustomLoggerTests.cs b/src/Cli.Tests/CustomLoggerTests.cs index 8c08efc3e6..cce73f0f75 100644 --- a/src/Cli.Tests/CustomLoggerTests.cs +++ b/src/Cli.Tests/CustomLoggerTests.cs @@ -6,7 +6,7 @@ namespace Cli.Tests; /// /// Tests for covering both the standard CLI /// path (writes to stdout/stderr with abbreviated labels) and the MCP stdio path -/// (suppressed by default, opt-in via either CLI --LogLevel or the +/// (suppressed by default, opt-in via either CLI --log-level or the /// runtime config's log-level, always routed to stderr to keep the /// JSON-RPC channel on stdout uncorrupted). /// @@ -85,7 +85,7 @@ public void LogOutput_UsesAbbreviatedLogLevelLabels(LogLevel logLevel, string ex } /// - /// MCP stdio mode with no overrides (neither CLI --LogLevel nor + /// MCP stdio mode with no overrides (neither CLI --log-level nor /// config log-level): all output must be suppressed so the JSON-RPC /// channel stays clean. /// @@ -106,7 +106,7 @@ public void Mcp_NoOverrides_SuppressesAllOutput() } /// - /// MCP stdio mode with a CLI-supplied --LogLevel: logs must always + /// MCP stdio mode with a CLI-supplied --log-level: logs must always /// go to stderr (never stdout) and the level threshold from /// must be honored. /// diff --git a/src/Cli.Tests/EndToEndTests.cs b/src/Cli.Tests/EndToEndTests.cs index e1644720b4..2a1e963231 100644 --- a/src/Cli.Tests/EndToEndTests.cs +++ b/src/Cli.Tests/EndToEndTests.cs @@ -814,7 +814,7 @@ public Task TestUpdatingStoredProcedureWithRestMethods() } /// - /// Test to validate that the engine starts successfully when --verbose and --LogLevel + /// Test to validate that the engine starts successfully when --verbose and --log-level /// options are used with the start command /// This test does not validate whether the engine logs messages at the specified log level /// @@ -822,15 +822,17 @@ public Task TestUpdatingStoredProcedureWithRestMethods() [DataTestMethod] [DataRow("", DisplayName = "No logging from command line.")] [DataRow("--verbose", DisplayName = "Verbose logging from command line.")] - [DataRow("--LogLevel 0", DisplayName = "LogLevel 0 from command line.")] - [DataRow("--LogLevel 1", DisplayName = "LogLevel 1 from command line.")] - [DataRow("--LogLevel 2", DisplayName = "LogLevel 2 from command line.")] - [DataRow("--LogLevel Trace", DisplayName = "LogLevel Trace from command line.")] - [DataRow("--LogLevel Debug", DisplayName = "LogLevel Debug from command line.")] - [DataRow("--LogLevel Information", DisplayName = "LogLevel Information from command line.")] - [DataRow("--LogLevel tRace", DisplayName = "Case sensitivity: LogLevel Trace from command line.")] - [DataRow("--LogLevel DebUG", DisplayName = "Case sensitivity: LogLevel Debug from command line.")] - [DataRow("--LogLevel information", DisplayName = "Case sensitivity: LogLevel Information from command line.")] + [DataRow("--log-level 0", DisplayName = "LogLevel 0 from command line.")] + [DataRow("--log-level 1", DisplayName = "LogLevel 1 from command line.")] + [DataRow("--log-level 2", DisplayName = "LogLevel 2 from command line.")] + [DataRow("--log-level Trace", DisplayName = "LogLevel Trace from command line.")] + [DataRow("--log-level Debug", DisplayName = "LogLevel Debug from command line.")] + [DataRow("--log-level Information", DisplayName = "LogLevel Information from command line.")] + [DataRow("--log-level tRace", DisplayName = "Case sensitivity: LogLevel Trace from command line.")] + [DataRow("--log-level DebUG", DisplayName = "Case sensitivity: LogLevel Debug from command line.")] + [DataRow("--log-level information", DisplayName = "Case sensitivity: LogLevel Information from command line.")] + [DataRow("--LogLevel 0", DisplayName = "Case sensitivity: LogLevel legacy Information from command line.")] + [DataRow("--LogLevel information", DisplayName = "Case sensitivity: LogLevel legacy Information from command line.")] public void TestEngineStartUpWithVerboseAndLogLevelOptions(string logLevelOption) { _fileSystem!.File.WriteAllText(TEST_RUNTIME_CONFIG_FILE, INITIAL_CONFIG); @@ -850,7 +852,7 @@ public void TestEngineStartUpWithVerboseAndLogLevelOptions(string logLevelOption } /// - /// Test to validate that the engine starts successfully when --LogLevel is set to Warning + /// Test to validate that the engine starts successfully when --log-level is set to Warning /// or above. At these levels, CLI phase messages (logged at Information) are suppressed, /// so no stdout output with message 'info' is expected during the CLI phase. /// @@ -871,7 +873,7 @@ public async Task TestEngineStartUpWithHighLogLevelOptions(string logLevelOption StringWriter consoleOutput = new(); Console.SetOut(consoleOutput); - string[] args = { "start", "--config", TEST_RUNTIME_CONFIG_FILE, "--LogLevel", logLevelOption }; + string[] args = { "start", "--config", TEST_RUNTIME_CONFIG_FILE, "--log-level", logLevelOption }; _fileSystem!.File.WriteAllText(TEST_RUNTIME_CONFIG_FILE, INITIAL_CONFIG); // Run Program.Execute on a background task because StartEngine blocks until the host shuts down. @@ -886,7 +888,7 @@ public async Task TestEngineStartUpWithHighLogLevelOptions(string logLevelOption } /// - /// Test to validate that the engine starts successfully when --LogLevel is set to None. + /// Test to validate that the engine starts successfully when --log-level is set to None. /// At these levels, CLI phase messages (logged at Information) are suppressed, /// so no stdout output is expected during the CLI phase. /// @@ -901,7 +903,7 @@ public async Task TestEngineStartUpWithLogLevelNone(string logLevelOption) StringWriter consoleOutput = new(); Console.SetOut(consoleOutput); - string[] args = { "start", "--config", TEST_RUNTIME_CONFIG_FILE, "--LogLevel", logLevelOption }; + string[] args = { "start", "--config", TEST_RUNTIME_CONFIG_FILE, "--log-level", logLevelOption }; _fileSystem!.File.WriteAllText(TEST_RUNTIME_CONFIG_FILE, INITIAL_CONFIG); // Run Program.Execute on a background task because StartEngine blocks until the host shuts down. @@ -915,12 +917,12 @@ public async Task TestEngineStartUpWithLogLevelNone(string logLevelOption) } /// Validates that `dab start` correctly sets - /// based on whether the --LogLevel CLI flag is provided. + /// based on whether the --log-level CLI flag is provided. /// - /// When the --LogLevel flag is provided, IsCliOverriding should be true. - /// When the --LogLevel flag is omitted (log level comes from the config file), IsCliOverriding should be false. + /// When the --log-level flag is provided, IsCliOverriding should be true. + /// When the --log-level flag is omitted (log level comes from the config file), IsCliOverriding should be false. /// - /// The --LogLevel CLI flag value, or null to omit the flag. + /// The --log-level CLI flag value, or null to omit the flag. /// Expected value of Startup.IsCliOverriding. [DataTestMethod] [DataRow(null, false, DisplayName = "IsCliOverriding is false")] @@ -975,6 +977,7 @@ public async Task TestStartCommandResolvesLogLevelFromConfigOrFlag( StartOptions options = new( verbose: false, logLevel: cliLogLevel, + logLevelLegacy: null, isHttpsRedirectionDisabled: false, mcpStdio: false, mcpRole: null, diff --git a/src/Cli/Commands/StartOptions.cs b/src/Cli/Commands/StartOptions.cs index 5857786790..2ba50d0c65 100644 --- a/src/Cli/Commands/StartOptions.cs +++ b/src/Cli/Commands/StartOptions.cs @@ -21,11 +21,12 @@ public class StartOptions : Options public LogBuffer CliBuffer { get; } - public StartOptions(bool verbose, LogLevel? logLevel, bool isHttpsRedirectionDisabled, bool mcpStdio, string? mcpRole, string config) + public StartOptions(bool verbose, LogLevel? logLevel, LogLevel? logLevelLegacy, bool isHttpsRedirectionDisabled, bool mcpStdio, string? mcpRole, string config) : base(config) { // When verbose is true we set LogLevel to information. LogLevel = verbose is true ? Microsoft.Extensions.Logging.LogLevel.Information : logLevel; + LogLevelLegacy = logLevelLegacy; IsHttpsRedirectionDisabled = isHttpsRedirectionDisabled; McpStdio = mcpStdio; McpRole = mcpRole; @@ -37,9 +38,12 @@ public StartOptions(bool verbose, LogLevel? logLevel, bool isHttpsRedirectionDis [Option("verbose", SetName = "verbose", Required = false, HelpText = "Specifies logging level as informational.")] public bool Verbose { get; } - [Option("LogLevel", SetName = "LogLevel", Required = false, HelpText = LOGLEVEL_HELPTEXT)] + [Option("log-level", SetName = "loglevel", Required = false, HelpText = LOGLEVEL_HELPTEXT)] public LogLevel? LogLevel { get; } + [Option("LogLevel", SetName = "LogLevel", Required = false, HelpText = LOGLEVEL_HELPTEXT, Hidden = true)] + public LogLevel? LogLevelLegacy { get; } + [Option("no-https-redirect", Required = false, HelpText = "Disables automatic https redirects.")] public bool IsHttpsRedirectionDisabled { get; } diff --git a/src/Cli/ConfigGenerator.cs b/src/Cli/ConfigGenerator.cs index ae3087debb..b0a333a912 100644 --- a/src/Cli/ConfigGenerator.cs +++ b/src/Cli/ConfigGenerator.cs @@ -3066,10 +3066,10 @@ public static bool TryStartEngineWithOptions(StartOptions options, FileSystemRun List args = new() { "--ConfigFileName", runtimeConfigFile }; - /// Add arguments for LogLevel. Only pass --LogLevel when user explicitly specified it, + /// Add arguments for LogLevel. Only pass --log-level when user explicitly specified it, /// so that MCP logging/setLevel can still adjust the level when no CLI override is present. /// - /// When --LogLevel is NOT specified: + /// When --log-level is NOT specified: /// - MCP stdio mode: Service defaults to None for clean stdout output /// - Non-MCP mode: Service defaults to Debug (Development) or Error (Production) based on config LogLevel minimumLogLevel; @@ -3079,19 +3079,30 @@ public static bool TryStartEngineWithOptions(StartOptions options, FileSystemRun Utils.IsConfigOverriding = false; Utils.ConfigLogLevel = LogLevel.Information; + LogLevel? logLevel = null; if (options.LogLevel is not null) { - if (options.LogLevel is < LogLevel.Trace or > LogLevel.None) + logLevel = options.LogLevel; + } + else if (options.LogLevelLegacy is not null) + { + options.CliBuffer.BufferLog(LogLevel.Warning, $"--LogLevel is deprecated, please use --log-level instead."); + logLevel = options.LogLevelLegacy; + } + + if (logLevel is not null) + { + if (logLevel is < LogLevel.Trace or > LogLevel.None) { options.CliBuffer.BufferLog(LogLevel.Error, - $"LogLevel's valid range is 0 to 6, your value: {options.LogLevel}, see: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.loglevel"); + $"LogLevel's valid range is 0 to 6, your value: {logLevel}, see: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.loglevel"); return false; } - minimumLogLevel = (LogLevel)options.LogLevel; - // Only add --LogLevel when user explicitly specified it via CLI. + minimumLogLevel = (LogLevel)logLevel; + // Only add --log-level when user explicitly specified it via CLI. // This allows MCP logging/setLevel to work when no CLI override is present. - args.Add("--LogLevel"); + args.Add("--log-level"); args.Add(minimumLogLevel.ToString()); } else @@ -3100,7 +3111,7 @@ public static bool TryStartEngineWithOptions(StartOptions options, FileSystemRun // Track whether config explicitly set a log level. In MCP stdio mode this // allows CLI logs to be emitted to stderr (instead of being suppressed) - // when the user expressed intent via the config file rather than --LogLevel. + // when the user expressed intent via the config file rather than --log-level. if (deserializedRuntimeConfig.HasExplicitLogLevel()) { Utils.IsConfigOverriding = true; diff --git a/src/Cli/CustomLoggerProvider.cs b/src/Cli/CustomLoggerProvider.cs index 80a046a042..a4625b0924 100644 --- a/src/Cli/CustomLoggerProvider.cs +++ b/src/Cli/CustomLoggerProvider.cs @@ -28,9 +28,9 @@ public class CustomConsoleLogger : ILogger private readonly LogLevel _minimumLogLevel; // Minimum LogLevel for CLI output. - // For MCP mode: prefer CLI's --LogLevel, fall back to config's log-level, otherwise suppress all. + // For MCP mode: prefer CLI's --log-level, fall back to config's log-level, otherwise suppress all. // For non-MCP mode: always use the level passed to the constructor. - // Note: --LogLevel is meant for the ENGINE's log level, not CLI's output. + // Note: --log-level is meant for the ENGINE's log level, not CLI's output. public CustomConsoleLogger(LogLevel minimumLogLevel = LogLevel.Information) { _minimumLogLevel = Cli.Utils.IsMcpStdioMode @@ -93,13 +93,13 @@ public CustomConsoleLogger(LogLevel minimumLogLevel = LogLevel.Information) /// /// Creates Log message by setting console message color based on LogLevel. /// In MCP stdio mode: - /// - If user explicitly set --LogLevel (CLI) or log-level (config): write to stderr (colored output) + /// - If user explicitly set --log-level (CLI) or log-level (config): write to stderr (colored output) /// - Otherwise: suppress entirely to keep stdout clean for JSON-RPC protocol. /// public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { // In MCP stdio mode, only output logs if user explicitly requested a log level - // via either the CLI --LogLevel flag or the runtime config file's log-level. + // via either the CLI --log-level flag or the runtime config file's log-level. // In that case, write to stderr to keep stdout clean for JSON-RPC. if (Cli.Utils.IsMcpStdioMode) { diff --git a/src/Cli/Exporter.cs b/src/Cli/Exporter.cs index 1a209e9e27..8c095817e8 100644 --- a/src/Cli/Exporter.cs +++ b/src/Cli/Exporter.cs @@ -113,6 +113,7 @@ private static async Task ExportGraphQL( StartOptions startOptions = new( verbose: false, logLevel: LogLevel.None, + logLevelLegacy: null, isHttpsRedirectionDisabled: false, config: options.Config!, mcpStdio: false, diff --git a/src/Cli/Program.cs b/src/Cli/Program.cs index 6eb89c60d8..a9d9247ebd 100644 --- a/src/Cli/Program.cs +++ b/src/Cli/Program.cs @@ -60,7 +60,7 @@ private static void ParseEarlyFlags(string[] args) { Utils.IsMcpStdioMode = true; } - else if (string.Equals(arg, "--LogLevel", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length) + else if (string.Equals(arg, "--log-level", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length) { Utils.IsCliOverriding = true; if (Enum.TryParse(args[i + 1], ignoreCase: true, out LogLevel cliLogLevel)) diff --git a/src/Cli/Utils.cs b/src/Cli/Utils.cs index 8f309407a8..9367f9260f 100644 --- a/src/Cli/Utils.cs +++ b/src/Cli/Utils.cs @@ -29,13 +29,13 @@ public class Utils public static bool IsMcpStdioMode { get; set; } /// - /// When true, the CLI is the source overriding the log level (i.e., --LogLevel was supplied). + /// When true, the CLI is the source overriding the log level (i.e., --log-level was supplied). /// This allows logs to be written to stderr instead of being completely suppressed. /// public static bool IsCliOverriding { get; set; } /// - /// The log level specified via CLI --LogLevel flag. + /// The log level specified via CLI --log-level flag. /// Only valid when IsCliOverriding is true. /// public static LogLevel CliLogLevel { get; set; } = LogLevel.Information; @@ -43,7 +43,7 @@ public class Utils /// /// When true, the runtime config is the source overriding the log level /// (i.e., runtime.telemetry.log-level was explicitly set). - /// This allows CLI logs to be written to stderr in MCP mode even when no --LogLevel flag was provided. + /// This allows CLI logs to be written to stderr in MCP mode even when no --log-level flag was provided. /// public static bool IsConfigOverriding { get; set; } diff --git a/src/Core/Telemetry/ILogLevelController.cs b/src/Core/Telemetry/ILogLevelController.cs index 8a8ff0d673..87d6d958d5 100644 --- a/src/Core/Telemetry/ILogLevelController.cs +++ b/src/Core/Telemetry/ILogLevelController.cs @@ -12,7 +12,7 @@ public interface ILogLevelController { /// /// Gets a value indicating whether the CLI is the source overriding the log level - /// (i.e., --LogLevel was supplied). When true, runtime-config (hot-reload) + /// (i.e., --log-level was supplied). When true, runtime-config (hot-reload) /// updates are ignored. /// bool IsCliOverriding { get; } @@ -35,7 +35,7 @@ public interface ILogLevelController /// The MCP level string is mapped to the appropriate LogLevel. /// Log-level precedence (highest to lowest): /// 1. Agent (MCP logging/setLevel) — always wins. - /// 2. CLI --LogLevel flag. + /// 2. CLI --log-level flag. /// 3. Config runtime.telemetry.log-level. /// 4. Defaults. /// diff --git a/src/Service.Tests/UnitTests/DynamicLogLevelProviderTests.cs b/src/Service.Tests/UnitTests/DynamicLogLevelProviderTests.cs index c74963778e..57e79ff138 100644 --- a/src/Service.Tests/UnitTests/DynamicLogLevelProviderTests.cs +++ b/src/Service.Tests/UnitTests/DynamicLogLevelProviderTests.cs @@ -175,7 +175,7 @@ public void UpdateFromRuntimeConfig_RespectsAgentOverride() /// /// Hot-reloading the runtime config must not overwrite a CLI-set level. The CLI - /// --LogLevel flag is the operator's deliberate startup choice, so a + /// --log-level flag is the operator's deliberate startup choice, so a /// subsequent with a /// different config-pinned level must be ignored. /// diff --git a/src/Service/Program.cs b/src/Service/Program.cs index e83dde30c7..1bc67f0acf 100644 --- a/src/Service/Program.cs +++ b/src/Service/Program.cs @@ -222,9 +222,9 @@ public static IHostBuilder CreateHostBuilder(string[] args, bool runMcpStdio, st /// /// Extracts the log level from the command line arguments and optionally from config. - /// When --LogLevel is present, returns that value with CLI override flag set. - /// When in MCP stdio mode without explicit --LogLevel, reads the config file to check for log level. - /// When in normal mode without explicit --LogLevel, defaults to Error (UpdateFromRuntimeConfig() + /// When --log-level is present, returns that value with CLI override flag set. + /// When in MCP stdio mode without explicit --log-level, reads the config file to check for log level. + /// When in normal mode without explicit --log-level, defaults to Error (UpdateFromRuntimeConfig() /// will later adjust based on config: Debug for Development mode, Error for Production mode). /// /// Array that may contain log level information. @@ -237,19 +237,19 @@ private static LogLevel GetLogLevelFromCommandLineArgsOrConfig(string[] args, bo LogLevel logLevel; isConfigOverriding = false; - // Check if --LogLevel was explicitly specified via CLI (case-insensitive parsing) - int logLevelIndex = Array.FindIndex(args, a => string.Equals(a, "--LogLevel", StringComparison.OrdinalIgnoreCase)); + // Check if --log-level was explicitly specified via CLI (case-insensitive parsing) + int logLevelIndex = Array.FindIndex(args, a => string.Equals(a, "--log-level", StringComparison.OrdinalIgnoreCase)); bool hasCliLogLevel = logLevelIndex >= 0 && logLevelIndex + 1 < args.Length; if (hasCliLogLevel && Enum.TryParse(args[logLevelIndex + 1], ignoreCase: true, out LogLevel cliLogLevel)) { - // User explicitly set --LogLevel via CLI (highest priority) + // User explicitly set --log-level via CLI (highest priority) logLevel = cliLogLevel; isCliOverriding = true; } else if (runMcpStdio) { - // MCP stdio mode without explicit --LogLevel: check config for log level (second priority) + // MCP stdio mode without explicit --log-level: check config for log level (second priority) isCliOverriding = false; logLevel = LogLevel.None; // Default if config doesn't have log level @@ -269,7 +269,7 @@ private static LogLevel GetLogLevelFromCommandLineArgsOrConfig(string[] args, bo } else { - // Normal (non-MCP) mode without explicit --LogLevel: + // Normal (non-MCP) mode without explicit --log-level: // Start with Error as fallback. UpdateFromRuntimeConfig() will later // adjust based on config: Debug for Development mode, Error for Production mode. // This initial value is used before config is loaded. diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs index 876b63378e..b4a2ae6529 100644 --- a/src/Service/Startup.cs +++ b/src/Service/Startup.cs @@ -316,7 +316,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); - // ILogger explicit creation required for logger to use --LogLevel startup argument specified. + // ILogger explicit creation required for logger to use --log-level startup argument specified. services.AddSingleton>(implementationFactory: (serviceProvider) => { LogLevelInitializer logLevelInit = new(MinimumLogLevel, typeof(BasicHealthReportResponseWriter).FullName, _configProvider, _hotReloadEventHandler); @@ -324,7 +324,7 @@ public void ConfigureServices(IServiceCollection services) return loggerFactory.CreateLogger(); }); - // ILogger explicit creation required for logger to use --LogLevel startup argument specified. + // ILogger explicit creation required for logger to use --log-level startup argument specified. services.AddSingleton>(implementationFactory: (serviceProvider) => { LogLevelInitializer logLevelInit = new(MinimumLogLevel, typeof(ComprehensiveHealthReportResponseWriter).FullName, _configProvider, _hotReloadEventHandler); @@ -332,7 +332,7 @@ public void ConfigureServices(IServiceCollection services) return loggerFactory.CreateLogger(); }); - // ILogger explicit creation required for logger to use --LogLevel startup argument specified. + // ILogger explicit creation required for logger to use --log-level startup argument specified. services.AddSingleton>(implementationFactory: (serviceProvider) => { LogLevelInitializer logLevelInit = new(MinimumLogLevel, typeof(HealthCheckHelper).FullName, _configProvider, _hotReloadEventHandler); @@ -340,7 +340,7 @@ public void ConfigureServices(IServiceCollection services) return loggerFactory.CreateLogger(); }); - // ILogger explicit creation required for logger to use --LogLevel startup argument specified. + // ILogger explicit creation required for logger to use --log-level startup argument specified. services.AddSingleton>(implementationFactory: (serviceProvider) => { LogLevelInitializer logLevelInit = new(MinimumLogLevel, typeof(HttpUtilities).FullName, _configProvider, _hotReloadEventHandler);