Skip to content
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

Remove handwritten binding logic from Logging.Console; use generator instead #88067

Merged
merged 7 commits into from
Jul 14, 2023

Conversation

layomia
Copy link
Contributor

@layomia layomia commented Jun 26, 2023

Generated code (click to view)
// <auto-generated/>
#nullable enable

/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
internal static class GeneratedConfigurationBinder
{
    /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
    public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Microsoft.Extensions.Logging.Console.ConsoleFormatterOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null);

    /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
    public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Microsoft.Extensions.Logging.Console.ConsoleLoggerOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null);

    /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
    public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Microsoft.Extensions.Logging.Console.JsonConsoleFormatterOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null);

    /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
    public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Microsoft.Extensions.Logging.Console.SimpleConsoleFormatterOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null);
}

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Logging.Console;
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Text.Encodings.Web;
    using System.Text.Json;

    /// <summary>Provide core binding logic.</summary>
    internal static class CoreBindingHelper
    {
        public static void BindCore(IConfiguration configuration, ref ConsoleFormatterOptions obj, BinderOptions? binderOptions)
        {
            if (obj is null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            List<string>? temp = null;
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                switch (section.Key)
                {
                    case "IncludeScopes":
                        {
                            if (configuration["IncludeScopes"] is string stringValue0)
                            {
                                obj.IncludeScopes = ParseBool(stringValue0, () => section.Path)!;
                            }
                        }
                        break;
                    case "TimestampFormat":
                        {
                            obj.TimestampFormat = configuration["TimestampFormat"]!;
                        }
                        break;
                    case "UseUtcTimestamp":
                        {
                            if (configuration["UseUtcTimestamp"] is string stringValue2)
                            {
                                obj.UseUtcTimestamp = ParseBool(stringValue2, () => section.Path)!;
                            }
                        }
                        break;
                    default:
                        {
                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
                            {
                                (temp ??= new List<string>()).Add($"'{section.Key}'");
                            }
                        }
                        break;
                }
            }

            if (temp is not null)
            {
                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(ConsoleFormatterOptions)}: {string.Join(", ", temp)}");
            }
        }

        public static void BindCore(IConfiguration configuration, ref ConsoleLoggerOptions obj, BinderOptions? binderOptions)
        {
            if (obj is null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            List<string>? temp = null;
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                switch (section.Key)
                {
                    case "DisableColors":
                        {
                            if (configuration["DisableColors"] is string stringValue3)
                            {
                                obj.DisableColors = ParseBool(stringValue3, () => section.Path)!;
                            }
                        }
                        break;
                    case "Format":
                        {
                            if (configuration["Format"] is string stringValue4)
                            {
                                obj.Format = ParseConsoleLoggerFormat(stringValue4, () => section.Path)!;
                            }
                        }
                        break;
                    case "FormatterName":
                        {
                            obj.FormatterName = configuration["FormatterName"]!;
                        }
                        break;
                    case "IncludeScopes":
                        {
                            if (configuration["IncludeScopes"] is string stringValue6)
                            {
                                obj.IncludeScopes = ParseBool(stringValue6, () => section.Path)!;
                            }
                        }
                        break;
                    case "LogToStandardErrorThreshold":
                        {
                            if (configuration["LogToStandardErrorThreshold"] is string stringValue7)
                            {
                                obj.LogToStandardErrorThreshold = ParseLogLevel(stringValue7, () => section.Path)!;
                            }
                        }
                        break;
                    case "TimestampFormat":
                        {
                            obj.TimestampFormat = configuration["TimestampFormat"]!;
                        }
                        break;
                    case "UseUtcTimestamp":
                        {
                            if (configuration["UseUtcTimestamp"] is string stringValue9)
                            {
                                obj.UseUtcTimestamp = ParseBool(stringValue9, () => section.Path)!;
                            }
                        }
                        break;
                    case "QueueFullMode":
                        {
                            if (configuration["QueueFullMode"] is string stringValue10)
                            {
                                obj.QueueFullMode = ParseConsoleLoggerQueueFullMode(stringValue10, () => section.Path)!;
                            }
                        }
                        break;
                    case "MaxQueueLength":
                        {
                            if (configuration["MaxQueueLength"] is string stringValue11)
                            {
                                obj.MaxQueueLength = ParseInt(stringValue11, () => section.Path)!;
                            }
                        }
                        break;
                    default:
                        {
                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
                            {
                                (temp ??= new List<string>()).Add($"'{section.Key}'");
                            }
                        }
                        break;
                }
            }

            if (temp is not null)
            {
                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(ConsoleLoggerOptions)}: {string.Join(", ", temp)}");
            }
        }


        public static void BindCore(IConfiguration configuration, ref JsonWriterOptions obj, BinderOptions? binderOptions)
        {
            List<string>? temp = null;
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                switch (section.Key)
                {
                    case "Encoder":
                        {
                            throw new InvalidOperationException("Cannot create instance of type 'System.Text.Encodings.Web.JavaScriptEncoder' because it is missing a public instance constructor.");
                        }
                    case "Indented":
                        {
                            if (configuration["Indented"] is string stringValue12)
                            {
                                obj.Indented = ParseBool(stringValue12, () => section.Path)!;
                            }
                        }
                        break;
                    case "MaxDepth":
                        {
                            if (configuration["MaxDepth"] is string stringValue13)
                            {
                                obj.MaxDepth = ParseInt(stringValue13, () => section.Path)!;
                            }
                        }
                        break;
                    case "SkipValidation":
                        {
                            if (configuration["SkipValidation"] is string stringValue14)
                            {
                                obj.SkipValidation = ParseBool(stringValue14, () => section.Path)!;
                            }
                        }
                        break;
                    default:
                        {
                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
                            {
                                (temp ??= new List<string>()).Add($"'{section.Key}'");
                            }
                        }
                        break;
                }
            }

            if (temp is not null)
            {
                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(JsonWriterOptions)}: {string.Join(", ", temp)}");
            }
        }

        public static void BindCore(IConfiguration configuration, ref JsonConsoleFormatterOptions obj, BinderOptions? binderOptions)
        {
            if (obj is null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            List<string>? temp = null;
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                switch (section.Key)
                {
                    case "JsonWriterOptions":
                        {
                            if (HasChildren(section))
                            {
                                JsonWriterOptions temp15 = obj.JsonWriterOptions;
                                BindCore(section, ref temp15, binderOptions);
                                obj.JsonWriterOptions = temp15;
                            }
                        }
                        break;
                    case "IncludeScopes":
                        {
                            if (configuration["IncludeScopes"] is string stringValue16)
                            {
                                obj.IncludeScopes = ParseBool(stringValue16, () => section.Path)!;
                            }
                        }
                        break;
                    case "TimestampFormat":
                        {
                            obj.TimestampFormat = configuration["TimestampFormat"]!;
                        }
                        break;
                    case "UseUtcTimestamp":
                        {
                            if (configuration["UseUtcTimestamp"] is string stringValue18)
                            {
                                obj.UseUtcTimestamp = ParseBool(stringValue18, () => section.Path)!;
                            }
                        }
                        break;
                    default:
                        {
                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
                            {
                                (temp ??= new List<string>()).Add($"'{section.Key}'");
                            }
                        }
                        break;
                }
            }

            if (temp is not null)
            {
                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(JsonConsoleFormatterOptions)}: {string.Join(", ", temp)}");
            }
        }

        public static void BindCore(IConfiguration configuration, ref SimpleConsoleFormatterOptions obj, BinderOptions? binderOptions)
        {
            if (obj is null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            List<string>? temp = null;
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                switch (section.Key)
                {
                    case "ColorBehavior":
                        {
                            if (configuration["ColorBehavior"] is string stringValue19)
                            {
                                obj.ColorBehavior = ParseLoggerColorBehavior(stringValue19, () => section.Path)!;
                            }
                        }
                        break;
                    case "SingleLine":
                        {
                            if (configuration["SingleLine"] is string stringValue20)
                            {
                                obj.SingleLine = ParseBool(stringValue20, () => section.Path)!;
                            }
                        }
                        break;
                    case "IncludeScopes":
                        {
                            if (configuration["IncludeScopes"] is string stringValue21)
                            {
                                obj.IncludeScopes = ParseBool(stringValue21, () => section.Path)!;
                            }
                        }
                        break;
                    case "TimestampFormat":
                        {
                            obj.TimestampFormat = configuration["TimestampFormat"]!;
                        }
                        break;
                    case "UseUtcTimestamp":
                        {
                            if (configuration["UseUtcTimestamp"] is string stringValue23)
                            {
                                obj.UseUtcTimestamp = ParseBool(stringValue23, () => section.Path)!;
                            }
                        }
                        break;
                    default:
                        {
                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
                            {
                                (temp ??= new List<string>()).Add($"'{section.Key}'");
                            }
                        }
                        break;
                }
            }

            if (temp is not null)
            {
                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(SimpleConsoleFormatterOptions)}: {string.Join(", ", temp)}");
            }
        }

        public static bool HasChildren(IConfiguration configuration)
        {
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                return true;
            }
            return false;
        }

        public static bool ParseBool(string stringValue, Func<string?> getPath)
        {
            try
            {
                return bool.Parse(stringValue);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception);
            }
        }

        public static ConsoleLoggerFormat ParseConsoleLoggerFormat(string stringValue, Func<string?> getPath)
        {
            try
            {
                return (ConsoleLoggerFormat)Enum.Parse(typeof(ConsoleLoggerFormat), stringValue, ignoreCase: true);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ConsoleLoggerFormat)}'.", exception);
            }
        }

        public static LogLevel ParseLogLevel(string stringValue, Func<string?> getPath)
        {
            try
            {
                return (LogLevel)Enum.Parse(typeof(LogLevel), stringValue, ignoreCase: true);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(LogLevel)}'.", exception);
            }
        }

        public static ConsoleLoggerQueueFullMode ParseConsoleLoggerQueueFullMode(string stringValue, Func<string?> getPath)
        {
            try
            {
                return (ConsoleLoggerQueueFullMode)Enum.Parse(typeof(ConsoleLoggerQueueFullMode), stringValue, ignoreCase: true);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ConsoleLoggerQueueFullMode)}'.", exception);
            }
        }

        public static int ParseInt(string stringValue, Func<string?> getPath)
        {
            try
            {
                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
            }
        }

        public static LoggerColorBehavior ParseLoggerColorBehavior(string stringValue, Func<string?> getPath)
        {
            try
            {
                return (LoggerColorBehavior)Enum.Parse(typeof(LoggerColorBehavior), stringValue, ignoreCase: true);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(LoggerColorBehavior)}'.", exception);
            }
        }
    }
}

