[Repo] TracerProviderBuilder dependency injection docs updates (#3869)

* Added content for the "Guidance for library authors" section.

* Code review.

* Spellcheck!

* Code review.

* Code review.

* Code review.

* Bug fix.

* Lint.

* Tweaks.

Co-authored-by: Cijo Thomas <cijo.thomas@gmail.com>
Co-authored-by: Utkarsh Umesan Pillai <utpilla@microsoft.com>
This commit is contained in:
Mikel Blanchard 2022-11-10 09:44:29 -08:00 committed by GitHub
parent 82941d6c79
commit 165b6f4ca4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 281 additions and 11 deletions

View File

@ -378,17 +378,15 @@ For the below examples imagine an exporter with this constructor:
```csharp ```csharp
public class MyCustomExporter : BaseExporter<Activity> public class MyCustomExporter : BaseExporter<Activity>
{ {
public MyCustomExporter( public MyCustomExporter(MyCustomService myCustomService)
ILogger<MyCustomExporter> logger,
MyCustomService myCustomService)
{ {
// Implementation not important // Implementation not important
} }
} }
``` ```
We want to inject the `ILogger<MyCustomExporter>` and `MyCustomService` We want to inject `MyCustomService` dependency into our `MyCustomExporter`
dependencies into our `MyCustomExporter` instance. instance.
#### Using Sdk.CreateTracerProviderBuilder() #### Using Sdk.CreateTracerProviderBuilder()
@ -399,7 +397,6 @@ To register `MyCustomExporter` and `MyCustomService` we can use the
using var tracerProvider = Sdk.CreateTracerProviderBuilder() using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.ConfigureServices(services => .ConfigureServices(services =>
{ {
services.AddLogging();
services.AddSingleton<MyCustomService>(); services.AddSingleton<MyCustomService>();
}) })
.AddExporter<MyCustomExporter>(ExportProcessorType.Batch) .AddExporter<MyCustomExporter>(ExportProcessorType.Batch)
@ -486,10 +483,6 @@ shutdown.
`IServiceCollection` during `ConfigureBuilder` because the `IServiceProvider` `IServiceCollection` during `ConfigureBuilder` because the `IServiceProvider`
has already been created. has already been created.
### Guidance for library authors
// TODO: Add details here
## Configuration files and environment variables ## Configuration files and environment variables
// TODO: Add details here // TODO: Add details here

View File

@ -1,10 +1,13 @@
# Extending the OpenTelemetry .NET SDK # Extending the OpenTelemetry .NET SDK
Quick links:
* [Building your own exporter](#exporter) * [Building your own exporter](#exporter)
* [Building your own instrumentation library](#instrumentation-library) * [Building your own instrumentation library](#instrumentation-library)
* [Building your own processor](#processor) * [Building your own processor](#processor)
* [Building your own sampler](#sampler) * [Building your own sampler](#sampler)
* [Building your own resource detector](#resource-detector) * [Building your own resource detector](#resource-detector)
* [Registration extension method guidance for library authors](#registration-extension-method-guidance-for-library-authors)
* [References](#references) * [References](#references)
## Exporter ## Exporter
@ -62,7 +65,9 @@ A demo exporter which simply writes activity name to the console is shown
Apart from the exporter itself, you should also provide extension methods as Apart from the exporter itself, you should also provide extension methods as
shown [here](./MyExporterExtensions.cs). This allows users to add the Exporter shown [here](./MyExporterExtensions.cs). This allows users to add the Exporter
to the `TracerProvider` as shown in the example [here](./Program.cs). to the `TracerProvider` as shown in the example [here](./Program.cs). See
[here](#registration-extension-method-guidance-for-library-authors) for more
detailed extension method guidance.
### Exporting Activity Status ### Exporting Activity Status
@ -334,6 +339,278 @@ Custom resource detectors can be implemented:
A demo ResourceDetector is shown [here](./MyResourceDetector.cs). A demo ResourceDetector is shown [here](./MyResourceDetector.cs).
## Registration extension method guidance for library authors
**Note** This information applies to the OpenTelemetry SDK version 1.4.0 and
newer only.
Library authors are encouraged to provide extension methods users may call to
register custom OpenTelemetry components into their `TracerProvider`s. These
extension methods can target either the `TracerProviderBuilder` or the
`IServiceCollection` classes. Both of these patterns are described below.
When providing registration extensions:
* **DO** support the [.NET Options
pattern](https://learn.microsoft.com/dotnet/core/extensions/options) and
**DO** support [named
options](https://learn.microsoft.com/dotnet/core/extensions/options#named-options-support-using-iconfigurenamedoptions).
The Options pattern allows users to bind
[configuration](https://learn.microsoft.com/dotnet/core/extensions/configuration)
to options classes and provides extension points for working with instances as
they are created. Multiple providers may exist in the same application for a
single configuration and multiple components (for example exporters) may exist
in the same provider. Named options help users target configuration to
specific components.
* Use the
[Configure](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.optionsservicecollectionextensions.configure#microsoft-extensions-dependencyinjection-optionsservicecollectionextensions-configure-1(microsoft-extensions-dependencyinjection-iservicecollection-system-string-system-action((-0))))
extension to register configuration callbacks for a given name.
* Use the
[IOptionsMonitor&lt;T&gt;.Get](https://learn.microsoft.com/dotnet/api/microsoft.extensions.options.ioptionsmonitor-1.get)
method to access options class instances by name.
* **DO** throw exceptions for issues that prevent the component being registered
from starting. The OpenTelemetry SDK is allowed to crash if it cannot be
started. It **MUST NOT** crash once running.
### TracerProviderBuilder extension methods
When registering pipeline components (for example samplers, exporters, or
resource detectors) it is recommended to use the `TracerProviderBuilder` as the
target type for registration extension methods. These extensions will be highly
discoverable for users interacting with the `TracerProviderBuilder` in their IDE
of choice.
The following example shows how to register a custom exporter with named options
support using a `TracerProviderBuilder` extension.
```csharp
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using MyLibrary;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
namespace OpenTelemetry.Trace
{
public static class MyLibraryTracerProviderBuilderRegistrationExtensions
{
public static TracerProviderBuilder AddMyLibraryExporter(
this TracerProviderBuilder builder,
string? name = null,
Action<MyExporterOptions>? configureExporterOptions = null,
Action<BatchExportActivityProcessorOptions>? configureBatchProcessorOptions = null)
{
ArgumentNullException.ThrowIfNull(builder);
// Support named options.
name ??= Options.DefaultName;
builder.ConfigureServices(services =>
{
if (configureExporterOptions != null)
{
// Support configuration through Options API.
services.Configure(name, configureExporterOptions);
}
if (configureBatchProcessorOptions != null)
{
// Support configuration through Options API.
services.Configure<ExportActivityProcessorOptions>(
name,
o => configureBatchProcessorOptions(o.BatchExportProcessorOptions));
}
// Register custom service as a singleton.
services.TryAddSingleton<MyCustomService>();
});
builder.ConfigureBuilder((sp, builder) =>
{
// Retrieve MyExporterOptions instance using name.
var options = sp.GetRequiredService<IOptionsMonitor<MyExporterOptions>>().Get(name);
// Retrieve MyCustomService singleton.
var myCustomService = sp.GetRequiredService<MyCustomService>();
// Registers MyCustomExporter with a batch processor.
builder.AddExporter(
ExportProcessorType.Batch,
new MyCustomExporter(options, myCustomService),
name,
configure: null);
});
// Return builder for call chaining.
return builder;
}
}
}
namespace MyLibrary
{
// Options class can be bound to IConfiguration or configured by code.
public class MyExporterOptions
{
public Uri? IngestionUri { get; set; }
}
internal sealed class MyCustomExporter : BaseExporter<Activity>
{
public MyCustomExporter(
MyExporterOptions options,
MyCustomService myCustomService)
{
// Implementation not shown.
}
public override ExportResult Export(in Batch<Activity> batch)
{
// Implementation not shown.
return ExportResult.Success;
}
}
internal sealed class MyCustomService
{
// Implementation not shown.
}
}
```
When providing `TracerProviderBuilder` registration extensions:
* **DO** Use the `OpenTelemetry.Trace` namespace for `TracerProviderBuilder`
registration extensions to help with discoverability.
* **DO** Return the `TracerProviderBuilder` passed in to support call chaining
of registration methods.
* **DO** Use the `TracerProviderBuilder.ConfigureServices` extension method to
register dependent services.
* **DO** Use the `TracerProviderBuilder.ConfigureBuilder` extension method to
peform configuration once the final `IServiceProvider` is available.
### IServiceCollection extension methods
When registering instrumentation or listening to telemetry in a library
providing other features it is recommended to use the `IServiceCollection` as
the target type for registration extension methods.
The following example shows how a library might enable tracing and metric
support using an `IServiceCollection` extension by calling
`ConfigureOpenTelemetryTracing`.
```csharp
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using MyLibrary;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
namespace Microsoft.Extensions.DependencyInjection
{
public static class MyLibraryServiceCollectionRegistrationExtensions
{
public static IServiceCollection AddMyLibrary(
this IServiceCollection services,
string? name = null,
Action<MyLibraryOptions>? configure = null)
{
ArgumentNullException.ThrowIfNull(services);
// Register library services.
services.TryAddSingleton<IMyLibraryService, MyLibraryService>();
// Support named options.
name ??= Options.Options.DefaultName;
if (configure != null)
{
// Support configuration through Options API.
services.Configure(name, configure);
}
// Configure OpenTelemetry tracing.
services.ConfigureOpenTelemetryTracing(builder => builder.ConfigureBuilder((sp, builder) =>
{
var options = sp.GetRequiredService<IOptionsMonitor<MyLibraryOptions>>().Get(name);
if (options.EnableTracing)
{
builder.AddSource("MyLibrary");
}
}));
// Configure OpenTelemetry metrics.
services.ConfigureOpenTelemetryMetrics(builder => builder.ConfigureBuilder((sp, builder) =>
{
var options = sp.GetRequiredService<IOptionsMonitor<MyLibraryOptions>>().Get(name);
if (options.EnableMetrics)
{
builder.AddMeter("MyLibrary");
}
}));
return services;
}
}
}
namespace MyLibrary
{
// Options class can be bound to IConfiguration or configured by code.
public class MyLibraryOptions
{
public bool EnableTracing { get; set; }
public bool EnableMetrics { get; set; }
}
internal sealed class MyLibraryService : IMyLibraryService
{
// Implementation not shown.
}
public interface IMyLibraryService
{
// Implementation not shown.
}
}
```
The benefit to using the `IServiceCollection` style is users only need to call a
single `AddMyLibrary` extension to configure the library itself and optionally
turn on OpenTelemetry integration for multiple signals (tracing & metrics in
this case).
**Note** `ConfigureOpenTelemetryTracing` does not automatically start
OpenTelemetry. The host is responsible for either calling
`AddOpenTelemetryTracing` in the
[OpenTelemetry.Extensions.Hosting](../../../src/OpenTelemetry.Extensions.Hosting/README.md)
package, calling `Build` when using the `Sdk.CreateTracerProviderBuilder`
method, or by accessing the `TracerProvider` from the `IServiceCollection` where
`ConfigureOpenTelemetryTracing` was performed.
When providing `IServiceCollection` registration extensions:
* **DO** Use the `Microsoft.Extensions.DependencyInjection` namespace for
`IServiceCollection` registration extensions to help with discoverability.
* **DO** Return the `IServiceCollection` passed in to support call chaining of
registration methods.
* **DO** Use the `IServiceCollection` directly to register dependent services.
* **DO** Use the `TracerProviderBuilder.ConfigureBuilder` extension method to
peform configuration once the final `IServiceProvider` is available.
## References ## References
* [Exporter * [Exporter