diff --git a/src/Cli.Tests/EndToEndTests.cs b/src/Cli.Tests/EndToEndTests.cs
index 99a9b77b6e..5b56a1d3c1 100644
--- a/src/Cli.Tests/EndToEndTests.cs
+++ b/src/Cli.Tests/EndToEndTests.cs
@@ -810,8 +810,9 @@ public Task TestUpdatingStoredProcedureWithRestMethods()
///
/// Test to validate that the engine starts successfully when --verbose and --LogLevel
- /// options are used with the start command
- /// This test does not validate whether the engine logs messages at the specified log level
+ /// options are used with the start command, for log levels at or below Information.
+ /// CLI phase messages (version info, config path) are expected in the output because
+ /// they are logged at Information level.
///
/// Log level options
[DataTestMethod]
@@ -820,24 +821,12 @@ public Task TestUpdatingStoredProcedureWithRestMethods()
[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 3", DisplayName = "LogLevel 3 from command line.")]
- [DataRow("--LogLevel 4", DisplayName = "LogLevel 4 from command line.")]
- [DataRow("--LogLevel 5", DisplayName = "LogLevel 5 from command line.")]
- [DataRow("--LogLevel 6", DisplayName = "LogLevel 6 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 Warning", DisplayName = "LogLevel Warning from command line.")]
- [DataRow("--LogLevel Error", DisplayName = "LogLevel Error from command line.")]
- [DataRow("--LogLevel Critical", DisplayName = "LogLevel Critical from command line.")]
- [DataRow("--LogLevel None", DisplayName = "LogLevel None 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("--LogLevel waRNing", DisplayName = "Case sensitivity: LogLevel Warning from command line.")]
- [DataRow("--LogLevel eRROR", DisplayName = "Case sensitivity: LogLevel Error from command line.")]
- [DataRow("--LogLevel CrItIcal", DisplayName = "Case sensitivity: LogLevel Critical from command line.")]
- [DataRow("--LogLevel NONE", DisplayName = "Case sensitivity: LogLevel None from command line.")]
public void TestEngineStartUpWithVerboseAndLogLevelOptions(string logLevelOption)
{
_fileSystem!.File.WriteAllText(TEST_RUNTIME_CONFIG_FILE, INITIAL_CONFIG);
@@ -856,6 +845,60 @@ public void TestEngineStartUpWithVerboseAndLogLevelOptions(string logLevelOption
StringAssert.Contains(output, $"User provided config file: {TEST_RUNTIME_CONFIG_FILE}", StringComparison.Ordinal);
}
+ ///
+ /// Test to validate that the engine starts successfully when --LogLevel is set to Warning
+ /// or above. At these levels, CLI phase messages (logged at Information) are suppressed,
+ /// so no stdout output is expected during the CLI phase.
+ ///
+ /// Log level options
+ [DataTestMethod]
+ [DataRow("--LogLevel 3", DisplayName = "LogLevel 3 from command line.")]
+ [DataRow("--LogLevel 4", DisplayName = "LogLevel 4 from command line.")]
+ [DataRow("--LogLevel 5", DisplayName = "LogLevel 5 from command line.")]
+ [DataRow("--LogLevel 6", DisplayName = "LogLevel 6 from command line.")]
+ [DataRow("--LogLevel Warning", DisplayName = "LogLevel Warning from command line.")]
+ [DataRow("--LogLevel Error", DisplayName = "LogLevel Error from command line.")]
+ [DataRow("--LogLevel Critical", DisplayName = "LogLevel Critical from command line.")]
+ [DataRow("--LogLevel None", DisplayName = "LogLevel None from command line.")]
+ [DataRow("--LogLevel waRNing", DisplayName = "Case sensitivity: LogLevel Warning from command line.")]
+ [DataRow("--LogLevel eRROR", DisplayName = "Case sensitivity: LogLevel Error from command line.")]
+ [DataRow("--LogLevel CrItIcal", DisplayName = "Case sensitivity: LogLevel Critical from command line.")]
+ [DataRow("--LogLevel NONE", DisplayName = "Case sensitivity: LogLevel None from command line.")]
+ public void TestEngineStartUpWithHighLogLevelOptions(string logLevelOption)
+ {
+ _fileSystem!.File.WriteAllText(TEST_RUNTIME_CONFIG_FILE, INITIAL_CONFIG);
+
+ using Process process = ExecuteDabCommand(
+ command: $"start --config {TEST_RUNTIME_CONFIG_FILE}",
+ logLevelOption
+ );
+
+ // CLI phase messages are at Information level and will be suppressed by Warning+.
+ // Verify the engine started (process not immediately exited) then clean up.
+ Assert.IsFalse(process.HasExited);
+ process.Kill();
+ }
+
+ ///
+ /// Tests that PreParseLogLevel correctly extracts the LogLevel from command line args
+ /// before full argument parsing, so the CLI logger is configured correctly.
+ ///
+ [DataTestMethod]
+ [DataRow(new string[] { "start", "--LogLevel", "None" }, LogLevel.None, DisplayName = "Parses --LogLevel None")]
+ [DataRow(new string[] { "start", "--LogLevel", "Warning" }, LogLevel.Warning, DisplayName = "Parses --LogLevel Warning")]
+ [DataRow(new string[] { "start", "--LogLevel", "Trace" }, LogLevel.Trace, DisplayName = "Parses --LogLevel Trace")]
+ [DataRow(new string[] { "start", "--LogLevel", "none" }, LogLevel.None, DisplayName = "Case-insensitive: --LogLevel none")]
+ [DataRow(new string[] { "start", "--LogLevel", "NONE" }, LogLevel.None, DisplayName = "Case-insensitive: --LogLevel NONE")]
+ [DataRow(new string[] { "start", "--LogLevel", "6" }, LogLevel.None, DisplayName = "Numeric: --LogLevel 6")]
+ [DataRow(new string[] { "start", "--LogLevel", "0" }, LogLevel.Trace, DisplayName = "Numeric: --LogLevel 0")]
+ [DataRow(new string[] { "start", "--LogLevel=None" }, LogLevel.None, DisplayName = "Equals syntax: --LogLevel=None")]
+ [DataRow(new string[] { "start" }, LogLevel.Information, DisplayName = "No --LogLevel returns Information default")]
+ [DataRow(new string[] { "start", "--verbose" }, LogLevel.Information, DisplayName = "--verbose returns Information default")]
+ public void TestPreParseLogLevel(string[] args, LogLevel expectedLogLevel)
+ {
+ Assert.AreEqual(expected: expectedLogLevel, actual: Program.PreParseLogLevel(args));
+ }
+
///
/// Validates that valid usage of verbs and associated options produce exit code 0 (CliReturnCode.SUCCESS).
/// Verifies that explicitly implemented verbs (add, update, init, start) and appropriately
diff --git a/src/Cli/CustomLoggerProvider.cs b/src/Cli/CustomLoggerProvider.cs
index c06918b93f..e52e2400e3 100644
--- a/src/Cli/CustomLoggerProvider.cs
+++ b/src/Cli/CustomLoggerProvider.cs
@@ -8,18 +8,30 @@
///
public class CustomLoggerProvider : ILoggerProvider
{
+ private readonly LogLevel _minimumLogLevel;
+
+ public CustomLoggerProvider(LogLevel minimumLogLevel = LogLevel.Information)
+ {
+ _minimumLogLevel = minimumLogLevel;
+ }
+
public void Dispose() { }
///
public ILogger CreateLogger(string categoryName)
{
- return new CustomConsoleLogger();
+ return new CustomConsoleLogger(_minimumLogLevel);
}
public class CustomConsoleLogger : ILogger
{
// Minimum LogLevel. LogLevel below this would be disabled.
- private readonly LogLevel _minimumLogLevel = LogLevel.Information;
+ private readonly LogLevel _minimumLogLevel;
+
+ public CustomConsoleLogger(LogLevel minimumLogLevel = LogLevel.Information)
+ {
+ _minimumLogLevel = minimumLogLevel;
+ }
// Color values based on LogLevel
// LogLevel Foreground Background
@@ -61,7 +73,7 @@ public class CustomConsoleLogger : ILogger
///
public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
{
- if (!IsEnabled(logLevel) || logLevel < _minimumLogLevel)
+ if (!IsEnabled(logLevel))
{
return;
}
@@ -79,7 +91,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
///
public bool IsEnabled(LogLevel logLevel)
{
- return true;
+ return logLevel != LogLevel.None && logLevel >= _minimumLogLevel;
}
public IDisposable? BeginScope(TState state) where TState : notnull
{
diff --git a/src/Cli/Program.cs b/src/Cli/Program.cs
index e4732da095..e6732a4871 100644
--- a/src/Cli/Program.cs
+++ b/src/Cli/Program.cs
@@ -26,6 +26,11 @@ public static int Main(string[] args)
// Load environment variables from .env file if present.
DotNetEnv.Env.Load();
+ // Pre-parse the --LogLevel option so the CLI logger respects the
+ // requested log level before the engine starts.
+ LogLevel cliLogLevel = PreParseLogLevel(args);
+ Utils.LoggerFactoryForCli = Utils.GetLoggerFactoryForCli(cliLogLevel);
+
// Logger setup and configuration
ILoggerFactory loggerFactory = Utils.LoggerFactoryForCli;
ILogger cliLogger = loggerFactory.CreateLogger();
@@ -41,6 +46,39 @@ public static int Main(string[] args)
return Execute(args, cliLogger, fileSystem, loader);
}
+ ///
+ /// Pre-parses the --LogLevel option from the command-line arguments before full
+ /// argument parsing, so the CLI logger can be configured at the right minimum
+ /// level for CLI phase messages (version info, config loading, etc.).
+ ///
+ /// Command line arguments
+ /// The parsed LogLevel, or Information if not specified or invalid.
+ internal static LogLevel PreParseLogLevel(string[] args)
+ {
+ for (int i = 0; i < args.Length; i++)
+ {
+ // Handle --LogLevel None (two separate tokens)
+ if (args[i].Equals("--LogLevel", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length)
+ {
+ if (Enum.TryParse(args[i + 1], ignoreCase: true, out LogLevel level))
+ {
+ return level;
+ }
+ }
+ // Handle --LogLevel=None (single token with equals sign)
+ else if (args[i].StartsWith("--LogLevel=", StringComparison.OrdinalIgnoreCase))
+ {
+ string value = args[i]["--LogLevel=".Length..];
+ if (Enum.TryParse(value, ignoreCase: true, out LogLevel level))
+ {
+ return level;
+ }
+ }
+ }
+
+ return LogLevel.Information;
+ }
+
///
/// Execute the CLI command
///
diff --git a/src/Cli/Utils.cs b/src/Cli/Utils.cs
index 48edd4411c..a7b19372c2 100644
--- a/src/Cli/Utils.cs
+++ b/src/Cli/Utils.cs
@@ -960,10 +960,10 @@ public static bool IsEntityProvided(string? entity, ILogger cliLogger, string co
///
/// Returns ILoggerFactory with CLI custom logger provider.
///
- public static ILoggerFactory GetLoggerFactoryForCli()
+ public static ILoggerFactory GetLoggerFactoryForCli(LogLevel minimumLogLevel = LogLevel.Information)
{
ILoggerFactory loggerFactory = new LoggerFactory();
- loggerFactory.AddProvider(new CustomLoggerProvider());
+ loggerFactory.AddProvider(new CustomLoggerProvider(minimumLogLevel));
return loggerFactory;
}
}
diff --git a/src/Config/ObjectModel/RuntimeConfig.cs b/src/Config/ObjectModel/RuntimeConfig.cs
index 0684040f85..81f796d3dc 100644
--- a/src/Config/ObjectModel/RuntimeConfig.cs
+++ b/src/Config/ObjectModel/RuntimeConfig.cs
@@ -792,7 +792,8 @@ public LogLevel GetConfiguredLogLevel(string loggerFilter = "")
return (LogLevel)value;
}
- Runtime!.Telemetry!.LoggerLevel!.TryGetValue("default", out value);
+ value = Runtime!.Telemetry!.LoggerLevel!
+ .FirstOrDefault(kvp => kvp.Key.Equals("default", StringComparison.OrdinalIgnoreCase)).Value;
if (value is not null)
{
return (LogLevel)value;
diff --git a/src/Core/Configurations/RuntimeConfigValidator.cs b/src/Core/Configurations/RuntimeConfigValidator.cs
index f5112844da..4802d81efd 100644
--- a/src/Core/Configurations/RuntimeConfigValidator.cs
+++ b/src/Core/Configurations/RuntimeConfigValidator.cs
@@ -1578,7 +1578,7 @@ private static bool IsLoggerFilterValid(string loggerFilter)
{
for (int j = 0; j < loggerSub.Length; j++)
{
- if (!loggerSub[j].Equals(validFiltersSub[j]))
+ if (!loggerSub[j].Equals(validFiltersSub[j], StringComparison.OrdinalIgnoreCase))
{
isValid = false;
break;
diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs
index aa12a7d465..282949c891 100644
--- a/src/Service.Tests/Configuration/ConfigurationTests.cs
+++ b/src/Service.Tests/Configuration/ConfigurationTests.cs
@@ -4132,6 +4132,7 @@ public void ValidLogLevelFilters(LogLevel logLevel, Type loggingType)
[DataTestMethod]
[TestCategory(TestCategory.MSSQL)]
[DataRow(LogLevel.Trace, "default")]
+ [DataRow(LogLevel.Warning, "Default")]
[DataRow(LogLevel.Debug, "Azure")]
[DataRow(LogLevel.Information, "Azure.DataApiBuilder")]
[DataRow(LogLevel.Warning, "Azure.DataApiBuilder.Core")]