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 ConfigurationBinder usage from Console Logging #82098

Merged
merged 3 commits into from
Feb 16, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.Versioning;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.Logging
{
/// <summary>
/// Configures a ConsoleFormatterOptions object from an IConfiguration.
/// </summary>
/// <remarks>
/// Doesn't use ConfigurationBinder in order to allow ConfigurationBinder, and all its dependencies,
/// to be trimmed. This improves app size and startup.
/// </remarks>
[UnsupportedOSPlatform("browser")]
internal sealed class ConsoleFormatterConfigureOptions : IConfigureOptions<ConsoleFormatterOptions>
{
private readonly IConfiguration _configuration;

public ConsoleFormatterConfigureOptions(ILoggerProviderConfiguration<ConsoleLoggerProvider> providerConfiguration)
{
_configuration = providerConfiguration.GetFormatterOptionsSection();
}

public void Configure(ConsoleFormatterOptions options) => Bind(_configuration, options);

public static void Bind(IConfiguration configuration, ConsoleFormatterOptions options)
tarekgh marked this conversation as resolved.
Show resolved Hide resolved
{
if (ConsoleLoggerConfigureOptions.ParseBool(configuration, "IncludeScopes", out bool includeScopes))
{
options.IncludeScopes = includeScopes;
}

if (configuration["TimestampFormat"] is string timestampFormat)
{
options.TimestampFormat = timestampFormat;
}

if (ConsoleLoggerConfigureOptions.ParseBool(configuration, "UseUtcTimestamp", out bool useUtcTimestamp))
{
options.UseUtcTimestamp = useUtcTimestamp;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Globalization;
using System.Runtime.Versioning;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.Logging
{
/// <summary>
/// Configures a ConsoleLoggerOptions object from an IConfiguration.
/// </summary>
/// <remarks>
/// Doesn't use ConfigurationBinder in order to allow ConfigurationBinder, and all its dependencies,
/// to be trimmed. This improves app size and startup.
/// </remarks>
[UnsupportedOSPlatform("browser")]
internal sealed class ConsoleLoggerConfigureOptions : IConfigureOptions<ConsoleLoggerOptions>
{
private readonly IConfiguration _configuration;

public ConsoleLoggerConfigureOptions(ILoggerProviderConfiguration<ConsoleLoggerProvider> providerConfiguration)
{
_configuration = providerConfiguration.Configuration;
}

public void Configure(ConsoleLoggerOptions options)
{
if (ParseBool(_configuration, "DisableColors", out bool disableColors))
{
#pragma warning disable CS0618 // Type or member is obsolete
options.DisableColors = disableColors;
#pragma warning restore CS0618 // Type or member is obsolete
}

#pragma warning disable CS0618 // Type or member is obsolete
if (ParseEnum(_configuration, "Format", out ConsoleLoggerFormat format))
{
options.Format = format;
}
#pragma warning restore CS0618 // Type or member is obsolete

if (_configuration["FormatterName"] is string formatterName)
{
options.FormatterName = formatterName;
}

if (ParseBool(_configuration, "IncludeScopes", out bool includeScopes))
{
#pragma warning disable CS0618 // Type or member is obsolete
options.IncludeScopes = includeScopes;
#pragma warning restore CS0618 // Type or member is obsolete
}

if (ParseEnum(_configuration, "LogToStandardErrorThreshold", out LogLevel logToStandardErrorThreshold))
{
options.LogToStandardErrorThreshold = logToStandardErrorThreshold;
}

if (ParseInt(_configuration, "MaxQueueLength", out int maxQueueLength))
{
options.MaxQueueLength = maxQueueLength;
}

if (ParseEnum(_configuration, "QueueFullMode", out ConsoleLoggerQueueFullMode queueFullMode))
{
options.QueueFullMode = queueFullMode;
}

if (_configuration["TimestampFormat"] is string timestampFormat)
{
#pragma warning disable CS0618 // Type or member is obsolete
options.TimestampFormat = timestampFormat;
#pragma warning restore CS0618 // Type or member is obsolete
}

if (ParseBool(_configuration, "UseUtcTimestamp", out bool useUtcTimestamp))
{
#pragma warning disable CS0618 // Type or member is obsolete
options.UseUtcTimestamp = useUtcTimestamp;
#pragma warning restore CS0618 // Type or member is obsolete
}
}

/// <summary>
/// Parses the configuration value at the specified key into a bool.
/// </summary>
/// <returns>true if the value was successfully found and parsed. false if the key wasn't found.</returns>
/// <exception cref="InvalidOperationException">Thrown when invalid data was found at the specified configuration key.</exception>
public static bool ParseBool(IConfiguration configuration, string key, out bool value)
{
if (configuration[key] is string valueString)
{
try
{
value = bool.Parse(valueString);
return true;
}
catch (Exception e)
{
ThrowInvalidConfigurationException(configuration, key, typeof(bool), e);
}
}

value = default;
return false;
}

/// <summary>
/// Parses the configuration value at the specified key into an enum.
/// </summary>
/// <returns>true if the value was successfully found and parsed. false if the key wasn't found.</returns>
/// <exception cref="InvalidOperationException">Thrown when invalid data was found at the specified configuration key.</exception>
public static bool ParseEnum<T>(IConfiguration configuration, string key, out T value) where T : struct
{
if (configuration[key] is string valueString)
{
try
{
value =
#if NETFRAMEWORK || NETSTANDARD2_0
(T)Enum.Parse(typeof(T), valueString, ignoreCase: true);
#else
Enum.Parse<T>(valueString, ignoreCase: true);
#endif
return true;
}
catch (Exception e)
{
ThrowInvalidConfigurationException(configuration, key, typeof(T), e);
}
}

value = default;
return false;
}

/// <summary>
/// Parses the configuration value at the specified key into an int.
/// </summary>
/// <returns>true if the value was successfully found and parsed. false if the key wasn't found.</returns>
/// <exception cref="InvalidOperationException">Thrown when invalid data was found at the specified configuration key.</exception>
public static bool ParseInt(IConfiguration configuration, string key, out int value)
{
if (configuration[key] is string valueString)
{
try
{
value = int.Parse(valueString, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
return true;
}
catch (Exception e)
{
ThrowInvalidConfigurationException(configuration, key, typeof(int), e);
}
}

value = default;
return false;
}

private static void ThrowInvalidConfigurationException(IConfiguration configuration, string key, Type valueType, Exception innerException)
{
IConfigurationSection section = configuration.GetSection(key);
throw new InvalidOperationException(SR.Format(SR.InvalidConfigurationData, section.Path, valueType), innerException);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,18 @@ public static class ConsoleLoggerExtensions
/// Adds a console logger named 'Console' to the factory.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "AddConsoleFormatter and RegisterProviderOptions are only called with Options types which only have simple properties.")]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "AddConsoleFormatter and RegisterProviderOptions are only dangerous when the Options type cannot be statically analyzed, but that is not the case here. " +
"The DynamicallyAccessedMembers annotations on them will make sure to preserve the right members from the different options objects.")]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(JsonWriterOptions))]
public static ILoggingBuilder AddConsole(this ILoggingBuilder builder)
{
builder.AddConfiguration();

builder.AddConsoleFormatter<JsonConsoleFormatter, JsonConsoleFormatterOptions>();
builder.AddConsoleFormatter<SystemdConsoleFormatter, ConsoleFormatterOptions>();
builder.AddConsoleFormatter<SimpleConsoleFormatter, SimpleConsoleFormatterOptions>();
builder.AddConsoleFormatter<JsonConsoleFormatter, JsonConsoleFormatterOptions, JsonConsoleFormatterConfigureOptions>();
builder.AddConsoleFormatter<SystemdConsoleFormatter, ConsoleFormatterOptions, ConsoleFormatterConfigureOptions>();
builder.AddConsoleFormatter<SimpleConsoleFormatter, SimpleConsoleFormatterOptions, SimpleConsoleFormatterConfigureOptions>();

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, ConsoleLoggerProvider>());
LoggerProviderOptions.RegisterProviderOptions<ConsoleLoggerOptions, ConsoleLoggerProvider>(builder.Services);

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<ConsoleLoggerOptions>, ConsoleLoggerConfigureOptions>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<ConsoleLoggerOptions>, LoggerProviderOptionsChangeTokenSource<ConsoleLoggerOptions, ConsoleLoggerProvider>>());

return builder;
}
Expand Down Expand Up @@ -135,13 +131,7 @@ private static ILoggingBuilder AddFormatterWithName(this ILoggingBuilder builder
where TOptions : ConsoleFormatterOptions
where TFormatter : ConsoleFormatter
{
builder.AddConfiguration();

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ConsoleFormatter, TFormatter>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<TOptions>, ConsoleLoggerFormatterConfigureOptions<TFormatter, TOptions>>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<TOptions>, ConsoleLoggerFormatterOptionsChangeTokenSource<TFormatter, TOptions>>());

return builder;
return AddConsoleFormatter<TFormatter, TOptions, ConsoleLoggerFormatterConfigureOptions<TFormatter, TOptions>>(builder);
}

/// <summary>
Expand All @@ -161,6 +151,25 @@ private static ILoggingBuilder AddFormatterWithName(this ILoggingBuilder builder
builder.Services.Configure(configure);
return builder;
}

private static ILoggingBuilder AddConsoleFormatter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFormatter, TOptions, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TConfigureOptions>(this ILoggingBuilder builder)
where TOptions : ConsoleFormatterOptions
where TFormatter : ConsoleFormatter
where TConfigureOptions : class, IConfigureOptions<TOptions>
{
builder.AddConfiguration();

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ConsoleFormatter, TFormatter>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<TOptions>, TConfigureOptions>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<TOptions>, ConsoleLoggerFormatterOptionsChangeTokenSource<TFormatter, TOptions>>());

return builder;
}

internal static IConfiguration GetFormatterOptionsSection(this ILoggerProviderConfiguration<ConsoleLoggerProvider> providerConfiguration)
{
return providerConfiguration.Configuration.GetSection("FormatterOptions");
}
}

