-
Notifications
You must be signed in to change notification settings - Fork 329
Implemented MCP Set Log Level #3419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2582,7 +2582,8 @@ public static bool TryStartEngineWithOptions(StartOptions options, FileSystemRun | |
| List<string> args = new() | ||
| { "--ConfigFileName", runtimeConfigFile }; | ||
|
|
||
| /// Add arguments for LogLevel. Checks if LogLevel is overridden with option `--LogLevel`. | ||
| /// Add arguments for LogLevel. Only pass --LogLevel when user explicitly specified it, | ||
| /// so that MCP logging/setLevel can still adjust the level when no CLI override is present. | ||
| /// If not provided, Default minimum LogLevel is Debug for Development mode and Error for Production mode. | ||
| LogLevel minimumLogLevel; | ||
| if (options.LogLevel is not null) | ||
|
|
@@ -2597,17 +2598,22 @@ public static bool TryStartEngineWithOptions(StartOptions options, FileSystemRun | |
|
|
||
| minimumLogLevel = (LogLevel)options.LogLevel; | ||
| _logger.LogInformation("Setting minimum LogLevel: {minimumLogLevel}.", minimumLogLevel); | ||
|
|
||
| // Only add --LogLevel 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(minimumLogLevel.ToString()); | ||
| } | ||
| else | ||
| { | ||
| minimumLogLevel = deserializedRuntimeConfig.GetConfiguredLogLevel(); | ||
| HostMode hostModeType = deserializedRuntimeConfig.IsDevelopmentMode() ? HostMode.Development : HostMode.Production; | ||
|
|
||
| _logger.LogInformation($"Setting default minimum LogLevel: {minimumLogLevel} for {hostModeType} mode.", minimumLogLevel, hostModeType); | ||
| } | ||
|
|
||
| args.Add("--LogLevel"); | ||
| args.Add(minimumLogLevel.ToString()); | ||
| // Don't add --LogLevel arg since user didn't explicitly set it. | ||
| // Service will determine default log level based on config or host mode. | ||
| } | ||
|
Comment on lines
2585
to
+2616
|
||
|
|
||
| // This will add args to disable automatic redirects to https if specified by user | ||
| if (options.IsHttpsRedirectionDisabled) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| namespace Azure.DataApiBuilder.Core.Telemetry | ||
| { | ||
| /// <summary> | ||
| /// Interface for controlling log levels dynamically at runtime. | ||
| /// This allows MCP and other components to adjust logging without | ||
| /// direct coupling to the concrete implementation. | ||
| /// </summary> | ||
| public interface ILogLevelController | ||
| { | ||
| /// <summary> | ||
| /// Gets a value indicating whether the log level was overridden by CLI arguments. | ||
| /// When true, MCP and config-based log level changes are ignored. | ||
| /// </summary> | ||
| bool IsCliOverridden { get; } | ||
|
|
||
| /// <summary> | ||
| /// Gets a value indicating whether the log level was explicitly set in the config file. | ||
| /// When true along with IsCliOverridden being false, MCP log level changes are ignored. | ||
| /// </summary> | ||
| bool IsConfigOverridden { get; } | ||
|
|
||
| /// <summary> | ||
| /// Updates the log level from an MCP logging/setLevel request. | ||
| /// The MCP level string is mapped to the appropriate LogLevel. | ||
| /// Log level precedence (highest to lowest): | ||
| /// 1. CLI --LogLevel flag (IsCliOverridden = true) | ||
| /// 2. Config runtime.telemetry.log-level (IsConfigOverridden = true) | ||
| /// 3. MCP logging/setLevel (only works if neither CLI nor Config set a level) | ||
| /// </summary> | ||
| /// <param name="mcpLevel">The MCP log level string (e.g., "debug", "info", "warning", "error").</param> | ||
| /// <returns>True if the level was changed; false if CLI or Config override prevented the change.</returns> | ||
| bool UpdateFromMcp(string mcpLevel); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| #nullable enable | ||
|
|
||
| using Azure.DataApiBuilder.Service.Telemetry; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
|
||
| namespace Azure.DataApiBuilder.Service.Tests.UnitTests | ||
| { | ||
| /// <summary> | ||
| /// Unit tests for the DynamicLogLevelProvider class. | ||
| /// Tests the MCP logging/setLevel support. | ||
| /// </summary> | ||
| [TestClass] | ||
| public class DynamicLogLevelProviderTests | ||
| { | ||
| [TestMethod] | ||
| public void UpdateFromMcp_ValidLevel_ChangesLogLevel() | ||
| { | ||
| // Arrange | ||
| DynamicLogLevelProvider provider = new(); | ||
| provider.SetInitialLogLevel(LogLevel.Error, isCliOverridden: false); | ||
|
|
||
| // Act | ||
| bool result = provider.UpdateFromMcp("debug"); | ||
|
|
||
| // Assert | ||
| Assert.IsTrue(result); | ||
| Assert.AreEqual(LogLevel.Debug, provider.CurrentLogLevel); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public void UpdateFromMcp_CliOverridden_DoesNotChangeLogLevel() | ||
| { | ||
| // Arrange | ||
| DynamicLogLevelProvider provider = new(); | ||
| provider.SetInitialLogLevel(LogLevel.Error, isCliOverridden: true); | ||
|
|
||
| // Act | ||
| bool result = provider.UpdateFromMcp("debug"); | ||
|
|
||
| // Assert | ||
| Assert.IsFalse(result); | ||
| Assert.AreEqual(LogLevel.Error, provider.CurrentLogLevel); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public void UpdateFromMcp_ConfigOverridden_DoesNotChangeLogLevel() | ||
| { | ||
| // Arrange | ||
| DynamicLogLevelProvider provider = new(); | ||
| provider.SetInitialLogLevel(LogLevel.Warning, isCliOverridden: false, isConfigOverridden: true); | ||
|
|
||
| // Act | ||
| bool result = provider.UpdateFromMcp("debug"); | ||
|
|
||
| // Assert | ||
| Assert.IsFalse(result); | ||
| Assert.AreEqual(LogLevel.Warning, provider.CurrentLogLevel); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public void UpdateFromMcp_InvalidLevel_ReturnsFalse() | ||
| { | ||
| // Arrange | ||
| DynamicLogLevelProvider provider = new(); | ||
| provider.SetInitialLogLevel(LogLevel.Error, isCliOverridden: false); | ||
|
|
||
| // Act | ||
| bool result = provider.UpdateFromMcp("invalid"); | ||
|
|
||
| // Assert | ||
| Assert.IsFalse(result); | ||
| Assert.AreEqual(LogLevel.Error, provider.CurrentLogLevel); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public void ShouldLog_ReturnsCorrectResult() | ||
| { | ||
| // Arrange | ||
| DynamicLogLevelProvider provider = new(); | ||
| provider.SetInitialLogLevel(LogLevel.Warning, isCliOverridden: false); | ||
|
|
||
| // Assert - logs at or above Warning should pass | ||
| Assert.IsTrue(provider.ShouldLog(LogLevel.Warning)); | ||
| Assert.IsTrue(provider.ShouldLog(LogLevel.Error)); | ||
| Assert.IsFalse(provider.ShouldLog(LogLevel.Debug)); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,43 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using Azure.DataApiBuilder.Config.ObjectModel; | ||
| using Azure.DataApiBuilder.Core.Telemetry; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace Azure.DataApiBuilder.Service.Telemetry | ||
| { | ||
| public class DynamicLogLevelProvider | ||
| /// <summary> | ||
| /// Provides dynamic log level control with support for CLI override, runtime config, and MCP. | ||
| /// </summary> | ||
| public class DynamicLogLevelProvider : ILogLevelController | ||
| { | ||
| /// <summary> | ||
| /// Maps MCP log level strings to Microsoft.Extensions.Logging.LogLevel. | ||
| /// MCP levels: debug, info, notice, warning, error, critical, alert, emergency. | ||
| /// </summary> | ||
| private static readonly Dictionary<string, LogLevel> _mcpLevelMapping = new(StringComparer.OrdinalIgnoreCase) | ||
| { | ||
| ["debug"] = LogLevel.Debug, | ||
| ["info"] = LogLevel.Information, | ||
| ["notice"] = LogLevel.Information, // MCP "notice" maps to Information (no direct equivalent) | ||
| ["warning"] = LogLevel.Warning, | ||
| ["error"] = LogLevel.Error, | ||
| ["critical"] = LogLevel.Critical, | ||
| ["alert"] = LogLevel.Critical, // MCP "alert" maps to Critical | ||
| ["emergency"] = LogLevel.Critical // MCP "emergency" maps to Critical | ||
| }; | ||
|
|
||
| public LogLevel CurrentLogLevel { get; private set; } | ||
|
|
||
| public bool IsCliOverridden { get; private set; } | ||
|
|
||
| public void SetInitialLogLevel(LogLevel logLevel = LogLevel.Error, bool isCliOverridden = false) | ||
| public bool IsConfigOverridden { get; private set; } | ||
|
|
||
| public void SetInitialLogLevel(LogLevel logLevel = LogLevel.Error, bool isCliOverridden = false, bool isConfigOverridden = false) | ||
| { | ||
| CurrentLogLevel = logLevel; | ||
| IsCliOverridden = isCliOverridden; | ||
| IsConfigOverridden = isConfigOverridden; | ||
| } | ||
|
|
||
| public void UpdateFromRuntimeConfig(RuntimeConfig runtimeConfig) | ||
|
|
@@ -20,7 +46,52 @@ public void UpdateFromRuntimeConfig(RuntimeConfig runtimeConfig) | |
| if (!IsCliOverridden) | ||
| { | ||
| CurrentLogLevel = runtimeConfig.GetConfiguredLogLevel(); | ||
|
|
||
| // Track if config explicitly set a log level (not just using defaults) | ||
| IsConfigOverridden = !runtimeConfig.IsLogLevelNull(); | ||
| } | ||
|
Comment on lines
46
to
+52
|
||
| } | ||
|
|
||
| /// <summary> | ||
| /// Updates the log level from an MCP logging/setLevel request. | ||
| /// Precedence (highest to lowest): | ||
| /// 1. CLI --LogLevel flag (IsCliOverridden = true) | ||
| /// 2. Config runtime.telemetry.log-level (IsConfigOverridden = true) | ||
| /// 3. MCP logging/setLevel | ||
| /// | ||
| /// If CLI or Config overrode, this method accepts the request silently but does not change the level. | ||
| /// </summary> | ||
| /// <param name="mcpLevel">The MCP log level string (e.g., "debug", "info", "warning", "error").</param> | ||
| /// <returns>True if the level was changed; false if CLI/Config override prevented the change or level was invalid.</returns> | ||
| public bool UpdateFromMcp(string mcpLevel) | ||
| { | ||
| // If CLI overrode the log level, accept the request but don't change anything. | ||
| // This prevents MCP clients from getting errors, but CLI wins. | ||
| if (IsCliOverridden) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| // If Config explicitly set the log level, accept the request but don't change anything. | ||
| // Config has second precedence after CLI. | ||
| if (IsConfigOverridden) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| if (string.IsNullOrWhiteSpace(mcpLevel)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| if (_mcpLevelMapping.TryGetValue(mcpLevel, out LogLevel logLevel)) | ||
| { | ||
| CurrentLogLevel = logLevel; | ||
| return true; | ||
| } | ||
|
|
||
| // Unknown level - don't change, but don't fail either | ||
| return false; | ||
| } | ||
|
|
||
| public bool ShouldLog(LogLevel logLevel) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the default log-level branch,
_logger.LogInformation($"Setting default minimum LogLevel: {minimumLogLevel} for {hostModeType} mode.", minimumLogLevel, hostModeType);uses an interpolated string while also passing structured args—those args won’t be captured as structured fields. Use a message template without$"..."(or remove the extra args) so logging behaves as intended.