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

[di] Expose a detached TracerProviderBuilder extension on IServiceCollection which may modify services. #4508

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static OpenTelemetry.Trace.OpenTelemetryDependencyInjectionTracingServiceCollectionExtensions.ConfigureOpenTelemetryTracerProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action<OpenTelemetry.Trace.TracerProviderBuilder!>! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
static OpenTelemetry.Trace.OpenTelemetryDependencyInjectionTracingServiceCollectionExtensions.ConfigureOpenTelemetryTracerProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action<OpenTelemetry.Trace.TracerProviderBuilder!>! configure) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
`TracerProvider`.
([#4468](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4468))

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

## 1.5.0-alpha.2

Released 2023-Mar-31
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,44 +26,76 @@ public static class OpenTelemetryDependencyInjectionTracingServiceCollectionExte
{
/// <summary>
/// Registers an action used to configure the OpenTelemetry <see
/// cref="TracerProviderBuilder"/> used to create the <see
/// cref="TracerProvider"/> for the <see cref="IServiceCollection"/> being
/// configured.
/// cref="TracerProviderBuilder"/>.
Copy link
Member

Choose a reason for hiding this comment

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

Good improvement! Was confusing before "used to configure ... used to create the ... for the ... being configured" 😆

/// </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="TracerProvider"/> will not be created automatically
/// using this method. To begin collecting metrics use the
/// <item>A <see cref="TracerProvider"/> will NOT be created automatically
/// using this method. To begin collecting traces 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="TracerProviderBuilder"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls
/// can be chained.</returns>
public static IServiceCollection ConfigureOpenTelemetryTracerProvider(
this IServiceCollection services,
Action<IServiceProvider, TracerProviderBuilder> configure)
Action<TracerProviderBuilder> configure)
{
RegisterBuildAction(services, configure);
Guard.ThrowIfNull(services);
Guard.ThrowIfNull(configure);

configure(new TracerProviderServiceCollectionBuilder(services));

return services;
}

private static void RegisterBuildAction(IServiceCollection services, Action<IServiceProvider, TracerProviderBuilder> configure)
/// <summary>
/// Registers an action used to configure the OpenTelemetry <see
/// cref="TracerProviderBuilder"/> 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="TracerProvider"/> will NOT be created automatically
/// using this method. To begin collecting traces 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="TracerProviderBuilder"/> once the <see
/// cref="IServiceProvider"/> has been created. Many helper extensions
/// register services and may throw if invoked inside the configuration
/// delegate.</item>
/// </list>
/// </remarks>
/// <param name="services"><see cref="IServiceCollection" />.</param>
/// <param name="configure">Callback action to configure the <see
/// cref="TracerProviderBuilder"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls
/// can be chained.</returns>
public static IServiceCollection ConfigureOpenTelemetryTracerProvider(
Copy link
Member

Choose a reason for hiding this comment

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

Many helper extensions register services and may throw if invoked inside the configuration --> This part is bit unclear as end users may not know which extensions register services versus which ones do not.

Could we add some guidance here for end users? some message saying - if you don't need access to ServiceProvider then use the other overload.

this IServiceCollection services,
Action<IServiceProvider, TracerProviderBuilder> configure)
{
Guard.ThrowIfNull(services);
Guard.ThrowIfNull(configure);

services.AddSingleton<IConfigureTracerProviderBuilder>(
new ConfigureTracerProviderBuilderCallbackWrapper(configure));

return services;
}

private sealed class ConfigureTracerProviderBuilderCallbackWrapper : IConfigureTracerProviderBuilder
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// <copyright file="TracerProviderServiceCollectionBuilder.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.Trace;

internal sealed class TracerProviderServiceCollectionBuilder : TracerProviderBuilder, ITracerProviderBuilder
{
public TracerProviderServiceCollectionBuilder(IServiceCollection services)
{
services.ConfigureOpenTelemetryTracerProvider((sp, builder) => this.Services = null);

this.Services = services;
}

public IServiceCollection? Services { get; set; }

public TracerProvider? Provider => null;

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

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

return this;
}

/// <inheritdoc />
public override TracerProviderBuilder AddSource(params string[] names)
{
Guard.ThrowIfNull(names);

this.ConfigureBuilderInternal((sp, builder) =>
{
builder.AddSource(names);
});

return this;
}

/// <inheritdoc />
public override TracerProviderBuilder AddLegacySource(string operationName)
{
Guard.ThrowIfNullOrWhitespace(operationName);

this.ConfigureBuilderInternal((sp, builder) =>
{
builder.AddLegacySource(operationName);
});

return this;
}

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

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

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

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

services.ConfigureOpenTelemetryTracerProvider(configure);

return this;
}

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

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

configure(services);

return this;
}
}
71 changes: 11 additions & 60 deletions src/OpenTelemetry/Trace/Builder/TracerProviderBuilderBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace OpenTelemetry.Trace;
public class TracerProviderBuilderBase : TracerProviderBuilder, ITracerProviderBuilder
{
private readonly bool allowBuild;
private IServiceCollection? services;
private readonly TracerProviderServiceCollectionBuilder innerBuilder;

/// <summary>
/// Initializes a new instance of the <see cref="TracerProviderBuilderBase"/> class.
Expand All @@ -43,9 +43,7 @@ public TracerProviderBuilderBase()
.TryAddSingleton<TracerProvider>(
sp => throw new NotSupportedException("Self-contained TracerProvider cannot be accessed using the application IServiceProvider call Build instead."));

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

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

this.allowBuild = true;
}
Expand All @@ -58,9 +56,7 @@ internal TracerProviderBuilderBase(IServiceCollection services)
.AddOpenTelemetryTracerProviderBuilderServices()
.TryAddSingleton<TracerProvider>(sp => new TracerProviderSdk(sp, ownsServiceProvider: false));

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

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