[UnsupportedOSPlatform("browser")]
Expand All @@ -171,7 +180,7 @@ internal sealed class ConsoleLoggerFormatterConfigureOptions<TFormatter, [Dynami
[RequiresDynamicCode(ConsoleLoggerExtensions.RequiresDynamicCodeMessage)]
[RequiresUnreferencedCode(ConsoleLoggerExtensions.TrimmingRequiresUnreferencedCodeMessage)]
public ConsoleLoggerFormatterConfigureOptions(ILoggerProviderConfiguration<ConsoleLoggerProvider> providerConfiguration) :
base(providerConfiguration.Configuration.GetSection("FormatterOptions"))
base(providerConfiguration.GetFormatterOptionsSection())
{
}
}
Expand All @@ -182,7 +191,7 @@ internal sealed class ConsoleLoggerFormatterOptionsChangeTokenSource<TFormatter,
where TFormatter : ConsoleFormatter
{
public ConsoleLoggerFormatterOptionsChangeTokenSource(ILoggerProviderConfiguration<ConsoleLoggerProvider> providerConfiguration)
: base(providerConfiguration.Configuration.GetSection("FormatterOptions"))
: base(providerConfiguration.GetFormatterOptionsSection())
{
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.Versioning;
using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.Logging
{
/// <summary>
/// Configures a JsonConsoleFormatterOptions object from an IConfiguration.
/// </summary>
/// <remarks>
/// Doesn't use ConfigurationBinder in order to allow ConfigurationBinder, and all its dependencies,
/// to be trimmed. This improves app size and startup.
/// </remarks>
[UnsupportedOSPlatform("browser")]
internal sealed class JsonConsoleFormatterConfigureOptions : IConfigureOptions<JsonConsoleFormatterOptions>
{
private readonly IConfiguration _configuration;

public JsonConsoleFormatterConfigureOptions(ILoggerProviderConfiguration<ConsoleLoggerProvider> providerConfiguration)
{
_configuration = providerConfiguration.GetFormatterOptionsSection();
}

public void Configure(JsonConsoleFormatterOptions options)
{
ConsoleFormatterConfigureOptions.Bind(_configuration, options);

if (_configuration.GetSection("JsonWriterOptions") is IConfigurationSection jsonWriterOptionsConfig)
{
JsonWriterOptions jsonWriterOptions = options.JsonWriterOptions;

if (ConsoleLoggerConfigureOptions.ParseBool(jsonWriterOptionsConfig, "Indented", out bool indented))
{
jsonWriterOptions.Indented = indented;
}

if (ConsoleLoggerConfigureOptions.ParseInt(jsonWriterOptionsConfig, "MaxDepth", out int maxDepth))
{
jsonWriterOptions.MaxDepth = maxDepth;
}

if (ConsoleLoggerConfigureOptions.ParseBool(jsonWriterOptionsConfig, "SkipValidation", out bool skipValidation))
{
jsonWriterOptions.SkipValidation = skipValidation;
}

options.JsonWriterOptions = jsonWriterOptions;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,7 @@
<data name="WarningMessageOnDrop" xml:space="preserve">
<value>{0} message(s) dropped because of queue size limit. Increase the queue size or decrease logging verbosity to avoid this. You may change `ConsoleLoggerQueueFullMode` to stop dropping messages.</value>
</data>
<data name="InvalidConfigurationData" xml:space="preserve">
<value>Failed to convert configuration value at '{0}' to type '{1}'.</value>
</data>
</root>
Loading