Skip to content

Hot Reload for Loggers #2634

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

Merged
merged 39 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
9470c93
Added logic to check have multiple log-level filters
Mar 14, 2025
db12b75
Added logic to check have multiple log-level filters
Mar 14, 2025
52801fe
Added new file
Mar 15, 2025
74dfb12
Test failure fixes
Mar 17, 2025
24c3d2c
Merge Conflict Resolution
Mar 17, 2025
7a2801d
Changes based on comments
Mar 19, 2025
069d647
Changes based on comments
Mar 20, 2025
d82d558
Comment Changes
Mar 24, 2025
593c0aa
Comment Changes
Mar 24, 2025
662ae87
Comment Changes
Mar 25, 2025
6bd7c6f
Added missing file
Mar 25, 2025
4a6a28e
Change use of hard coded string
Mar 25, 2025
0e83f12
Merge branch 'main' into dev/rubencerna/Add_LogLevel_Filters
RubenCerna2079 Mar 25, 2025
415d638
Fix test cases
Mar 25, 2025
1e0f039
Fixed use of hard coded strings
Mar 26, 2025
76ae1e6
Changes based on comments
Mar 27, 2025
aeb3b1e
Added missing file
Mar 27, 2025
afbb9e6
Hot Reload Changes
Mar 18, 2025
2924c15
Hot Reload Changes
Mar 20, 2025
7047f6b
Hot Reload Changes
Mar 20, 2025
de35cf6
Added missing file
Mar 24, 2025
2abb858
Test changes
Mar 25, 2025
2e75d42
Merge Changes
Mar 26, 2025
1515849
Fixed logging comment
Mar 27, 2025
84e5285
Added hot reload test
Mar 27, 2025
c75adf1
Added test
Mar 27, 2025
935847f
Allow log level to be hot reloaded in production mode
Mar 27, 2025
95ade95
Changes based on comments
Mar 28, 2025
d6ac383
Changes based on comments
Mar 31, 2025
f9d5b69
Comment Changes
Mar 31, 2025
76e01fe
Comment Changes
Mar 31, 2025
b1b489e
Comment Changes
Apr 1, 2025
e66c70e
Resolved build issue
Apr 1, 2025
44fc714
Fixed merge conflicts
Apr 1, 2025
4fd2d79
Fixed Merge Conflicts
Apr 1, 2025
77a1ee3
Changes based on comments
Apr 1, 2025
8ef9eff
Fix Tests
Apr 2, 2025
2383f48
Merge branch 'main' into dev/rubencerna/HotReload_Logger
RubenCerna2079 Apr 2, 2025
9094991
Merge branch 'main' into dev/rubencerna/HotReload_Logger
RubenCerna2079 Apr 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Cli/ConfigGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1675,7 +1675,7 @@ public static bool TryStartEngineWithOptions(StartOptions options, FileSystemRun
minimumLogLevel = deserializedRuntimeConfig.GetConfiguredLogLevel();
HostMode hostModeType = deserializedRuntimeConfig.IsDevelopmentMode() ? HostMode.Development : HostMode.Production;

_logger.LogInformation("Setting default minimum LogLevel: {minimumLogLevel} for {hostMode} mode.", minimumLogLevel, hostModeType);
_logger.LogInformation($"Setting default minimum LogLevel: {minimumLogLevel} for {hostModeType} mode.", minimumLogLevel, hostModeType);
}

args.Add("--LogLevel");
Expand Down
2 changes: 1 addition & 1 deletion src/Config/DabConfigEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ public static class DabConfigEvents
public const string GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED = "GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED";
public const string GRAPHQL_SCHEMA_EVICTION_ON_CONFIG_CHANGED = "GRAPHQL_SCHEMA_EVICTION_ON_CONFIG_CHANGED";
public const string GRAPHQL_SCHEMA_CREATOR_ON_CONFIG_CHANGED = "GRAPHQL_SCHEMA_CREATOR_ON_CONFIG_CHANGED";

