Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[di] Expose a detached LoggerProviderBuilder extension on IServiceCollection which may modify services #4531

Merged
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
12 changes: 6 additions & 6 deletions src/OpenTelemetry.Api.ProviderBuilderExtensions/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

* Added an `IServiceCollection.ConfigureOpenTelemetryMeterProvider` overload
which may be used to configure `MeterProviderBuilder`s while the
`IServiceCollection` is modifiable (before the `IServiceProvider` has been
created).
([#4517](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4517))

Comment on lines +5 to +10
Copy link
Member Author

Choose a reason for hiding this comment

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

Merge issue from #4517

## 1.5.0-rc.1

Released 2023-May-25
Expand All @@ -18,12 +24,6 @@ Released 2023-May-25
created).
([#4508](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4508))

* Added an `IServiceCollection.ConfigureOpenTelemetryMeterProvider` overload
which may be used to configure `MeterProviderBuilder`s while the
`IServiceCollection` is modifiable (before the `IServiceProvider` has been
created).
([#4517](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4517))

## 1.5.0-alpha.2

Released 2023-Mar-31
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// <copyright file="LoggerProviderServiceCollectionBuilder.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

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

namespace OpenTelemetry.Logs;

internal sealed class LoggerProviderServiceCollectionBuilder : LoggerProviderBuilder, ILoggerProviderBuilder
{
public LoggerProviderServiceCollectionBuilder(IServiceCollection services)
{
services.ConfigureOpenTelemetryLoggerProvider((sp, builder) => this.Services = null);

this.Services = services;
}

public IServiceCollection? Services { get; set; }

public LoggerProvider? Provider => null;

/// <inheritdoc />
public override LoggerProviderBuilder AddInstrumentation<TInstrumentation>(Func<TInstrumentation> instrumentationFactory)
{
Guard.ThrowIfNull(instrumentationFactory);

this.ConfigureBuilderInternal((sp, builder) =>
{
builder.AddInstrumentation(instrumentationFactory);
});

return this;
}

/// <inheritdoc />
public LoggerProviderBuilder ConfigureServices(Action<IServiceCollection> configure)
=> this.ConfigureServicesInternal(configure);

/// <inheritdoc cref="IDeferredLoggerProviderBuilder.Configure" />
public LoggerProviderBuilder ConfigureBuilder(Action<IServiceProvider, LoggerProviderBuilder> configure)
=> this.ConfigureBuilderInternal(configure);

/// <inheritdoc />
LoggerProviderBuilder IDeferredLoggerProviderBuilder.Configure(Action<IServiceProvider, LoggerProviderBuilder> configure)
=> this.ConfigureBuilderInternal(configure);

private LoggerProviderBuilder ConfigureBuilderInternal(Action<IServiceProvider, LoggerProviderBuilder> configure)
{
var services = this.Services
?? throw new NotSupportedException("Builder cannot be configured during LoggerProvider construction.");

services.ConfigureOpenTelemetryLoggerProvider(configure);

return this;
}

private LoggerProviderBuilder ConfigureServicesInternal(Action<IServiceCollection> configure)
{
Guard.ThrowIfNull(configure);

var services = this.Services
?? throw new NotSupportedException("Services cannot be configured during LoggerProvider construction.");

configure(services);

return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,44 +26,79 @@ internal static class OpenTelemetryDependencyInjectionLoggingServiceCollectionEx
{
/// <summary>
/// Registers an action used to configure the OpenTelemetry <see
/// cref="LoggerProviderBuilder"/> used to create the <see
/// cref="LoggerProvider"/> for the <see cref="IServiceCollection"/> being
/// configured.
/// cref="LoggerProviderBuilder"/>.
/// </summary>
/// <remarks>
/// Notes:
/// <list type="bullet">
/// <item>This is safe to be called multiple times and by library authors.
/// Each registered configuration action will be applied
/// sequentially.</item>
/// <item>A <see cref="LoggerProvider"/> will not be created automatically
/// using this method. To begin collecting metrics use the
/// <item>A <see cref="LoggerProvider"/> will NOT be created automatically
/// using this method. To begin collecting logs use the
/// <c>IServiceCollection.AddOpenTelemetry</c> extension in the
/// <c>OpenTelemetry.Extensions.Hosting</c> package.</item>
/// </list>
/// </remarks>
/// <param name="services">The <see cref="IServiceCollection" /> to add
/// services to.</param>
/// <param name="services"><see cref="IServiceCollection" />.</param>
/// <param name="configure">Callback action to configure the <see
/// cref="LoggerProviderBuilder"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls
/// can be chained.</returns>
public static IServiceCollection ConfigureOpenTelemetryLoggerProvider(
this IServiceCollection services,
Action<IServiceProvider, LoggerProviderBuilder> configure)
Action<LoggerProviderBuilder> configure)
{
RegisterBuildAction(services, configure);
Guard.ThrowIfNull(services);
Guard.ThrowIfNull(configure);

configure(new LoggerProviderServiceCollectionBuilder(services));

return services;
}

private static void RegisterBuildAction(IServiceCollection services, Action<IServiceProvider, LoggerProviderBuilder> configure)
/// <summary>
/// Registers an action used to configure the OpenTelemetry <see
/// cref="LoggerProviderBuilder"/> once the <see cref="IServiceProvider"/>
/// is available.
/// </summary>
/// <remarks>
/// Notes:
/// <list type="bullet">
/// <item>This is safe to be called multiple times and by library authors.
/// Each registered configuration action will be applied
/// sequentially.</item>
/// <item>A <see cref="LoggerProvider"/> will NOT be created automatically
/// using this method. To begin collecting logs use the
/// <c>IServiceCollection.AddOpenTelemetry</c> extension in the
/// <c>OpenTelemetry.Extensions.Hosting</c> package.</item>
/// <item>The supplied configuration delegate is called once the <see
/// cref="IServiceProvider"/> is available. Services may NOT be added to a
/// <see cref="LoggerProviderBuilder"/> once the <see
/// cref="IServiceProvider"/> has been created. Many helper extensions
/// register services and may throw if invoked inside the configuration
/// delegate. If you don't need access to the <see cref="IServiceProvider"/>
/// call <see cref="ConfigureOpenTelemetryLoggerProvider(IServiceCollection,
/// Action{LoggerProviderBuilder})"/> instead which is safe to be used with
/// helper extensions.</item>
/// </list>
/// </remarks>
/// <param name="services"><see cref="IServiceCollection" />.</param>
/// <param name="configure">Callback action to configure the <see
/// cref="LoggerProviderBuilder"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls
/// can be chained.</returns>
public static IServiceCollection ConfigureOpenTelemetryLoggerProvider(
this IServiceCollection services,
Action<IServiceProvider, LoggerProviderBuilder> configure)
{
Guard.ThrowIfNull(services);
Guard.ThrowIfNull(configure);

services.AddSingleton<IConfigureLoggerProviderBuilder>(
new ConfigureLoggerProviderBuilderCallbackWrapper(configure));

return services;
}

private sealed class ConfigureLoggerProviderBuilderCallbackWrapper : IConfigureLoggerProviderBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ internal OpenTelemetryBuilder WithLogging(Action<LoggerProviderBuilder> configur
// Note: This enables ILogger integration
this.Services.AddLogging().AddOpenTelemetry();

var builder = new LoggerProviderServiceCollectionBuilder(this.Services);
var builder = new LoggerProviderBuilderBase(this.Services);

configure(builder);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="LoggerProviderServiceCollectionBuilder.cs" company="OpenTelemetry Authors">
// <copyright file="LoggerProviderBuilderBase.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -23,20 +23,17 @@
namespace OpenTelemetry.Logs;

/// <summary>
/// Contains methods for registering actions into an <see
/// cref="IServiceCollection"/> which will be used to build a <see
/// cref="LoggerProvider"/> once the <see cref="IServiceProvider"/> is
/// available.
/// Contains methods for building <see cref="LoggerProvider"/> instances.
/// </summary>
internal sealed class LoggerProviderServiceCollectionBuilder : LoggerProviderBuilder, ILoggerProviderBuilder
internal sealed class LoggerProviderBuilderBase : LoggerProviderBuilder, ILoggerProviderBuilder
Copy link
Member Author

Choose a reason for hiding this comment

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

@utpilla

Originally when this was added you commented that you would prefer it was named LoggerProviderBuilderBase so it matched tracing & metrics. Just FYI it ended up being exactly that 😄

{
private readonly bool allowBuild;
private IServiceCollection? services;
private readonly LoggerProviderServiceCollectionBuilder innerBuilder;

/// <summary>
/// Initializes a new instance of the <see cref="LoggerProviderServiceCollectionBuilder"/> class.
/// Initializes a new instance of the <see cref="LoggerProviderBuilderBase"/> class.
/// </summary>
public LoggerProviderServiceCollectionBuilder()
public LoggerProviderBuilderBase()
{
var services = new ServiceCollection();

Expand All @@ -46,24 +43,20 @@ public LoggerProviderServiceCollectionBuilder()
.TryAddSingleton<LoggerProvider>(
sp => throw new NotSupportedException("Self-contained LoggerProvider cannot be accessed using the application IServiceProvider call Build instead."));

services.ConfigureOpenTelemetryLoggerProvider((sp, builder) => this.services = null);

this.services = services;
this.innerBuilder = new LoggerProviderServiceCollectionBuilder(services);

this.allowBuild = true;
}

internal LoggerProviderServiceCollectionBuilder(IServiceCollection services)
internal LoggerProviderBuilderBase(IServiceCollection services)
{
Guard.ThrowIfNull(services);

services
.AddOpenTelemetryLoggerProviderBuilderServices()
.TryAddSingleton<LoggerProvider>(sp => new LoggerProviderSdk(sp, ownsServiceProvider: false));

services.ConfigureOpenTelemetryLoggerProvider((sp, builder) => this.services = null);

this.services = services;
this.innerBuilder = new LoggerProviderServiceCollectionBuilder(services);

this.allowBuild = false;
}
Expand All @@ -74,23 +67,26 @@ internal LoggerProviderServiceCollectionBuilder(IServiceCollection services)
/// <inheritdoc />
public override LoggerProviderBuilder AddInstrumentation<TInstrumentation>(Func<TInstrumentation> instrumentationFactory)
{
Guard.ThrowIfNull(instrumentationFactory);

this.ConfigureBuilderInternal((sp, builder) =>
{
builder.AddInstrumentation(instrumentationFactory);
});
this.innerBuilder.AddInstrumentation(instrumentationFactory);

return this;
}

/// <inheritdoc />
LoggerProviderBuilder ILoggerProviderBuilder.ConfigureServices(Action<IServiceCollection> configure)
=> this.ConfigureServicesInternal(configure);
{
this.innerBuilder.ConfigureServices(configure);

return this;
}

/// <inheritdoc />
LoggerProviderBuilder IDeferredLoggerProviderBuilder.Configure(Action<IServiceProvider, LoggerProviderBuilder> configure)
=> this.ConfigureBuilderInternal(configure);
{
this.innerBuilder.ConfigureBuilder(configure);

return this;
}

internal LoggerProvider Build()
{
Expand All @@ -99,10 +95,10 @@ internal LoggerProvider Build()
throw new NotSupportedException("A LoggerProviderBuilder bound to external service cannot be built directly. Access the LoggerProvider using the application IServiceProvider instead.");
}

var services = this.services
var services = this.innerBuilder.Services
?? throw new NotSupportedException("LoggerProviderBuilder build method cannot be called multiple times.");

this.services = null;
this.innerBuilder.Services = null;

#if DEBUG
bool validateScopes = true;
Expand All @@ -113,26 +109,4 @@ internal LoggerProvider Build()

return new LoggerProviderSdk(serviceProvider, ownsServiceProvider: true);
}

private LoggerProviderBuilder ConfigureBuilderInternal(Action<IServiceProvider, LoggerProviderBuilder> configure)
{
var services = this.services
?? throw new NotSupportedException("Builder cannot be configured during LoggerProvider construction.");

services.ConfigureOpenTelemetryLoggerProvider(configure);

return this;
}

private LoggerProviderBuilder ConfigureServicesInternal(Action<IServiceCollection> configure)
{
Guard.ThrowIfNull(configure);

var services = this.services
?? throw new NotSupportedException("Services cannot be configured during LoggerProvider construction.");

configure(services);

return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ public static LoggerProviderBuilder AddProcessor(
/// <returns><see cref="LoggerProvider"/>.</returns>
public static LoggerProvider Build(this LoggerProviderBuilder loggerProviderBuilder)
{
if (loggerProviderBuilder is LoggerProviderServiceCollectionBuilder loggerProviderServiceCollectionBuilder)
if (loggerProviderBuilder is LoggerProviderBuilderBase loggerProviderBuilderBase)
{
return loggerProviderServiceCollectionBuilder.Build();
return loggerProviderBuilderBase.Build();
}

return new LoggerProvider();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ 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(
new LoggerProviderBuilderBase(builder.Services).ConfigureBuilder(
(sp, logging) =>
{
var options = sp.GetRequiredService<IOptionsMonitor<OpenTelemetryLoggerOptions>>().CurrentValue;
Expand Down
2 changes: 1 addition & 1 deletion src/OpenTelemetry/Sdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public static TracerProviderBuilder CreateTracerProviderBuilder()
/// to build a <see cref="LoggerProvider"/>.</returns>
internal static LoggerProviderBuilder CreateLoggerProviderBuilder()
{
return new LoggerProviderServiceCollectionBuilder();
return new LoggerProviderBuilderBase();
}
}
}
Loading