@layomia layomia added this to the 8.0.0 milestone Jun 26, 2023
@layomia layomia self-assigned this Jun 26, 2023
@ghost
Copy link

ghost commented Jun 26, 2023

Tagging subscribers to this area: @dotnet/area-extensions-logging
See info in area-owners.md if you want to be subscribed.

Issue Details
Generated code (click to view)
// <auto-generated/>
#nullable enable

/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
internal static class GeneratedConfigurationBinder
{
    /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
    public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Microsoft.Extensions.Logging.Console.ConsoleFormatterOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null);

    /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
    public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Microsoft.Extensions.Logging.Console.ConsoleLoggerOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null);

    /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
    public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Microsoft.Extensions.Logging.Console.JsonConsoleFormatterOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null);

    /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
    public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Microsoft.Extensions.Logging.Console.SimpleConsoleFormatterOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null);
}

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Logging.Console;
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Text.Encodings.Web;
    using System.Text.Json;

    /// <summary>Provide core binding logic.</summary>
    internal static class CoreBindingHelper
    {
        public static void BindCore(IConfiguration configuration, ref ConsoleFormatterOptions obj, BinderOptions? binderOptions)
        {
            if (obj is null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            List<string>? temp = null;
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                switch (section.Key)
                {
                    case "IncludeScopes":
                        {
                            if (configuration["IncludeScopes"] is string stringValue0)
                            {
                                obj.IncludeScopes = ParseBool(stringValue0, () => section.Path)!;
                            }
                        }
                        break;
                    case "TimestampFormat":
                        {
                            obj.TimestampFormat = configuration["TimestampFormat"]!;
                        }
                        break;
                    case "UseUtcTimestamp":
                        {
                            if (configuration["UseUtcTimestamp"] is string stringValue2)
                            {
                                obj.UseUtcTimestamp = ParseBool(stringValue2, () => section.Path)!;
                            }
                        }
                        break;
                    default:
                        {
                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
                            {
                                (temp ??= new List<string>()).Add($"'{section.Key}'");
                            }
                        }
                        break;
                }
            }

            if (temp is not null)
            {
                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(ConsoleFormatterOptions)}: {string.Join(", ", temp)}");
            }
        }

        public static void BindCore(IConfiguration configuration, ref ConsoleLoggerOptions obj, BinderOptions? binderOptions)
        {
            if (obj is null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            List<string>? temp = null;
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                switch (section.Key)
                {
                    case "DisableColors":
                        {
                            if (configuration["DisableColors"] is string stringValue3)
                            {
                                obj.DisableColors = ParseBool(stringValue3, () => section.Path)!;
                            }
                        }
                        break;
                    case "Format":
                        {
                            if (configuration["Format"] is string stringValue4)
                            {
                                obj.Format = ParseConsoleLoggerFormat(stringValue4, () => section.Path)!;
                            }
                        }
                        break;
                    case "FormatterName":
                        {
                            obj.FormatterName = configuration["FormatterName"]!;
                        }
                        break;
                    case "IncludeScopes":
                        {
                            if (configuration["IncludeScopes"] is string stringValue6)
                            {
                                obj.IncludeScopes = ParseBool(stringValue6, () => section.Path)!;
                            }
                        }
                        break;
                    case "LogToStandardErrorThreshold":
                        {
                            if (configuration["LogToStandardErrorThreshold"] is string stringValue7)
                            {
                                obj.LogToStandardErrorThreshold = ParseLogLevel(stringValue7, () => section.Path)!;
                            }
                        }
                        break;
                    case "TimestampFormat":
                        {
                            obj.TimestampFormat = configuration["TimestampFormat"]!;
                        }
                        break;
                    case "UseUtcTimestamp":
                        {
                            if (configuration["UseUtcTimestamp"] is string stringValue9)
                            {
                                obj.UseUtcTimestamp = ParseBool(stringValue9, () => section.Path)!;
                            }
                        }
                        break;
                    case "QueueFullMode":
                        {
                            if (configuration["QueueFullMode"] is string stringValue10)
                            {
                                obj.QueueFullMode = ParseConsoleLoggerQueueFullMode(stringValue10, () => section.Path)!;
                            }
                        }
                        break;
                    case "MaxQueueLength":
                        {
                            if (configuration["MaxQueueLength"] is string stringValue11)
                            {
                                obj.MaxQueueLength = ParseInt(stringValue11, () => section.Path)!;
                            }
                        }
                        break;
                    default:
                        {
                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
                            {
                                (temp ??= new List<string>()).Add($"'{section.Key}'");
                            }
                        }
                        break;
                }
            }

            if (temp is not null)
            {
                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(ConsoleLoggerOptions)}: {string.Join(", ", temp)}");
            }
        }


        public static void BindCore(IConfiguration configuration, ref JsonWriterOptions obj, BinderOptions? binderOptions)
        {
            List<string>? temp = null;
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                switch (section.Key)
                {
                    case "Encoder":
                        {
                            throw new InvalidOperationException("Cannot create instance of type 'System.Text.Encodings.Web.JavaScriptEncoder' because it is missing a public instance constructor.");
                        }
                    case "Indented":
                        {
                            if (configuration["Indented"] is string stringValue12)
                            {
                                obj.Indented = ParseBool(stringValue12, () => section.Path)!;
                            }
                        }
                        break;
                    case "MaxDepth":
                        {
                            if (configuration["MaxDepth"] is string stringValue13)
                            {
                                obj.MaxDepth = ParseInt(stringValue13, () => section.Path)!;
                            }
                        }
                        break;
                    case "SkipValidation":
                        {
                            if (configuration["SkipValidation"] is string stringValue14)
                            {
                                obj.SkipValidation = ParseBool(stringValue14, () => section.Path)!;
                            }
                        }
                        break;
                    default:
                        {
                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
                            {
                                (temp ??= new List<string>()).Add($"'{section.Key}'");
                            }
                        }
                        break;
                }
            }

            if (temp is not null)
            {
                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(JsonWriterOptions)}: {string.Join(", ", temp)}");
            }
        }

        public static void BindCore(IConfiguration configuration, ref JsonConsoleFormatterOptions obj, BinderOptions? binderOptions)
        {
            if (obj is null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            List<string>? temp = null;
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                switch (section.Key)
                {
                    case "JsonWriterOptions":
                        {
                            if (HasChildren(section))
                            {
                                JsonWriterOptions temp15 = obj.JsonWriterOptions;
                                BindCore(section, ref temp15, binderOptions);
                                obj.JsonWriterOptions = temp15;
                            }
                        }
                        break;
                    case "IncludeScopes":
                        {
                            if (configuration["IncludeScopes"] is string stringValue16)
                            {
                                obj.IncludeScopes = ParseBool(stringValue16, () => section.Path)!;
                            }
                        }
                        break;
                    case "TimestampFormat":
                        {
                            obj.TimestampFormat = configuration["TimestampFormat"]!;
                        }
                        break;
                    case "UseUtcTimestamp":
                        {
                            if (configuration["UseUtcTimestamp"] is string stringValue18)
                            {
                                obj.UseUtcTimestamp = ParseBool(stringValue18, () => section.Path)!;
                            }
                        }
                        break;
                    default:
                        {
                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
                            {
                                (temp ??= new List<string>()).Add($"'{section.Key}'");
                            }
                        }
                        break;
                }
            }

            if (temp is not null)
            {
                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(JsonConsoleFormatterOptions)}: {string.Join(", ", temp)}");
            }
        }

        public static void BindCore(IConfiguration configuration, ref SimpleConsoleFormatterOptions obj, BinderOptions? binderOptions)
        {
            if (obj is null)
            {
                throw new ArgumentNullException(nameof(obj));
            }

            List<string>? temp = null;
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                switch (section.Key)
                {
                    case "ColorBehavior":
                        {
                            if (configuration["ColorBehavior"] is string stringValue19)
                            {
                                obj.ColorBehavior = ParseLoggerColorBehavior(stringValue19, () => section.Path)!;
                            }
                        }
                        break;
                    case "SingleLine":
                        {
                            if (configuration["SingleLine"] is string stringValue20)
                            {
                                obj.SingleLine = ParseBool(stringValue20, () => section.Path)!;
                            }
                        }
                        break;
                    case "IncludeScopes":
                        {
                            if (configuration["IncludeScopes"] is string stringValue21)
                            {
                                obj.IncludeScopes = ParseBool(stringValue21, () => section.Path)!;
                            }
                        }
                        break;
                    case "TimestampFormat":
                        {
                            obj.TimestampFormat = configuration["TimestampFormat"]!;
                        }
                        break;
                    case "UseUtcTimestamp":
                        {
                            if (configuration["UseUtcTimestamp"] is string stringValue23)
                            {
                                obj.UseUtcTimestamp = ParseBool(stringValue23, () => section.Path)!;
                            }
                        }
                        break;
                    default:
                        {
                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
                            {
                                (temp ??= new List<string>()).Add($"'{section.Key}'");
                            }
                        }
                        break;
                }
            }

            if (temp is not null)
            {
                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(SimpleConsoleFormatterOptions)}: {string.Join(", ", temp)}");
            }
        }

        public static bool HasChildren(IConfiguration configuration)
        {
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                return true;
            }
            return false;
        }

        public static bool ParseBool(string stringValue, Func<string?> getPath)
        {
            try
            {
                return bool.Parse(stringValue);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception);
            }
        }

        public static ConsoleLoggerFormat ParseConsoleLoggerFormat(string stringValue, Func<string?> getPath)
        {
            try
            {
                return (ConsoleLoggerFormat)Enum.Parse(typeof(ConsoleLoggerFormat), stringValue, ignoreCase: true);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ConsoleLoggerFormat)}'.", exception);
            }
        }

        public static LogLevel ParseLogLevel(string stringValue, Func<string?> getPath)
        {
            try
            {
                return (LogLevel)Enum.Parse(typeof(LogLevel), stringValue, ignoreCase: true);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(LogLevel)}'.", exception);
            }
        }

        public static ConsoleLoggerQueueFullMode ParseConsoleLoggerQueueFullMode(string stringValue, Func<string?> getPath)
        {
            try
            {
                return (ConsoleLoggerQueueFullMode)Enum.Parse(typeof(ConsoleLoggerQueueFullMode), stringValue, ignoreCase: true);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ConsoleLoggerQueueFullMode)}'.", exception);
            }
        }

        public static int ParseInt(string stringValue, Func<string?> getPath)
        {
            try
            {
                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
            }
        }

        public static LoggerColorBehavior ParseLoggerColorBehavior(string stringValue, Func<string?> getPath)
        {
            try
            {
                return (LoggerColorBehavior)Enum.Parse(typeof(LoggerColorBehavior), stringValue, ignoreCase: true);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(LoggerColorBehavior)}'.", exception);
            }
        }
    }
}
Author: layomia
Assignees: layomia
Labels:

