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
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