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

[sdk-logs] Support builder behavior on OpenTelemetryLoggerOptions (proposal 3) #4502

Closed
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
Expand Up @@ -35,6 +35,7 @@ public static IServiceCollection AddOpenTelemetryLoggerProviderBuilderServices(t

services!.TryAddSingleton<LoggerProviderBuilderSdk>();
services!.RegisterOptionsFactory(configuration => new BatchExportLogRecordProcessorOptions(configuration));
services!.RegisterOptionsFactory((sp, configuration, name) => new OpenTelemetryLoggerOptions(sp.GetRequiredService<LoggerProviderBuilderSdk>()));

return services!;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Internal;
using OpenTelemetry.Resources;

Expand All @@ -28,6 +29,24 @@ namespace OpenTelemetry.Logs;
/// </summary>
internal static class LoggerProviderBuilderExtensions
{
/// <summary>
/// Registers a configuration action for the <see
/// cref="OpenTelemetryLoggerOptions"/> used by <see cref="ILogger"/>
/// integration (<see cref="OpenTelemetryLoggerProvider"/>).
/// </summary>
/// <param name="loggerProviderBuilder"><see cref="LoggerProviderBuilder"/>.</param>
/// <param name="configure">Configuration action.</param>
/// <returns>Returns <see cref="LoggerProviderBuilder"/> for chaining.</returns>
public static LoggerProviderBuilder ConfigureLoggerOptions(
this LoggerProviderBuilder loggerProviderBuilder,
Action<OpenTelemetryLoggerOptions> configure)
{
Guard.ThrowIfNull(configure);

return loggerProviderBuilder.ConfigureServices(
services => services.Configure(configure));
}

/// <summary>
/// Sets the <see cref="ResourceBuilder"/> from which the Resource associated with
/// this provider is built from. Overwrites currently set ResourceBuilder.
Expand Down
122 changes: 113 additions & 9 deletions src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#nullable enable

using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Internal;
using OpenTelemetry.Resources;

Expand All @@ -27,8 +28,30 @@ namespace OpenTelemetry.Logs;
/// </summary>
public class OpenTelemetryLoggerOptions
{
internal readonly List<BaseProcessor<LogRecord>> Processors = new();
internal ResourceBuilder? ResourceBuilder;
private readonly LoggerProviderBuilder? loggerProviderBuilder;
private readonly LoggerProviderServiceCollectionBuilder? serviceCollectionBuilder;
private readonly LoggerProviderBuilderSdk? loggerProviderBuilderSdk;

private bool includeFormattedMessage;
private bool includeScopes;
private bool parseStateValues;
private bool includeAttributes = true;
private bool includeTraceState;

public OpenTelemetryLoggerOptions()
{
this.loggerProviderBuilder = Sdk.CreateLoggerProviderBuilder();
}

internal OpenTelemetryLoggerOptions(LoggerProviderServiceCollectionBuilder loggerProviderServiceCollection)
{
this.serviceCollectionBuilder = loggerProviderServiceCollection;
}

internal OpenTelemetryLoggerOptions(LoggerProviderBuilderSdk loggerProviderBuilderSdk)
{
this.loggerProviderBuilderSdk = loggerProviderBuilderSdk;
}

/// <summary>
/// Gets or sets a value indicating whether or not formatted log message
Expand All @@ -41,14 +64,22 @@ public class OpenTelemetryLoggerOptions
/// message template is not found, a formatted log message is always
/// included.
/// </remarks>
public bool IncludeFormattedMessage { get; set; }
public bool IncludeFormattedMessage
{
get => this.GetFeature(o => o.includeFormattedMessage);
set => this.SetFeature(o => o.includeFormattedMessage = value);
}

/// <summary>
/// Gets or sets a value indicating whether or not log scopes should be
/// included on generated <see cref="LogRecord"/>s. Default value:
/// <see langword="false"/>.
/// </summary>
public bool IncludeScopes { get; set; }
public bool IncludeScopes
{
get => this.GetFeature(o => o.includeScopes);
set => this.SetFeature(o => o.includeScopes = value);
}

/// <summary>
/// Gets or sets a value indicating whether or not log state should be
Expand All @@ -67,33 +98,46 @@ public class OpenTelemetryLoggerOptions
/// langword="null"/>.</item>
/// </list>
/// </remarks>
public bool ParseStateValues { get; set; }
public bool ParseStateValues
{
get => this.GetFeature(o => o.parseStateValues);
set => this.SetFeature(o => o.parseStateValues = value);
}

/// <summary>
/// Gets or sets a value indicating whether or not attributes specified
/// via log state should be included on generated <see
/// cref="LogRecord"/>s. Default value: <see langword="true"/>.
/// </summary>
internal bool IncludeAttributes { get; set; } = true;
internal bool IncludeAttributes
{
get => this.GetFeature(o => o.includeAttributes);
set => this.SetFeature(o => o.includeAttributes = value);
}

/// <summary>
/// Gets or sets a value indicating whether or not the <see
/// cref="Activity.TraceStateString"/> for the current <see
/// cref="Activity"/> should be included on generated <see
/// cref="LogRecord"/>s. Default value: <see langword="false"/>.
/// </summary>
internal bool IncludeTraceState { get; set; }
internal bool IncludeTraceState
{
get => this.GetFeature(o => o.includeTraceState);
set => this.SetFeature(o => o.includeTraceState = value);
}

/// <summary>
/// Adds processor to the options.
/// </summary>
/// <param name="processor">Log processor to add.</param>
/// <returns>Returns <see cref="OpenTelemetryLoggerOptions"/> for chaining.</returns>
// todo: [Obsolete("Call ConfigureOpenTelemetry instead AddProcessor will be removed in a future version.")]
public OpenTelemetryLoggerOptions AddProcessor(BaseProcessor<LogRecord> processor)
{
Guard.ThrowIfNull(processor);

this.Processors.Add(processor);
this.ConfigureOpenTelemetry(builder => builder.AddProcessor(processor));

return this;
}
Expand All @@ -104,14 +148,51 @@ public OpenTelemetryLoggerOptions AddProcessor(BaseProcessor<LogRecord> processo
/// </summary>
/// <param name="resourceBuilder"><see cref="ResourceBuilder"/> from which Resource will be built.</param>
/// <returns>Returns <see cref="OpenTelemetryLoggerOptions"/> for chaining.</returns>
// todo: [Obsolete("Call ConfigureOpenTelemetry instead SetResourceBuilder will be removed in a future version.")]
public OpenTelemetryLoggerOptions SetResourceBuilder(ResourceBuilder resourceBuilder)
{
Guard.ThrowIfNull(resourceBuilder);

this.ResourceBuilder = resourceBuilder;
this.ConfigureOpenTelemetry(builder => builder.SetResourceBuilder(resourceBuilder));

return this;
}

internal void ConfigureOpenTelemetry(Action<LoggerProviderBuilder> configure)
{
Guard.ThrowIfNull(configure);

if (this.serviceCollectionBuilder != null)
{
// Used during AddOpenTelemetry lifetime. IServiceCollection is open, IServiceProvider is unavailable.
configure(this.serviceCollectionBuilder);
}
else if (this.loggerProviderBuilderSdk != null)
{
// Used during OpenTelemetryLoggerProvider ctor lifetime. IServiceCollection is closed, IServiceProvider is available.
configure(this.loggerProviderBuilderSdk);
}
else if (this.loggerProviderBuilder != null)
{
// Only used for new OpenTelemetryLoggerOptions(). Shouldn't really be done but it is possible in the shipped APIs.
configure(this.loggerProviderBuilder);
}
else
{
throw new NotSupportedException("ConfigureOpenTelemetry is not supported on manually created OpenTelemetryLoggerOptions instances.");
}
}

internal LoggerProvider Build()
{
if (this.loggerProviderBuilder != null)
{
return this.loggerProviderBuilder.Build();
}

throw new NotSupportedException("Build is only supported on manually created OpenTelemetryLoggerOptions instances.");
}

internal OpenTelemetryLoggerOptions Copy()
{
return new()
Expand All @@ -123,4 +204,27 @@ internal OpenTelemetryLoggerOptions Copy()
IncludeTraceState = this.IncludeTraceState,
};
}

private void SetFeature(Action<OpenTelemetryLoggerOptions> action)
{
if (this.serviceCollectionBuilder != null)
{
this.serviceCollectionBuilder.ConfigureServices(services =>
services.Configure<OpenTelemetryLoggerOptions>(o => action(o)));
}
else
{
action(this);
}
}

private bool GetFeature(Func<OpenTelemetryLoggerOptions, bool> func)
{
if (this.serviceCollectionBuilder != null)
{
throw new NotSupportedException("OpenTelemetryLoggerOptions cannot be read during AddOpenTelemetry invocation.");
}

return func(this);
}
}
17 changes: 1 addition & 16 deletions src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,7 @@ public OpenTelemetryLoggerProvider(IOptionsMonitor<OpenTelemetryLoggerOptions> o

var optionsInstance = options.CurrentValue;

this.Provider = Sdk
.CreateLoggerProviderBuilder()
.ConfigureBuilder((sp, builder) =>
{
if (optionsInstance.ResourceBuilder != null)
{
builder.SetResourceBuilder(optionsInstance.ResourceBuilder);
}

foreach (var processor in optionsInstance.Processors)
{
builder.AddProcessor(processor);
}
})
.Build();

this.Provider = optionsInstance.Build();
this.Options = optionsInstance.Copy();
this.ownsProvider = true;
}
Expand Down
56 changes: 18 additions & 38 deletions src/OpenTelemetry/Logs/ILogger/OpenTelemetryLoggingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ public static class OpenTelemetryLoggingExtensions
/// <returns>The supplied <see cref="ILoggingBuilder"/> for call chaining.</returns>
public static ILoggingBuilder AddOpenTelemetry(
this ILoggingBuilder builder)
=> AddOpenTelemetry(builder, configure: null);

/// <summary>
/// Adds an OpenTelemetry logger named 'OpenTelemetry' to the <see cref="ILoggerFactory"/>.
/// </summary>
/// <remarks><inheritdoc cref="AddOpenTelemetry(ILoggingBuilder)" path="/remarks"/></remarks>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <param name="configure">Optional configuration action.</param>
/// <returns>The supplied <see cref="ILoggingBuilder"/> for call chaining.</returns>
public static ILoggingBuilder AddOpenTelemetry(
this ILoggingBuilder builder,
Action<OpenTelemetryLoggerOptions>? configure)
{
Guard.ThrowIfNull(builder);

Expand All @@ -51,52 +63,20 @@ public static ILoggingBuilder AddOpenTelemetry(
// Note: This will bind logger options element (eg "Logging:OpenTelemetry") to OpenTelemetryLoggerOptions
LoggerProviderOptions.RegisterProviderOptions<OpenTelemetryLoggerOptions, OpenTelemetryLoggerProvider>(builder.Services);

new LoggerProviderServiceCollectionBuilder(builder.Services).ConfigureBuilder(
(sp, logging) =>
{
var options = sp.GetRequiredService<IOptionsMonitor<OpenTelemetryLoggerOptions>>().CurrentValue;

if (options.ResourceBuilder != null)
{
logging.SetResourceBuilder(options.ResourceBuilder);

options.ResourceBuilder = null;
}

foreach (var processor in options.Processors)
{
logging.AddProcessor(processor);
}

options.Processors.Clear();
});

builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton<ILoggerProvider, OpenTelemetryLoggerProvider>(
sp => new OpenTelemetryLoggerProvider(
sp.GetRequiredService<LoggerProvider>(),
sp.GetRequiredService<IOptionsMonitor<OpenTelemetryLoggerOptions>>().CurrentValue,
disposeProvider: false)));

return builder;
}
builder.Services.AddOpenTelemetrySharedProviderBuilderServices();

/// <summary>
/// Adds an OpenTelemetry logger named 'OpenTelemetry' to the <see cref="ILoggerFactory"/>.
/// </summary>
/// <remarks><inheritdoc cref="AddOpenTelemetry(ILoggingBuilder)" path="/remarks"/></remarks>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <param name="configure">Optional configuration action.</param>
/// <returns>The supplied <see cref="ILoggingBuilder"/> for call chaining.</returns>
public static ILoggingBuilder AddOpenTelemetry(
this ILoggingBuilder builder,
Action<OpenTelemetryLoggerOptions>? configure)
{
if (configure != null)
{
builder.Services.Configure(configure);
}
var options = new OpenTelemetryLoggerOptions(
new LoggerProviderServiceCollectionBuilder(builder.Services));

configure?.Invoke(options);

return AddOpenTelemetry(builder);
return builder;
}
}
6 changes: 6 additions & 0 deletions src/OpenTelemetry/Logs/LoggerProviderSdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using System.Diagnostics;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using OpenTelemetry.Internal;
using OpenTelemetry.Resources;

Expand Down Expand Up @@ -62,6 +63,11 @@ public LoggerProviderSdk(
configureProviderBuilder.ConfigureBuilder(serviceProvider!, state);
}

// Note: Accessing options here allows the final instance to apply
// ConfigureOpenTelemetry actions against the LoggerProviderBuilderSdk
// instance
_ = serviceProvider!.GetRequiredService<IOptionsMonitor<OpenTelemetryLoggerOptions>>().CurrentValue;

var resourceBuilder = state.ResourceBuilder ?? ResourceBuilder.CreateDefault();
resourceBuilder.ServiceProvider = serviceProvider;
this.Resource = resourceBuilder.Build();
Expand Down
Loading