Skip to content

Commit

Permalink
[Tracing] Improve dependency injection support in tracing build-up us…
Browse files Browse the repository at this point in the history
…ing SDK (#3533)

* TracerBuilder depedency injection improvements.

* Fixes and API updates.

* Update skipped test.

* Tweak.

* Tweaks.

* Test fix.

* Turn on options in TracerProviderBuilderBase.

* Updated CHANGELOGs.

* Added XML comments to AddOpenTelemetryTracing methods.

* Tweaks.

* Added export helpers and restored removed API from hosting library so that there is no breakage during upgrades.

* Export extension tweaks.

* Tweak.

* Tracer builder improvements.

* Fix hosting multiple configurations test.

* Cleanup.

* Cleanup and fixes.

* Added "UseOpenTelemetry" extensions.

* Cleanup.

* Code review.

* Code review.

* Rename SDK extension "Configure" and restore "Add" in the hosting lib.

* Doc clarification.

* Some test coverage.

* README updates.

* Fix double dispose of TracerProvider when using Sdk.CreateTracerProviderBuilder.

* AddExporter tests.

* More test coverage.

* More test coverage.

* Code review.
  • Loading branch information
CodeBlanch authored Aug 29, 2022
1 parent 8700d5d commit e6d39b8
Show file tree
Hide file tree
Showing 35 changed files with 1,683 additions and 719 deletions.
2 changes: 1 addition & 1 deletion examples/MicroserviceExample/WorkerService/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
services.AddSingleton<MessageReceiver>();
services.AddOpenTelemetryTracing((builder) =>
services.AddOpenTelemetryTracing(builder =>
{
builder
.AddSource(nameof(MessageReceiver))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,5 @@ static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddReader<T>(this Op
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.Build(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.IServiceProvider serviceProvider) -> OpenTelemetry.Metrics.MeterProvider
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.Configure(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Action<System.IServiceProvider, OpenTelemetry.Metrics.MeterProviderBuilder> configure) -> OpenTelemetry.Metrics.MeterProviderBuilder
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.GetServices(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder) -> Microsoft.Extensions.DependencyInjection.IServiceCollection
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddInstrumentation<T>(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddProcessor<T>(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.Build(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.IServiceProvider serviceProvider) -> OpenTelemetry.Trace.TracerProvider
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.Configure(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.Action<System.IServiceProvider, OpenTelemetry.Trace.TracerProviderBuilder> configure) -> OpenTelemetry.Trace.TracerProviderBuilder
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.GetServices(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder) -> Microsoft.Extensions.DependencyInjection.IServiceCollection
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.SetSampler<T>(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder) -> OpenTelemetry.Trace.TracerProviderBuilder
4 changes: 4 additions & 0 deletions src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

* Dependency injection support when configuring
`TracerProvider` has been moved into the SDK.
([#3533](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3533))

## 1.0.0-rc9.6

Released 2022-Aug-18
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,49 +15,56 @@
// </copyright>

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

namespace OpenTelemetry.Extensions.Hosting.Implementation
namespace OpenTelemetry.Extensions.Hosting.Implementation;

internal sealed class TelemetryHostedService : IHostedService
{
internal class TelemetryHostedService : IHostedService
private readonly IServiceProvider serviceProvider;

public TelemetryHostedService(IServiceProvider serviceProvider)
{
private readonly IServiceProvider serviceProvider;
this.serviceProvider = serviceProvider;
}

public TelemetryHostedService(IServiceProvider serviceProvider)
public Task StartAsync(CancellationToken cancellationToken)
{
try
{
this.serviceProvider = serviceProvider;
// The sole purpose of this HostedService is to ensure all
// instrumentations, exporters, etc., are created and started.
Initialize(this.serviceProvider);
}

public Task StartAsync(CancellationToken cancellationToken)
catch (Exception ex)
{
try
{
// The sole purpose of this HostedService is to ensure all
// instrumentations, exporters, etc., are created and started.
var meterProvider = this.serviceProvider.GetService<MeterProvider>();
var tracerProvider = this.serviceProvider.GetService<TracerProvider>();

if (meterProvider == null && tracerProvider == null)
{
throw new InvalidOperationException("Could not resolve either MeterProvider or TracerProvider through application ServiceProvider, OpenTelemetry SDK has not been initialized.");
}
}
catch (Exception ex)
{
HostingExtensionsEventSource.Log.FailedOpenTelemetrySDK(ex);
}

return Task.CompletedTask;
HostingExtensionsEventSource.Log.FailedOpenTelemetrySDK(ex);
}

public Task StopAsync(CancellationToken cancellationToken)
return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

internal static void Initialize(IServiceProvider serviceProvider)
{
Debug.Assert(serviceProvider != null, "serviceProvider was null");

var meterProvider = serviceProvider.GetService<MeterProvider>();
var tracerProvider = serviceProvider.GetService<TracerProvider>();

if (meterProvider == null && tracerProvider == null)
{
return Task.CompletedTask;
throw new InvalidOperationException("Could not resolve either MeterProvider or TracerProvider through application ServiceProvider, OpenTelemetry SDK has not been initialized.");
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- OmniSharp/VS Code requires TargetFrameworks to be in descending order for IntelliSense and analysis. -->
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<Description>Startup extensions to register OpenTelemetry into the applications using Microsoft.Extensions.DependencyInjection and Microsoft.Extensions.Hosting</Description>
<Description>Contains extensions to register and start OpenTelemetry in applications using Microsoft.Extensions.Hosting</Description>
<RootNamespace>OpenTelemetry</RootNamespace>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,38 @@ namespace Microsoft.Extensions.DependencyInjection
public static class OpenTelemetryServicesExtensions
{
/// <summary>
/// Adds OpenTelemetry TracerProvider to the specified <see cref="IServiceCollection" />.
/// Configure OpenTelemetry and register a <see cref="IHostedService"/>
/// to automatically start tracing services in the supplied <see
/// cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
/// <remarks>
/// Note: This is safe to be called multiple times and by library authors.
/// Only a single <see cref="TracerProvider"/> will be created for a given
/// <see cref="IServiceCollection"/>.
/// </remarks>
/// <param name="services"><see cref="IServiceCollection"/>.</param>
/// <returns>Supplied <see cref="IServiceCollection"/> for chaining calls.</returns>
public static IServiceCollection AddOpenTelemetryTracing(this IServiceCollection services)
{
return services.AddOpenTelemetryTracing(builder => { });
}
=> AddOpenTelemetryTracing(services, (b) => { });

/// <summary>
/// Adds OpenTelemetry TracerProvider to the specified <see cref="IServiceCollection" />.
/// Configure OpenTelemetry and register a <see cref="IHostedService"/>
/// to automatically start tracing services in the supplied <see
/// cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <remarks><inheritdoc cref="AddOpenTelemetryTracing(IServiceCollection)" path="/remarks"/></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>
/// <returns>Supplied <see cref="IServiceCollection"/> for chaining calls.</returns>
public static IServiceCollection AddOpenTelemetryTracing(this IServiceCollection services, Action<TracerProviderBuilder> configure)
{
Guard.ThrowIfNull(configure);
Guard.ThrowIfNull(services);

var builder = new TracerProviderBuilderHosting(services);
configure(builder);
return services.AddOpenTelemetryTracing(sp => builder.Build(sp));
services.ConfigureOpenTelemetryTracing(configure);

services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, TelemetryHostedService>());

return services;
}

/// <summary>
Expand Down Expand Up @@ -81,34 +91,6 @@ public static IServiceCollection AddOpenTelemetryMetrics(this IServiceCollection
return services.AddOpenTelemetryMetrics(sp => builder.Build(sp));
}

/// <summary>
/// Adds OpenTelemetry TracerProvider to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="createTracerProvider">A delegate that provides the tracer provider to be registered.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
private static IServiceCollection AddOpenTelemetryTracing(this IServiceCollection services, Func<IServiceProvider, TracerProvider> createTracerProvider)
{
Guard.ThrowIfNull(services);
Guard.ThrowIfNull(createTracerProvider);

// Accessing Sdk class is just to trigger its static ctor,
// which sets default Propagators and default Activity Id format
_ = Sdk.SuppressInstrumentation;

try
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, TelemetryHostedService>());
return services.AddSingleton(s => createTracerProvider(s));
}
catch (Exception ex)
{
HostingExtensionsEventSource.Log.FailedInitialize(ex);
}

return services;
}

/// <summary>
/// Adds OpenTelemetry MeterProvider to the specified <see cref="IServiceCollection" />.
/// </summary>
Expand Down
24 changes: 11 additions & 13 deletions src/OpenTelemetry.Extensions.Hosting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,27 +45,27 @@ Similar methods exist for registering instrumentation (`AddInstrumentation<T>`)
and setting a sampler (`SetSampler<T>`).

You can also access the application `IServiceProvider` directly and accomplish
the same registration using the `Configure` extension like this:
the same registration using the `ConfigureBuilder` extension like this:

```csharp
services.AddSingleton<MyProcessor>();

services.AddOpenTelemetryTracing(hostingBuilder => hostingBuilder
.Configure((sp, builder) => builder
.ConfigureBuilder((sp, builder) => builder
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddProcessor(sp.GetRequiredService<MyProcessor>())));
```

**Note:** `Configure` is called _after_ the `IServiceProvider` has been built
**Note:** `ConfigureBuilder` is called _after_ the `IServiceProvider` has been built
from the application `IServiceCollection` so any services registered in the
`Configure` callback will be ignored.
`ConfigureBuilder` callback will be ignored.

#### Building Extension Methods

Library authors may want to configure the OpenTelemetry `TracerProvider` and
register application services to provide more complex features. This can be
accomplished concisely by using the `TracerProviderBuilder.GetServices`
accomplished concisely by using the `TracerProviderBuilder.ConfigureServices`
extension method inside of a more general `TracerProviderBuilder` configuration
extension like this:

Expand All @@ -74,15 +74,13 @@ public static class MyLibraryExtensions
{
public static TracerProviderBuilder AddMyFeature(this TracerProviderBuilder tracerProviderBuilder)
{
(tracerProviderBuilder.GetServices()
?? throw new NotSupportedException(
"MyFeature requires a hosting TracerProviderBuilder instance."))
.AddHostedService<MyHostedService>()
.AddSingleton<MyService>()
.AddSingleton<MyProcessor>()
.AddSingleton<MySampler>();

return tracerProviderBuilder
.ConfigureServices(services =>
services
.AddHostedService<MyHostedService>()
.AddSingleton<MyService>()
.AddSingleton<MyProcessor>()
.AddSingleton<MySampler>())
.AddProcessor<MyProcessor>()
.SetSampler<MySampler>();
}
Expand Down
Loading

0 comments on commit e6d39b8

Please sign in to comment.