From 2a2d74cb50a1c0dd7f1ffab07f4a3afac00eb9b2 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Fri, 5 Jun 2026 13:46:56 -0700 Subject: [PATCH 1/7] Change LogLevel to log-level --- .../Core/McpStdioServer.cs | 4 +- src/Cli.Tests/CustomLoggerTests.cs | 6 +-- src/Cli.Tests/EndToEndTests.cs | 38 ++++++++++--------- src/Cli/Commands/StartOptions.cs | 8 +++- src/Cli/ConfigGenerator.cs | 27 +++++++++---- src/Cli/CustomLoggerProvider.cs | 8 ++-- src/Cli/Program.cs | 2 +- src/Cli/Utils.cs | 6 +-- src/Core/Telemetry/ILogLevelController.cs | 4 +- .../UnitTests/DynamicLogLevelProviderTests.cs | 2 +- src/Service/Program.cs | 16 ++++---- src/Service/Startup.cs | 8 ++-- 12 files changed, 73 insertions(+), 56 deletions(-) diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs index 690098384b..973189ab74 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) { 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..3d977ab012 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")] 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/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); From eeea9b8bff96f0a0cdc907bb0d9c1b01e31a0905 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Fri, 5 Jun 2026 14:29:12 -0700 Subject: [PATCH 2/7] Fix issues --- src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs | 2 +- src/Cli.Tests/EndToEndTests.cs | 1 + src/Cli/Exporter.cs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs index 973189ab74..9c65d2a58d 100644 --- a/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs +++ b/src/Azure.DataApiBuilder.Mcp/Core/McpStdioServer.cs @@ -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/EndToEndTests.cs b/src/Cli.Tests/EndToEndTests.cs index 3d977ab012..2a1e963231 100644 --- a/src/Cli.Tests/EndToEndTests.cs +++ b/src/Cli.Tests/EndToEndTests.cs @@ -977,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/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, From cde5d8ff39ba1a301d4f7f24e7a35180386588fe Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 8 Jun 2026 09:43:24 -0700 Subject: [PATCH 3/7] Address comments based on copilot --- src/Cli.Tests/EndToEndTests.cs | 4 ++-- src/Cli/Properties/launchSettings.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Cli.Tests/EndToEndTests.cs b/src/Cli.Tests/EndToEndTests.cs index 2a1e963231..f68b1f1346 100644 --- a/src/Cli.Tests/EndToEndTests.cs +++ b/src/Cli.Tests/EndToEndTests.cs @@ -831,8 +831,8 @@ public Task TestUpdatingStoredProcedureWithRestMethods() [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.")] + [DataRow("--LogLevel 0", DisplayName = "Case sensitivity: LogLevel 0 legacy from command line.")] + [DataRow("--LogLevel information", DisplayName = "Case sensitivity: LogLevel Information legacy from command line.")] public void TestEngineStartUpWithVerboseAndLogLevelOptions(string logLevelOption) { _fileSystem!.File.WriteAllText(TEST_RUNTIME_CONFIG_FILE, INITIAL_CONFIG); diff --git a/src/Cli/Properties/launchSettings.json b/src/Cli/Properties/launchSettings.json index 43a4439fdb..4d1b2ec731 100644 --- a/src/Cli/Properties/launchSettings.json +++ b/src/Cli/Properties/launchSettings.json @@ -2,8 +2,8 @@ "profiles": { "Cli": { "commandName": "Project", - "commandLineArgs": "start", + "commandLineArgs": "validate --config C:\\Users\\rcernaserna\\DAB\\data-api-builder\\src\\Service\\dab-config.MsSql.json", "httpPort": 5002 } } -} +} \ No newline at end of file From ab36b24083c471027fc1e16e0215602d7b901920 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 8 Jun 2026 10:03:49 -0700 Subject: [PATCH 4/7] Changes based on copilot --- src/Cli/Properties/launchSettings.json | 2 +- src/Service/Program.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Cli/Properties/launchSettings.json b/src/Cli/Properties/launchSettings.json index 4d1b2ec731..86425879c7 100644 --- a/src/Cli/Properties/launchSettings.json +++ b/src/Cli/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Cli": { "commandName": "Project", - "commandLineArgs": "validate --config C:\\Users\\rcernaserna\\DAB\\data-api-builder\\src\\Service\\dab-config.MsSql.json", + "commandLineArgs": "start", "httpPort": 5002 } } diff --git a/src/Service/Program.cs b/src/Service/Program.cs index 1bc67f0acf..c2315900ef 100644 --- a/src/Service/Program.cs +++ b/src/Service/Program.cs @@ -237,8 +237,10 @@ private static LogLevel GetLogLevelFromCommandLineArgsOrConfig(string[] args, bo LogLevel logLevel; isConfigOverriding = false; - // 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)); + // Check if --log-level or --LogLevel was explicitly specified via CLI (case-insensitive parsing) + int logLevelIndex = Array.FindIndex(args, a => + string.Equals(a, "--log-level", StringComparison.OrdinalIgnoreCase) || + string.Equals(a, "--LogLevel", StringComparison.OrdinalIgnoreCase)); bool hasCliLogLevel = logLevelIndex >= 0 && logLevelIndex + 1 < args.Length; if (hasCliLogLevel && Enum.TryParse(args[logLevelIndex + 1], ignoreCase: true, out LogLevel cliLogLevel)) From b4bd53bd7d398060c05992e61a2f01648d07e67d Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 8 Jun 2026 10:07:36 -0700 Subject: [PATCH 5/7] Restore file to original settings --- src/Cli/Properties/launchSettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cli/Properties/launchSettings.json b/src/Cli/Properties/launchSettings.json index 86425879c7..43a4439fdb 100644 --- a/src/Cli/Properties/launchSettings.json +++ b/src/Cli/Properties/launchSettings.json @@ -6,4 +6,4 @@ "httpPort": 5002 } } -} \ No newline at end of file +} From 98346bf035df3a8e0b1fdea06b7d82c364afa4af Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Wed, 10 Jun 2026 13:11:23 -0700 Subject: [PATCH 6/7] Changes based on comments --- src/Cli.Tests/EndToEndTests.cs | 2 +- src/Cli/Commands/StartOptions.cs | 8 ++++---- src/Cli/Exporter.cs | 4 ++-- src/Service/Program.cs | 17 ++++++++++++++--- src/Service/Startup.cs | 5 +++++ .../Telemetry/DynamicLogLevelProvider.cs | 5 ++++- 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/Cli.Tests/EndToEndTests.cs b/src/Cli.Tests/EndToEndTests.cs index f68b1f1346..f625dba101 100644 --- a/src/Cli.Tests/EndToEndTests.cs +++ b/src/Cli.Tests/EndToEndTests.cs @@ -977,10 +977,10 @@ public async Task TestStartCommandResolvesLogLevelFromConfigOrFlag( StartOptions options = new( verbose: false, logLevel: cliLogLevel, - logLevelLegacy: null, isHttpsRedirectionDisabled: false, mcpStdio: false, mcpRole: null, + logLevelLegacy: null, config: TEST_RUNTIME_CONFIG_FILE); // Run TryStartEngineWithOptions on a background task because StartEngine blocks until the host shuts down. diff --git a/src/Cli/Commands/StartOptions.cs b/src/Cli/Commands/StartOptions.cs index 2ba50d0c65..64b92f4e89 100644 --- a/src/Cli/Commands/StartOptions.cs +++ b/src/Cli/Commands/StartOptions.cs @@ -21,7 +21,7 @@ public class StartOptions : Options public LogBuffer CliBuffer { get; } - public StartOptions(bool verbose, LogLevel? logLevel, LogLevel? logLevelLegacy, bool isHttpsRedirectionDisabled, bool mcpStdio, string? mcpRole, string config) + public StartOptions(bool verbose, LogLevel? logLevel, bool isHttpsRedirectionDisabled, bool mcpStdio, string? mcpRole, LogLevel? logLevelLegacy, string config) : base(config) { // When verbose is true we set LogLevel to information. @@ -41,9 +41,6 @@ public StartOptions(bool verbose, LogLevel? logLevel, LogLevel? logLevelLegacy, [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; } @@ -53,6 +50,9 @@ public StartOptions(bool verbose, LogLevel? logLevel, LogLevel? logLevelLegacy, [Value(0, MetaName = "role", Required = false, HelpText = "Optional MCP permissions role, e.g. role:anonymous. If omitted, defaults to anonymous.")] public string? McpRole { get; } + [Option("LogLevel", SetName = "LogLevel", Required = false, HelpText = LOGLEVEL_HELPTEXT, Hidden = true)] + public LogLevel? LogLevelLegacy { get; } + public int Handler(ILogger logger, FileSystemRuntimeConfigLoader loader, IFileSystem fileSystem) { CliBuffer.BufferLog(Microsoft.Extensions.Logging.LogLevel.Information, $"{PRODUCT_NAME} {ProductInfo.GetProductVersion()}"); diff --git a/src/Cli/Exporter.cs b/src/Cli/Exporter.cs index 8c095817e8..9d6ea82801 100644 --- a/src/Cli/Exporter.cs +++ b/src/Cli/Exporter.cs @@ -113,11 +113,11 @@ private static async Task ExportGraphQL( StartOptions startOptions = new( verbose: false, logLevel: LogLevel.None, - logLevelLegacy: null, isHttpsRedirectionDisabled: false, config: options.Config!, mcpStdio: false, - mcpRole: null); + mcpRole: null, + logLevelLegacy: null); Task dabService = Task.Run(() => { diff --git a/src/Service/Program.cs b/src/Service/Program.cs index c2315900ef..fe41200fe3 100644 --- a/src/Service/Program.cs +++ b/src/Service/Program.cs @@ -98,9 +98,9 @@ public static bool StartEngine(string[] args, bool runMcpStdio, string? mcpRole) // Initialize log level EARLY, before building the host. // This ensures logging filters are effective during the entire host build process. // For MCP mode, we also read the config file early to check for log level override. - LogLevel initialLogLevel = GetLogLevelFromCommandLineArgsOrConfig(args, runMcpStdio, out bool isCliOverriding, out bool isConfigOverriding); + LogLevel initialLogLevel = GetLogLevelFromCommandLineArgsOrConfig(args, runMcpStdio, out bool isCliOverriding, out bool isConfigOverriding, out bool isLogLevelLegacy); - LogLevelProvider.SetInitialLogLevel(initialLogLevel, isCliOverriding, isConfigOverriding); + LogLevelProvider.SetInitialLogLevel(initialLogLevel, isCliOverriding, isConfigOverriding, isLogLevelLegacy); // For MCP stdio mode, redirect Console.Out to keep stdout clean for JSON-RPC. // MCP SDK uses Console.OpenStandardOutput() which gets the real stdout, unaffected by this redirect. @@ -232,12 +232,23 @@ public static IHostBuilder CreateHostBuilder(string[] args, bool runMcpStdio, st /// Set to true if log level is supplied via CLI args. /// Set to true if log level is supplied via the config file (MCP mode only). /// Appropriate log level. - private static LogLevel GetLogLevelFromCommandLineArgsOrConfig(string[] args, bool runMcpStdio, out bool isCliOverriding, out bool isConfigOverriding) + private static LogLevel GetLogLevelFromCommandLineArgsOrConfig(string[] args, bool runMcpStdio, out bool isCliOverriding, out bool isConfigOverriding, out bool isLogLevelLegacy) { LogLevel logLevel; isConfigOverriding = false; + args[2] = "--LogLevel"; // Check if --log-level or --LogLevel was explicitly specified via CLI (case-insensitive parsing) + isLogLevelLegacy = false; + foreach (string argument in args) + { + if (string.Equals(argument, "--LogLevel", StringComparison.OrdinalIgnoreCase)) + { + isLogLevelLegacy = true; + break; + } + } + int logLevelIndex = Array.FindIndex(args, a => string.Equals(a, "--log-level", StringComparison.OrdinalIgnoreCase) || string.Equals(a, "--LogLevel", StringComparison.OrdinalIgnoreCase)); diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs index b4a2ae6529..5e87af61a7 100644 --- a/src/Service/Startup.cs +++ b/src/Service/Startup.cs @@ -814,6 +814,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, RuntimeC // arrive before runtime config in the late-configured path) still surfaces its audit // log line through the standard logging pipeline. DynamicLogLevelProvider? logLevelProviderForAudit = app.ApplicationServices.GetService(); + if (logLevelProviderForAudit?.IsLogLevelLegacy is true) + { + _logBuffer.BufferLog(LogLevel.Warning, "--LogLevel is deprecated, please use --log-level instead."); + } + ILoggerFactory? earlyLoggerFactory = app.ApplicationServices.GetService(); if (logLevelProviderForAudit is not null && earlyLoggerFactory is not null) { diff --git a/src/Service/Telemetry/DynamicLogLevelProvider.cs b/src/Service/Telemetry/DynamicLogLevelProvider.cs index b405caa7f0..b36c8e0e14 100644 --- a/src/Service/Telemetry/DynamicLogLevelProvider.cs +++ b/src/Service/Telemetry/DynamicLogLevelProvider.cs @@ -25,6 +25,8 @@ public class DynamicLogLevelProvider : ILogLevelController public bool IsAgentOverriding { get; private set; } + public bool IsLogLevelLegacy { get; private set; } + /// /// Optional logger used to emit an Information line when the agent successfully overrides /// the log level. Wired by host startup once the logging pipeline is available; safe to @@ -41,13 +43,14 @@ public class DynamicLogLevelProvider : ILogLevelController /// The initial log level to set. /// Indicates whether the CLI is overriding the log level. /// Indicates whether the runtime config is overriding the log level. - public void SetInitialLogLevel(LogLevel logLevel = LogLevel.Error, bool isCliOverriding = false, bool isConfigOverriding = false) + public void SetInitialLogLevel(LogLevel logLevel = LogLevel.Error, bool isCliOverriding = false, bool isConfigOverriding = false, bool isLogLevelLegacy = false) { lock (_stateLock) { CurrentLogLevel = logLevel; IsCliOverriding = isCliOverriding; IsConfigOverriding = isConfigOverriding; + IsLogLevelLegacy = isLogLevelLegacy; } } From 861ccd9d605639b114590b50468834e3a92c0e63 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Wed, 10 Jun 2026 13:17:22 -0700 Subject: [PATCH 7/7] Remove unecessary addition --- src/Service/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Service/Program.cs b/src/Service/Program.cs index fe41200fe3..141b3890b4 100644 --- a/src/Service/Program.cs +++ b/src/Service/Program.cs @@ -237,7 +237,6 @@ private static LogLevel GetLogLevelFromCommandLineArgsOrConfig(string[] args, bo LogLevel logLevel; isConfigOverriding = false; - args[2] = "--LogLevel"; // Check if --log-level or --LogLevel was explicitly specified via CLI (case-insensitive parsing) isLogLevelLegacy = false; foreach (string argument in args)