this.allowBuild = false;
}
Expand All @@ -71,49 +67,34 @@ internal TracerProviderBuilderBase(IServiceCollection services)
/// <inheritdoc />
public override TracerProviderBuilder AddInstrumentation<TInstrumentation>(Func<TInstrumentation> instrumentationFactory)
{
Guard.ThrowIfNull(instrumentationFactory);

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

return this;
}

/// <inheritdoc />
public override TracerProviderBuilder AddSource(params string[] names)
{
Guard.ThrowIfNull(names);

this.ConfigureBuilderInternal((sp, builder) =>
{
builder.AddSource(names);
});
this.innerBuilder.AddSource(names);

return this;
}

/// <inheritdoc />
public override TracerProviderBuilder AddLegacySource(string operationName)
{
Guard.ThrowIfNullOrWhitespace(operationName);

this.ConfigureBuilderInternal((sp, builder) =>
{
builder.AddLegacySource(operationName);
});
this.innerBuilder.AddLegacySource(operationName);

return this;
}

/// <inheritdoc />
TracerProviderBuilder ITracerProviderBuilder.ConfigureServices(Action<IServiceCollection> configure)
=> this.ConfigureServicesInternal(configure);
=> this.innerBuilder.ConfigureServices(configure);

/// <inheritdoc />
TracerProviderBuilder IDeferredTracerProviderBuilder.Configure(Action<IServiceProvider, TracerProviderBuilder> configure)
=> this.ConfigureBuilderInternal(configure);
=> this.innerBuilder.ConfigureBuilder(configure);

internal TracerProvider InvokeBuild()
=> this.Build();
Expand All @@ -134,7 +115,7 @@ protected TracerProviderBuilder AddInstrumentation(
Guard.ThrowIfNullOrWhitespace(instrumentationVersion);
Guard.ThrowIfNull(instrumentationFactory);

return this.ConfigureBuilderInternal((sp, builder) =>
return this.innerBuilder.ConfigureBuilder((sp, builder) =>
{
if (builder is TracerProviderBuilderSdk tracerProviderBuilderState)
{
Expand All @@ -157,14 +138,14 @@ protected TracerProvider Build()
throw new NotSupportedException("A TracerProviderBuilder bound to external service cannot be built directly. Access the TracerProvider using the application IServiceProvider instead.");
}

var services = this.services;
var services = this.innerBuilder.Services;

if (services == null)
{
throw new NotSupportedException("TracerProviderBuilder build method cannot be called multiple times.");
}

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

#if DEBUG
bool validateScopes = true;
Expand All @@ -175,34 +156,4 @@ protected TracerProvider Build()

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

private TracerProviderBuilder ConfigureBuilderInternal(Action<IServiceProvider, TracerProviderBuilder> configure)
{
var services = this.services;

if (services == null)
{
throw new NotSupportedException("Builder cannot be configured during TracerProvider construction.");
}

services.ConfigureOpenTelemetryTracerProvider(configure);

return this;
}

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

var services = this.services;

if (services == null)
{
throw new NotSupportedException("Services cannot be configured during TracerProvider construction.");
}

configure(services);

return this;
}
}
Loading