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

View File

@ -1,10 +1,13 @@
# Extending the OpenTelemetry .NET SDK
Quick links:
* [Building your own exporter](#exporter)
* [Building your own instrumentation library](#instrumentation-library)
* [Building your own processor](#processor)
* [Building your own sampler](#sampler)
* [Building your own resource detector](#resource-detector)
* [Registration extension method guidance for library authors](#registration-extension-method-guidance-for-library-authors)
* [References](#references)
## 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
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
@ -334,6 +339,278 @@ Custom resource detectors can be implemented:
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
* [Exporter