public const string LOG_LEVEL_INITIALIZER_ON_CONFIG_CHANGE = "LOG_LEVEL_INITIALIZER_ON_CONFIG_CHANGE";
}
4 changes: 3 additions & 1 deletion src/Config/FileSystemRuntimeConfigLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ private bool TrySetupConfigFileWatcher()
return false;
}

if (RuntimeConfig is not null && RuntimeConfig.IsDevelopmentMode())
if (RuntimeConfig is not null)
{
try
{
Expand Down Expand Up @@ -318,6 +318,8 @@ private void HotReloadConfig(bool isDevMode, ILogger? logger = null)
IsNewConfigDetected = true;
IsNewConfigValidated = false;
SignalConfigChanged();

logger?.LogInformation("Hot-reload process finished.");
}

/// <summary>
Expand Down
5 changes: 3 additions & 2 deletions src/Config/HotReloadEventHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public HotReloadEventHandler()
{ AUTHZ_RESOLVER_ON_CONFIG_CHANGED, null },
{ GRAPHQL_SCHEMA_CREATOR_ON_CONFIG_CHANGED, null },
{ GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED, null },
{ GRAPHQL_SCHEMA_EVICTION_ON_CONFIG_CHANGED, null }
{ GRAPHQL_SCHEMA_EVICTION_ON_CONFIG_CHANGED, null },
{ LOG_LEVEL_INITIALIZER_ON_CONFIG_CHANGE, null }
};
}

Expand All @@ -49,7 +50,7 @@ public void Subscribe(string eventName, EventHandler<TEventArgs> handler)
{
if (_eventHandlers.ContainsKey(eventName))
{
_eventHandlers[eventName] = handler;
_eventHandlers[eventName] += handler;
}
}
}
64 changes: 48 additions & 16 deletions src/Config/RuntimeConfigLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,29 @@ protected void SignalConfigChanged(string message = "")
// Signal that a change has occurred to all change token listeners.
RaiseChanged();

