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;
+ }
+ }
}