Skip to content

Enable Custom log-level for Classes via log-level Filters #2620

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 24 commits into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9470c93
Added logic to check have multiple log-level filters
RubenCerna2079 Mar 14, 2025
db12b75
Added logic to check have multiple log-level filters
RubenCerna2079 Mar 14, 2025
52801fe
Added new file
RubenCerna2079 Mar 15, 2025
74dfb12
Test failure fixes
RubenCerna2079 Mar 17, 2025
24c3d2c
Merge Conflict Resolution
RubenCerna2079 Mar 17, 2025
7a2801d
Changes based on comments
RubenCerna2079 Mar 19, 2025
069d647
Changes based on comments
RubenCerna2079 Mar 20, 2025
d82d558
Comment Changes
RubenCerna2079 Mar 24, 2025
593c0aa
Comment Changes
RubenCerna2079 Mar 24, 2025
662ae87
Comment Changes
RubenCerna2079 Mar 25, 2025
6bd7c6f
Added missing file
RubenCerna2079 Mar 25, 2025
4a6a28e
Change use of hard coded string
RubenCerna2079 Mar 25, 2025
0e83f12
Merge branch 'main' into dev/rubencerna/Add_LogLevel_Filters
RubenCerna2079 Mar 25, 2025
415d638
Fix test cases
RubenCerna2079 Mar 25, 2025
1e0f039
Fixed use of hard coded strings
RubenCerna2079 Mar 26, 2025
76ae1e6
Changes based on comments
RubenCerna2079 Mar 27, 2025
aeb3b1e
Added missing file
RubenCerna2079 Mar 27, 2025
86f9ee0
Comment Changes
RubenCerna2079 Mar 27, 2025
a55a773
Fixed build error
RubenCerna2079 Mar 27, 2025
48c45e7
Merge branch 'main' into dev/rubencerna/Add_LogLevel_Filters
RubenCerna2079 Mar 28, 2025
80dd132
Comment Changes
RubenCerna2079 Mar 31, 2025
7d5ad3f
Merge branch 'main' into dev/rubencerna/Add_LogLevel_Filters
RubenCerna2079 Mar 31, 2025
53cacb6
Solve merge conflict
RubenCerna2079 Mar 31, 2025
63f7963
Changes based on comments
RubenCerna2079 Apr 1, 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
8 changes: 4 additions & 4 deletions schemas/dab.draft.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,11 @@
},
"log-level": {
"type": "object",
"description": "Global configuration of log level",
"description": "Global configuration of log level, defines logging severity levels for specific classes, when 'null' it will set logging level based on 'host: mode' property",
"additionalProperties": false,
"properties": {
"level": {
"description": "Defines logging severity levels, when in default value it will set logging level based on 'host: mode' property",
"patternProperties": {
"^[a-zA-Z_]\\w{0,}(\\.\\w{1,}){0,}$": {
"description": "Only specific namespaces that are used for logging in DAB are able to be used here. The regex pattern checks that it only allows for namespace values. So it only accepts a letter as its first value, from there it accepts any alphanumeric character, each section is divided by '.' and there cannot be more than 1 '.' in a row",
"type": "string",
"default": null,
"enum": [
Expand Down
2 changes: 2 additions & 0 deletions src/Cli/Commands/ValidateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO.Abstractions;
using Azure.DataApiBuilder.Config;
using Azure.DataApiBuilder.Product;
using Azure.DataApiBuilder.Service;
using Cli.Constants;
using CommandLine;
using Microsoft.Extensions.Logging;
Expand All @@ -28,6 +29,7 @@ public ValidateOptions(string config)
public int Handler(ILogger logger, FileSystemRuntimeConfigLoader loader, IFileSystem fileSystem)
{
logger.LogInformation("{productName} {version}", PRODUCT_NAME, ProductInfo.GetProductVersion());
Startup.AddValidFilters();
bool isValidConfig = ConfigGenerator.IsConfigValid(this, loader, fileSystem);

if (isValidConfig)
Expand Down
2 changes: 1 addition & 1 deletion src/Cli/ConfigGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1672,7 +1672,7 @@ public static bool TryStartEngineWithOptions(StartOptions options, FileSystemRun
}
else
{
minimumLogLevel = RuntimeConfig.GetConfiguredLogLevel(deserializedRuntimeConfig);
minimumLogLevel = deserializedRuntimeConfig.GetConfiguredLogLevel();
HostMode hostModeType = deserializedRuntimeConfig.IsDevelopmentMode() ? HostMode.Development : HostMode.Production;

_logger.LogInformation("Setting default minimum LogLevel: {minimumLogLevel} for {hostMode} mode.", minimumLogLevel, hostModeType);
Expand Down
49 changes: 0 additions & 49 deletions src/Config/Converters/LogLevelOptionsConverterFactory.cs

This file was deleted.

21 changes: 0 additions & 21 deletions src/Config/ObjectModel/LogLevelOptions.cs

This file was deleted.

57 changes: 47 additions & 10 deletions src/Config/ObjectModel/RuntimeConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -578,11 +578,26 @@ public uint GetPaginationLimit(int? first)
/// <summary>
/// Checks if the property log-level or its value are null
/// </summary>
public bool IsLogLevelNull() =>
Runtime is null ||
Runtime.Telemetry is null ||
Runtime.Telemetry.LoggerLevel is null ||
Runtime.Telemetry.LoggerLevel.Value is null;
public bool IsLogLevelNull()
{
if (Runtime is null ||
Runtime.Telemetry is null ||
Runtime.Telemetry.LoggerLevel is null ||
Runtime.Telemetry.LoggerLevel.Count == 0)
{
return true;
}

foreach (KeyValuePair<string, LogLevel?> logger in Runtime!.Telemetry.LoggerLevel)
{
if (logger.Key == null)
{
return true;
}
}

return false;
}

/// <summary>
/// Takes in the RuntimeConfig object and checks the LogLevel.
Expand All @@ -591,15 +606,37 @@ Runtime.Telemetry.LoggerLevel is null ||
/// If host mode is Development, return `LogLevel.Debug`, else
/// for production returns `LogLevel.Error`.
/// </summary>
public static LogLevel GetConfiguredLogLevel(RuntimeConfig runtimeConfig)
public LogLevel GetConfiguredLogLevel(string loggerFilter = "")
{
LogLevel? value = runtimeConfig.Runtime?.Telemetry?.LoggerLevel?.Value;
if (value is not null)

if (!IsLogLevelNull())
{
return (LogLevel)value;
int max = 0;
string currentFilter = string.Empty;
foreach (KeyValuePair<string, LogLevel?> logger in Runtime!.Telemetry!.LoggerLevel!)
{
// Checks if the new key that is valid has more priority than the current key
if (logger.Key.Length > max && loggerFilter.StartsWith(logger.Key))
{
max = logger.Key.Length;
currentFilter = logger.Key;
}
}

Runtime!.Telemetry!.LoggerLevel!.TryGetValue(currentFilter, out LogLevel? value);
if (value is not null)
{
return (LogLevel)value;
}

Runtime!.Telemetry!.LoggerLevel!.TryGetValue("default", out value);
if (value is not null)
{
return (LogLevel)value;
}
}

if (runtimeConfig.IsDevelopmentMode())
if (IsDevelopmentMode())
{
return LogLevel.Debug;
}
Expand Down
5 changes: 3 additions & 2 deletions src/Config/ObjectModel/TelemetryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// Licensed under the MIT License.

using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;

namespace Azure.DataApiBuilder.Config.ObjectModel;

/// <summary>
/// Represents the options for telemetry.
/// </summary>
public record TelemetryOptions(ApplicationInsightsOptions? ApplicationInsights = null, OpenTelemetryOptions? OpenTelemetry = null, LogLevelOptions? LoggerLevel = null)
public record TelemetryOptions(ApplicationInsightsOptions? ApplicationInsights = null, OpenTelemetryOptions? OpenTelemetry = null, Dictionary<string, LogLevel?>? LoggerLevel = null)
{
[JsonPropertyName("log-level")]
public LogLevelOptions? LoggerLevel { get; init; } = LoggerLevel;
public Dictionary<string, LogLevel?>? LoggerLevel { get; init; } = LoggerLevel;
}
1 change: 0 additions & 1 deletion src/Config/RuntimeConfigLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,6 @@ public static JsonSerializerOptions GetSerializationOptions(
options.Converters.Add(new MultipleMutationOptionsConverter(options));
options.Converters.Add(new DataSourceConverterFactory(replaceEnvVar));
options.Converters.Add(new HostOptionsConvertorFactory());
options.Converters.Add(new LogLevelOptionsConverterFactory());

if (replaceEnvVar)
{
Expand Down
18 changes: 18 additions & 0 deletions src/Core/Configurations/LoggerFilters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Azure.DataApiBuilder.Core.Configurations
{
public static class LoggerFilters
{
public static List<string> validFilters = new();

public static void AddFilter(string? loggerFilter)
{
if (loggerFilter != null)
{
validFilters.Add(loggerFilter);
}
}
}
}
56 changes: 56 additions & 0 deletions src/Core/Configurations/RuntimeConfigValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public void ValidateConfigProperties()
ValidateAuthenticationOptions(runtimeConfig);
ValidateGlobalEndpointRouteConfig(runtimeConfig);
ValidateAppInsightsTelemetryConnectionString(runtimeConfig);
ValidateLoggerFilters(runtimeConfig);

// Running these graphQL validations only in development mode to ensure
// fast startup of engine in production mode.
Expand Down Expand Up @@ -129,6 +130,25 @@ public void ValidateAppInsightsTelemetryConnectionString(RuntimeConfig runtimeCo
}
}

/// <summary>
/// Only certain classes can have different log levels, this function ensures that only those classes are used in the config file.
/// </summary>
public static void ValidateLoggerFilters(RuntimeConfig runtimeConfig)
{
if (runtimeConfig.Runtime?.Telemetry is not null && runtimeConfig.Runtime.Telemetry.LoggerLevel is not null)
{
Dictionary<string, LogLevel?> loggerLevelOptions = runtimeConfig.Runtime.Telemetry.LoggerLevel;

foreach (KeyValuePair<string, LogLevel?> logger in loggerLevelOptions)
{
if (!IsLoggerFilterValid(logger.Key))
{
throw new NotSupportedException($"Log level filter {logger.Key} needs to be of a valid log class.");
}
}
}
}

/// <summary>
/// This method runs several validations against the config file such as schema validation,
/// validation of entities metadata, validation of permissions, validation of entity configuration.
Expand Down Expand Up @@ -1323,4 +1343,40 @@ private bool IsValidPermissionAction(EntityActionOperation action, Entity entity
return action is EntityActionOperation.All || EntityAction.ValidPermissionOperations.Contains(action);
}
}

/// <summary>
/// Returns whether the log-level keyword is valid or not.
/// It does this by checking each section of the name of the class,
/// in order to ensure that the last section is complete.
/// E.g. Azure.DataApiBuilder is valid. While Azure.DataA is invalid.
/// </summary>
/// <param name="loggerFilter">String keyword that comes from log-level in config file</param>
private static bool IsLoggerFilterValid(string loggerFilter)
{
string[] loggerSub = loggerFilter.Split('.');
for (int i = 0; i < LoggerFilters.validFilters.Count; i++)
{
bool isValid = true;
string[] validFiltersSub = LoggerFilters.validFilters[i].Split('.');

if (loggerSub.Length <= validFiltersSub.Length)
{
for (int j = 0; j < loggerSub.Length; j++)
{
if (!loggerSub[j].Equals(validFiltersSub[j]))
{
isValid = false;
break;
}
}

if (isValid)
{
return true;
}
}
}

return false;
}
}
Loading