area-Extensions-Logging

Milestone: 8.0.0

@layomia
Copy link
Contributor Author

layomia commented Jun 28, 2023

The failing test is due to #88112.

@layomia layomia marked this pull request as draft June 28, 2023 00:04
@layomia layomia added the blocked Issue/PR is blocked on something - see comments label Jun 28, 2023
@eerhardt
Copy link
Member

The failing test is due to #88112.

That was totally a mistake in the test to have different cases. 😊 But at least now we know we have decent test coverage.

@ericstj
Copy link
Member

ericstj commented Jun 29, 2023

If you wanted to unblock this you could fix that test to have matching case, the have the fix for #88112 add test case(s).

@layomia layomia removed the blocked Issue/PR is blocked on something - see comments label Jun 29, 2023
@layomia
Copy link
Contributor Author

layomia commented Jun 29, 2023

Taking Eric's (St. J :) suggestion - #88067 (comment).

@layomia layomia marked this pull request as ready for review June 29, 2023 17:51
@layomia
Copy link
Contributor Author

layomia commented Jul 12, 2023

Generated code following #88338 (click to view) ```C# // #nullable enable #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.

///

Generated helper providing an AOT and linking compatible implementation for configuration binding.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
internal static class GeneratedConfigurationBinder
{
/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.
public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Microsoft.Extensions.Logging.Console.ConsoleFormatterOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null);

