[hosting] Register OpenTelemetry at the beginning of IServiceCollection (#4883)

Co-authored-by: Mikel Blanchard <mblanchard@macrosssoftware.com>
This commit is contained in:
Dan Nelson 2023-10-05 13:11:58 -05:00 committed by GitHub
parent f63e6e5362
commit 3e885c77f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 5 deletions

View File

@ -2,6 +2,14 @@
## Unreleased
* Changed the behavior of the `OpenTelemetryBuilder.AddOpenTelemetry` extension
to INSERT OpenTelemetry services at the beginning of the `IServiceCollection`
in an attempt to provide a better experience for end users capturing telemetry
in hosted services. Note that this does not guarantee that OpenTelemetry
services will be initialized while other hosted services start, so it is
possible to miss telemetry until OpenTelemetry services are fully initialized.
([#4883](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4883))
## 1.6.0
Released 2023-Sep-05

View File

@ -14,7 +14,6 @@
// limitations under the License.
// </copyright>
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using OpenTelemetry;
using OpenTelemetry.Extensions.Hosting.Implementation;
@ -35,10 +34,17 @@ public static class OpenTelemetryServicesExtensions
/// cref="IServiceCollection"/>.
/// </summary>
/// <remarks>
/// Note: This is safe to be called multiple times and by library authors.
/// Notes:
/// <list type="bullet">
/// <item>This is safe to be called multiple times and by library authors.
/// Only a single <see cref="TracerProvider"/> and/or <see
/// cref="MeterProvider"/> will be created for a given <see
/// cref="IServiceCollection"/>.
/// cref="IServiceCollection"/>.</item>
/// <item>OpenTelemetry SDK services are inserted at the beginning of the
/// <see cref="IServiceCollection"/> and started with the host. For details
/// about the ordering of events and capturing telemetry in
/// <see cref="IHostedService" />s see: <see href="https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Extensions.Hosting/README.md#hosted-service-ordering-and-telemetry-capture" />.</item>
/// </list>
/// </remarks>
/// <param name="services"><see cref="IServiceCollection"/>.</param>
/// <returns>The supplied <see cref="OpenTelemetryBuilder"/> for chaining
@ -47,8 +53,10 @@ public static class OpenTelemetryServicesExtensions
{
Guard.ThrowIfNull(services);
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IHostedService, TelemetryHostedService>());
if (!services.Any((ServiceDescriptor d) => d.ServiceType == typeof(IHostedService) && d.ImplementationType == typeof(TelemetryHostedService)))
{
services.Insert(0, ServiceDescriptor.Singleton<IHostedService, TelemetryHostedService>());
}
return new(services);
}

View File

@ -141,6 +141,10 @@ and
[new](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/examples/AspNetCore)
versions of the example application to assist you in your migration.
## Hosted Service Ordering and Telemetry Capture
TBD
## References
* [OpenTelemetry Project](https://opentelemetry.io/)

View File

@ -14,6 +14,7 @@
// limitations under the License.
// </copyright>
using System.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -450,9 +451,48 @@ public class OpenTelemetryServicesExtensionsTests
Assert.True(innerTestExecuted);
}
[Fact]
public async Task AddOpenTelemetry_HostedServiceOrder_DoesNotMatter()
{
var exportedItems = new List<Activity>();
var builder = new HostBuilder().ConfigureServices(services =>
{
services.AddHostedService<TestHostedService>();
services.AddOpenTelemetry()
.WithTracing(builder =>
{
builder.SetSampler(new AlwaysOnSampler());
builder.AddSource(nameof(TestHostedService));
builder.AddInMemoryExporter(exportedItems);
});
});
var host = builder.Build();
await host.StartAsync().ConfigureAwait(false);
await host.StopAsync().ConfigureAwait(false);
host.Dispose();
Assert.Single(exportedItems);
}
private sealed class MySampler : Sampler
{
public override SamplingResult ShouldSample(in SamplingParameters samplingParameters)
=> new(SamplingDecision.RecordAndSample);
}
private sealed class TestHostedService : BackgroundService
{
private readonly ActivitySource activitySource = new ActivitySource(nameof(TestHostedService));
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
using (var activity = this.activitySource.StartActivity("test"))
{
}
return Task.CompletedTask;
}
}
}