From 3e885c77f201daebf5a4c00109425296d0064b06 Mon Sep 17 00:00:00 2001 From: Dan Nelson <55757989+danelson@users.noreply.github.com> Date: Thu, 5 Oct 2023 13:11:58 -0500 Subject: [PATCH] [hosting] Register OpenTelemetry at the beginning of IServiceCollection (#4883) Co-authored-by: Mikel Blanchard --- .../CHANGELOG.md | 8 ++++ .../OpenTelemetryServicesExtensions.cs | 18 ++++++--- .../README.md | 4 ++ .../OpenTelemetryServicesExtensionsTests.cs | 40 +++++++++++++++++++ 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md index 8dd8ca62f72..0afddcc2d9e 100644 --- a/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md +++ b/src/OpenTelemetry.Extensions.Hosting/CHANGELOG.md @@ -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 diff --git a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryServicesExtensions.cs b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryServicesExtensions.cs index dff14ca1b38..e226dfba69a 100644 --- a/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryServicesExtensions.cs +++ b/src/OpenTelemetry.Extensions.Hosting/OpenTelemetryServicesExtensions.cs @@ -14,7 +14,6 @@ // limitations under the License. // -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"/>. /// /// - /// Note: This is safe to be called multiple times and by library authors. + /// Notes: + /// + /// This is safe to be called multiple times and by library authors. /// Only a single and/or will be created for a given . + /// cref="IServiceCollection"/>. + /// OpenTelemetry SDK services are inserted at the beginning of the + /// and started with the host. For details + /// about the ordering of events and capturing telemetry in + /// s see: . + /// /// /// . /// The supplied for chaining @@ -47,8 +53,10 @@ public static OpenTelemetryBuilder AddOpenTelemetry(this IServiceCollection serv { Guard.ThrowIfNull(services); - services.TryAddEnumerable( - ServiceDescriptor.Singleton()); + if (!services.Any((ServiceDescriptor d) => d.ServiceType == typeof(IHostedService) && d.ImplementationType == typeof(TelemetryHostedService))) + { + services.Insert(0, ServiceDescriptor.Singleton()); + } return new(services); } diff --git a/src/OpenTelemetry.Extensions.Hosting/README.md b/src/OpenTelemetry.Extensions.Hosting/README.md index 969828f62f1..c5d592b4e79 100644 --- a/src/OpenTelemetry.Extensions.Hosting/README.md +++ b/src/OpenTelemetry.Extensions.Hosting/README.md @@ -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/) diff --git a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs index c6a3b0a8108..181dec9e367 100644 --- a/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs +++ b/test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetryServicesExtensionsTests.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using System.Diagnostics; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -450,9 +451,48 @@ public void AddOpenTelemetry_WithLogging_NestedResolutionUsingConfigureTest() Assert.True(innerTestExecuted); } + [Fact] + public async Task AddOpenTelemetry_HostedServiceOrder_DoesNotMatter() + { + var exportedItems = new List(); + + var builder = new HostBuilder().ConfigureServices(services => + { + services.AddHostedService(); + 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; + } + } }