OnConfigChangedEvent(new HotReloadEventArgs(QUERY_MANAGER_FACTORY_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(METADATA_PROVIDER_FACTORY_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(QUERY_ENGINE_FACTORY_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(MUTATION_ENGINE_FACTORY_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(DOCUMENTOR_ON_CONFIG_CHANGED, message));

// Order of event firing matters: Authorization rules can only be updated after the
// MetadataProviderFactory has been updated with latest database object metadata.
// RuntimeConfig must already be updated and is implied to have been updated by the time
// this function is called.
OnConfigChangedEvent(new HotReloadEventArgs(AUTHZ_RESOLVER_ON_CONFIG_CHANGED, message));

// Order of event firing matters: Eviction must be done before creating a new schema and then updating the schema.
OnConfigChangedEvent(new HotReloadEventArgs(GRAPHQL_SCHEMA_EVICTION_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(GRAPHQL_SCHEMA_CREATOR_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED, message));
// All the data inside of the if statement should only update when DAB is in development mode.
if (RuntimeConfig!.IsDevelopmentMode())
{
OnConfigChangedEvent(new HotReloadEventArgs(QUERY_MANAGER_FACTORY_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(METADATA_PROVIDER_FACTORY_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(QUERY_ENGINE_FACTORY_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(MUTATION_ENGINE_FACTORY_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(DOCUMENTOR_ON_CONFIG_CHANGED, message));

// Order of event firing matters: Authorization rules can only be updated after the
// MetadataProviderFactory has been updated with latest database object metadata.
// RuntimeConfig must already be updated and is implied to have been updated by the time
// this function is called.
OnConfigChangedEvent(new HotReloadEventArgs(AUTHZ_RESOLVER_ON_CONFIG_CHANGED, message));

// Order of event firing matters: Eviction must be done before creating a new schema and then updating the schema.
OnConfigChangedEvent(new HotReloadEventArgs(GRAPHQL_SCHEMA_EVICTION_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(GRAPHQL_SCHEMA_CREATOR_ON_CONFIG_CHANGED, message));
OnConfigChangedEvent(new HotReloadEventArgs(GRAPHQL_SCHEMA_REFRESH_ON_CONFIG_CHANGED, message));
}

// Log Level Initializer is outside of if statement as it can be updated on both development and production mode.
OnConfigChangedEvent(new HotReloadEventArgs(LOG_LEVEL_INITIALIZER_ON_CONFIG_CHANGE, message));
}

/// <summary>
Expand Down Expand Up @@ -388,4 +395,29 @@ public void RestoreLkgConfig()
{
RuntimeConfig = LastValidRuntimeConfig;
}

/// <summary>
/// Uses the Last Valid Runtime Config and inserts the log-level property to the Runtime Config that will be used
/// during the hot-reload if DAB is in Production Mode, this means that only changes to log-level will be registered.
/// This is done in order to ensure that no unwanted changes are honored during hot-reload in Production Mode.
/// </summary>
public void InsertWantedChangesInProductionMode()
{
if (!RuntimeConfig!.IsDevelopmentMode())
{
// Creates copy of last valid runtime config and only adds the new logger level changes
RuntimeConfig runtimeConfigCopy = LastValidRuntimeConfig! with
{
Runtime = LastValidRuntimeConfig.Runtime! with
{
Telemetry = LastValidRuntimeConfig.Runtime!.Telemetry! with
{
LoggerLevel = RuntimeConfig.Runtime!.Telemetry!.LoggerLevel
}
}
};

RuntimeConfig = runtimeConfigCopy;
}
}
}
4 changes: 4 additions & 0 deletions src/Core/Configurations/RuntimeConfigProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,10 @@ public void ValidateConfig()
ILogger<RuntimeConfigValidator> logger = loggerFactory.CreateLogger<RuntimeConfigValidator>();
RuntimeConfigValidator runtimeConfigValidator = new(this, fileSystem, logger, true);

// This function only works if DAB is started in Production Mode
_configLoader.InsertWantedChangesInProductionMode();

// Checks if the new config is valid or invalid
_configLoader.IsNewConfigValidated = runtimeConfigValidator.TryValidateConfig(ConfigFilePath, loggerFactory).Result;

// Saves the lastValidRuntimeConfig as the new RuntimeConfig if it is validated for hot reload
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Azure.DataApiBuilder.Service.Tests.SqlTests;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Azure.DataApiBuilder.Service.Tests.Configuration.HotReload;
Expand Down Expand Up @@ -49,6 +50,7 @@ private static void GenerateConfigFile(
string restEnabled = "true",
string gQLPath = "/graphQL",
string gQLEnabled = "true",
string logFilter = "debug",
string entityName = "Book",
string sourceObject = "books",
string gQLEntityEnabled = "true",
Expand Down Expand Up @@ -91,6 +93,11 @@ private static void GenerateConfigFile(
""provider"": ""StaticWebApps""
},
""mode"": ""development""
},
""telemetry"": {
""log-level"": {
""default"": """ + logFilter + @"""
}
}
},
""entities"": {
Expand Down Expand Up @@ -542,6 +549,35 @@ public async Task HotReloadConfigDataSource()
SqlTestHelper.PerformTestEqualJsonStrings(_bookDBOContents, reloadGQLContents.GetProperty("items").ToString());
}

/// <summary>
/// Hot reload the configuration file so that it updated the log-level property.
/// Then we assert that the log-level property is properly updated by ensuring it is
/// not the same as the previous log-level and asserting it is the expected log-level.
/// </summary>
[TestCategory(MSSQL_ENVIRONMENT)]
[TestMethod]
public void HotReloadLogLevel()
{
// Arange
LogLevel expectedLogLevel = LogLevel.Trace;
string expectedFilter = "trace";
RuntimeConfig previousRuntimeConfig = _configProvider.GetConfig();
LogLevel previouslogLevel = previousRuntimeConfig.GetConfiguredLogLevel();

//Act
GenerateConfigFile(
connectionString: $"{ConfigurationTests.GetConnectionStringFromEnvironmentConfig(TestCategory.MSSQL).Replace("\\", "\\\\")}",
logFilter: expectedFilter);
System.Threading.Thread.Sleep(3000);

RuntimeConfig updatedRuntimeConfig = _configProvider.GetConfig();
LogLevel actualLogLevel = updatedRuntimeConfig.GetConfiguredLogLevel();

//Assert
Assert.AreNotEqual(previouslogLevel, actualLogLevel);
Assert.AreEqual(expectedLogLevel, actualLogLevel);
}

/// <summary>
/// Hot reload the configuration file so that it changes from one connection string
/// to an invalid connection string, then it hot reloads once more to the original
Expand Down
29 changes: 23 additions & 6 deletions src/Service/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Threading.Tasks;
using Azure.DataApiBuilder.Config;
using Azure.DataApiBuilder.Service.Exceptions;
using Azure.DataApiBuilder.Service.Telemetry;
using Microsoft.ApplicationInsights;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
Expand Down Expand Up @@ -133,17 +134,26 @@ private static ParseResult GetParseResult(Command cmd, string[] args)
/// </summary>
/// <param name="logLevel">minimum log level.</param>
/// <param name="appTelemetryClient">Telemetry client</param>
public static ILoggerFactory GetLoggerFactoryForLogLevel(LogLevel logLevel, TelemetryClient? appTelemetryClient = null)
public static ILoggerFactory GetLoggerFactoryForLogLevel(LogLevel logLevel, TelemetryClient? appTelemetryClient = null, LogLevelInitializer? logLevelInitializer = null)
{
return LoggerFactory
.Create(builder =>
{
// Category defines the namespace we will log from,
// including all sub-domains. ie: "Azure" includes
// "Azure.DataApiBuilder.Service"
builder.AddFilter(category: "Microsoft", logLevel);
builder.AddFilter(category: "Azure", logLevel);
builder.AddFilter(category: "Default", logLevel);
if (logLevelInitializer is null)
{
builder.AddFilter(category: "Microsoft", logLevel);
builder.AddFilter(category: "Azure", logLevel);
builder.AddFilter(category: "Default", logLevel);
}
else
{
builder.AddFilter(category: "Microsoft", level => level >= logLevelInitializer.MinLogLevel);
builder.AddFilter(category: "Azure", level => level >= logLevelInitializer.MinLogLevel);
builder.AddFilter(category: "Default", level => level >= logLevelInitializer.MinLogLevel);
}

// For Sending all the ILogger logs to Application Insights
if (Startup.AppInsightsOptions.Enabled && !string.IsNullOrWhiteSpace(Startup.AppInsightsOptions.ConnectionString))
Expand All @@ -157,8 +167,15 @@ public static ILoggerFactory GetLoggerFactoryForLogLevel(LogLevel logLevel, Tele
}
},
configureApplicationInsightsLoggerOptions: (options) => { }
)
.AddFilter<ApplicationInsightsLoggerProvider>(category: string.Empty, logLevel);
);
if (logLevelInitializer is null)
{
builder.AddFilter<ApplicationInsightsLoggerProvider>(category: string.Empty, logLevel);
}
else
{
builder.AddFilter<ApplicationInsightsLoggerProvider>(category: string.Empty, level => level >= logLevelInitializer.MinLogLevel);
}
}

if (Startup.OpenTelemetryOptions.Enabled && !string.IsNullOrWhiteSpace(Startup.OpenTelemetryOptions.Endpoint))
Expand Down
Loading
Loading