/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Microsoft.Extensions.Logging.Console.ConsoleLoggerOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null);

/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Microsoft.Extensions.Logging.Console.JsonConsoleFormatterOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null);

/// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Microsoft.Extensions.Logging.Console.SimpleConsoleFormatterOptions obj) => global::Microsoft.Extensions.Configuration.Binder.SourceGeneration.CoreBindingHelper.BindCore(configuration, ref obj, binderOptions: null);

}

namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Globalization;
using System.Text.Encodings.Web;
using System.Text.Json;

/// <summary>Provide core binding logic.</summary>
[GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
file static class CoreBindingHelper
{
    private readonly static Lazy<HashSet<string>> s_configKeys_ConsoleFormatterOptions = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "IncludeScopes", "TimestampFormat", "UseUtcTimestamp" });
    private readonly static Lazy<HashSet<string>> s_configKeys_ConsoleLoggerOptions = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "DisableColors", "Format", "FormatterName", "IncludeScopes", "LogToStandardErrorThreshold", "TimestampFormat", "UseUtcTimestamp", "QueueFullMode", "MaxQueueLength" });
    private readonly static Lazy<HashSet<string>> s_configKeys_JsonWriterOptions = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Encoder", "Indented", "MaxDepth", "SkipValidation" });
    private readonly static Lazy<HashSet<string>> s_configKeys_JsonConsoleFormatterOptions = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "JsonWriterOptions", "IncludeScopes", "TimestampFormat", "UseUtcTimestamp" });
    private readonly static Lazy<HashSet<string>> s_configKeys_SimpleConsoleFormatterOptions = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "ColorBehavior", "SingleLine", "IncludeScopes", "TimestampFormat", "UseUtcTimestamp" });

    public static void BindCore(IConfiguration configuration, ref ConsoleFormatterOptions obj, BinderOptions? binderOptions)
    {
        if (obj is null)
        {
            throw new ArgumentNullException(nameof(obj));
        }

        ValidateConfigurationKeys(typeof(ConsoleFormatterOptions), s_configKeys_ConsoleFormatterOptions, configuration, binderOptions);

        if (configuration["IncludeScopes"] is string value0)
        {
            obj.IncludeScopes = ParseBool(value0, () => configuration.GetSection("IncludeScopes").Path);
        }

        obj.TimestampFormat = configuration["TimestampFormat"]!;

        if (configuration["UseUtcTimestamp"] is string value2)
        {
            obj.UseUtcTimestamp = ParseBool(value2, () => configuration.GetSection("UseUtcTimestamp").Path);
        }
    }

    public static void BindCore(IConfiguration configuration, ref ConsoleLoggerOptions obj, BinderOptions? binderOptions)
    {
        if (obj is null)
        {
            throw new ArgumentNullException(nameof(obj));
        }

        ValidateConfigurationKeys(typeof(ConsoleLoggerOptions), s_configKeys_ConsoleLoggerOptions, configuration, binderOptions);

        if (configuration["DisableColors"] is string value3)
        {
            obj.DisableColors = ParseBool(value3, () => configuration.GetSection("DisableColors").Path);
        }

        if (configuration["Format"] is string value4)
        {
            obj.Format = ParseConsoleLoggerFormat(value4, () => configuration.GetSection("Format").Path);
        }

        obj.FormatterName = configuration["FormatterName"]!;

        if (configuration["IncludeScopes"] is string value6)
        {
            obj.IncludeScopes = ParseBool(value6, () => configuration.GetSection("IncludeScopes").Path);
        }

        if (configuration["LogToStandardErrorThreshold"] is string value7)
        {
            obj.LogToStandardErrorThreshold = ParseLogLevel(value7, () => configuration.GetSection("LogToStandardErrorThreshold").Path);
        }

        obj.TimestampFormat = configuration["TimestampFormat"]!;

        if (configuration["UseUtcTimestamp"] is string value9)
        {
            obj.UseUtcTimestamp = ParseBool(value9, () => configuration.GetSection("UseUtcTimestamp").Path);
        }

        if (configuration["QueueFullMode"] is string value10)
        {
            obj.QueueFullMode = ParseConsoleLoggerQueueFullMode(value10, () => configuration.GetSection("QueueFullMode").Path);
        }

        if (configuration["MaxQueueLength"] is string value11)
        {
            obj.MaxQueueLength = ParseInt(value11, () => configuration.GetSection("MaxQueueLength").Path);
        }
    }

    public static void BindCore(IConfiguration configuration, ref JsonWriterOptions obj, BinderOptions? binderOptions)
    {
        ValidateConfigurationKeys(typeof(JsonWriterOptions), s_configKeys_JsonWriterOptions, configuration, binderOptions);

        if (AsConfigWithChildren(configuration.GetSection("Encoder")) is IConfigurationSection section12)
        {
            throw new InvalidOperationException("Cannot create instance of type 'System.Text.Encodings.Web.JavaScriptEncoder' because it is missing a public instance constructor.");
        }

        if (configuration["Indented"] is string value13)
        {
            obj.Indented = ParseBool(value13, () => configuration.GetSection("Indented").Path);
        }

        if (configuration["MaxDepth"] is string value14)
        {
            obj.MaxDepth = ParseInt(value14, () => configuration.GetSection("MaxDepth").Path);
        }

        if (configuration["SkipValidation"] is string value15)
        {
            obj.SkipValidation = ParseBool(value15, () => configuration.GetSection("SkipValidation").Path);
        }
    }

    public static void BindCore(IConfiguration configuration, ref JsonConsoleFormatterOptions obj, BinderOptions? binderOptions)
    {
        if (obj is null)
        {
            throw new ArgumentNullException(nameof(obj));
        }

        ValidateConfigurationKeys(typeof(JsonConsoleFormatterOptions), s_configKeys_JsonConsoleFormatterOptions, configuration, binderOptions);

        if (AsConfigWithChildren(configuration.GetSection("JsonWriterOptions")) is IConfigurationSection section16)
        {
            JsonWriterOptions temp17 = obj.JsonWriterOptions;
            var temp18 = new JsonWriterOptions();
            BindCore(section16, ref temp18, binderOptions);
            obj.JsonWriterOptions = temp18;
            temp17 = temp18;
        }

        if (configuration["IncludeScopes"] is string value19)
        {
            obj.IncludeScopes = ParseBool(value19, () => configuration.GetSection("IncludeScopes").Path);
        }

        obj.TimestampFormat = configuration["TimestampFormat"]!;

        if (configuration["UseUtcTimestamp"] is string value21)
        {
            obj.UseUtcTimestamp = ParseBool(value21, () => configuration.GetSection("UseUtcTimestamp").Path);
        }
    }

    public static void BindCore(IConfiguration configuration, ref SimpleConsoleFormatterOptions obj, BinderOptions? binderOptions)
    {
        if (obj is null)
        {
            throw new ArgumentNullException(nameof(obj));
        }

        ValidateConfigurationKeys(typeof(SimpleConsoleFormatterOptions), s_configKeys_SimpleConsoleFormatterOptions, configuration, binderOptions);

        if (configuration["ColorBehavior"] is string value22)
        {
            obj.ColorBehavior = ParseLoggerColorBehavior(value22, () => configuration.GetSection("ColorBehavior").Path);
        }

        if (configuration["SingleLine"] is string value23)
        {
            obj.SingleLine = ParseBool(value23, () => configuration.GetSection("SingleLine").Path);
        }

        if (configuration["IncludeScopes"] is string value24)
        {
            obj.IncludeScopes = ParseBool(value24, () => configuration.GetSection("IncludeScopes").Path);
        }

        obj.TimestampFormat = configuration["TimestampFormat"]!;

        if (configuration["UseUtcTimestamp"] is string value26)
        {
            obj.UseUtcTimestamp = ParseBool(value26, () => configuration.GetSection("UseUtcTimestamp").Path);
        }
    }

    /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
    public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
    {
        if (binderOptions?.ErrorOnUnknownConfiguration is true)
        {
            List<string>? temp = null;
            foreach (IConfigurationSection section in configuration.GetChildren())
            {
                if (!keys.Value.Contains(section.Key))
                {
                    (temp ??= new List<string>()).Add($"'{section.Key}'");
                }
            }
            if (temp is not null)
            {
                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
            }
        }
    }

    public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
    {
        foreach (IConfigurationSection _ in configuration.GetChildren())
        {
            return configuration;
        }
        return null;
    }

    public static bool ParseBool(string value, Func<string?> getPath)
    {
        try
        {
            return bool.Parse(value);
        }
        catch (Exception exception)
        {
            throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(bool)}'.", exception);
        }
    }

    public static ConsoleLoggerFormat ParseConsoleLoggerFormat(string value, Func<string?> getPath)
    {
        try
        {
            return (ConsoleLoggerFormat)Enum.Parse(typeof(ConsoleLoggerFormat), value, ignoreCase: true);
        }
        catch (Exception exception)
        {
            throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ConsoleLoggerFormat)}'.", exception);
        }
    }

    public static LogLevel ParseLogLevel(string value, Func<string?> getPath)
    {
        try
        {
            return (LogLevel)Enum.Parse(typeof(LogLevel), value, ignoreCase: true);
        }
        catch (Exception exception)
        {
            throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(LogLevel)}'.", exception);
        }
    }

    public static ConsoleLoggerQueueFullMode ParseConsoleLoggerQueueFullMode(string value, Func<string?> getPath)
    {
        try
        {
            return (ConsoleLoggerQueueFullMode)Enum.Parse(typeof(ConsoleLoggerQueueFullMode), value, ignoreCase: true);
        }
        catch (Exception exception)
        {
            throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(ConsoleLoggerQueueFullMode)}'.", exception);
        }
    }

    public static int ParseInt(string value, Func<string?> getPath)
    {
        try
        {
            return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
        }
        catch (Exception exception)
        {
            throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(int)}'.", exception);
        }
    }

    public static LoggerColorBehavior ParseLoggerColorBehavior(string value, Func<string?> getPath)
    {
        try
        {
            return (LoggerColorBehavior)Enum.Parse(typeof(LoggerColorBehavior), value, ignoreCase: true);
        }
        catch (Exception exception)
        {
            throw new InvalidOperationException($"Failed to convert configuration value at '{getPath()}' to type '{typeof(LoggerColorBehavior)}'.", exception);
        }
    }
}

}

</details>

@layomia layomia requested a review from eerhardt July 12, 2023 19:06
Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking really good, @layomia.

Copy link
Member

@tarekgh tarekgh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modulo @eerhardt comments, LGTM.

Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks @layomia!

Copy link
Member

@eiriktsarpalis eiriktsarpalis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to see all this bespoke code being removed 👍

@layomia
Copy link
Contributor Author

layomia commented Jul 14, 2023

Failing checks unrelated; check with running indicator appears to be complete -https://dev.azure.com/dnceng-public/public/_build/results?buildId=339049&view=logs&jobId=f02b8cf9-dd4d-54fc-c292-2bb1d305b019.

@layomia layomia merged commit e264ba7 into dotnet:main Jul 14, 2023
88 of 103 checks passed
@ghost ghost locked as resolved and limited conversation to collaborators Aug 13, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants