diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs index 1ff80acc8b2..60081f1b90e 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs @@ -35,6 +35,10 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation internal class HttpInListener : ListenerHandler { internal const string ActivityOperationName = "Microsoft.AspNetCore.Hosting.HttpRequestIn"; +#if NET7_0_OR_GREATER + // https://github.com/dotnet/aspnetcore/blob/8d6554e655b64da75b71e0e20d6db54a3ba8d2fb/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs#L85 + internal static readonly string AspNetCoreActivitySourceName = "Microsoft.AspNetCore"; +#endif internal static readonly AssemblyName AssemblyName = typeof(HttpInListener).Assembly.GetName(); internal static readonly string ActivitySourceName = AssemblyName.Name; internal static readonly Version Version = AssemblyName.Version; @@ -96,8 +100,14 @@ public override void OnStartActivity(Activity activity, object payload) // Create a new activity with its parent set from the extracted context. // This makes the new activity as a "sibling" of the activity created by // Asp.Net Core. +#if NET7_0_OR_GREATER + // For NET7.0 onwards activity is created using ActivitySource so, + // we will use the source of the activity to create the new one. + Activity newOne = activity.Source.CreateActivity(ActivityOperationName, ActivityKind.Server, ctx.ActivityContext); +#else Activity newOne = new Activity(ActivityOperationName); newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags); +#endif newOne.TraceStateString = ctx.ActivityContext.TraceState; newOne.SetTag("IsCreatedByInstrumentation", bool.TrueString); @@ -135,8 +145,10 @@ public override void OnStartActivity(Activity activity, object payload) return; } +#if !NET7_0_OR_GREATER ActivityInstrumentationHelper.SetActivitySourceProperty(activity, ActivitySource); ActivityInstrumentationHelper.SetKindProperty(activity, ActivityKind.Server); +#endif var path = (request.PathBase.HasValue || request.Path.HasValue) ? (request.PathBase + request.Path).ToString() : "/"; activity.DisplayName = path; diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/TracerProviderBuilderExtensions.cs index 8ff58d22c73..2e2e4dfc253 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/TracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/TracerProviderBuilderExtensions.cs @@ -15,6 +15,10 @@ // using System; +#if NET7_0_OR_GREATER +using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; +#endif using OpenTelemetry.Instrumentation.AspNetCore; using OpenTelemetry.Instrumentation.AspNetCore.Implementation; using OpenTelemetry.Internal; @@ -42,7 +46,7 @@ public static TracerProviderBuilder AddAspNetCoreInstrumentation( { return deferredTracerProviderBuilder.Configure((sp, builder) => { - AddAspNetCoreInstrumentation(builder, sp.GetOptions(), configureAspNetCoreInstrumentationOptions); + AddAspNetCoreInstrumentation(builder, sp.GetOptions(), configureAspNetCoreInstrumentationOptions, sp); }); } @@ -51,22 +55,44 @@ public static TracerProviderBuilder AddAspNetCoreInstrumentation( internal static TracerProviderBuilder AddAspNetCoreInstrumentation( this TracerProviderBuilder builder, - AspNetCoreInstrumentation instrumentation) + AspNetCoreInstrumentation instrumentation, + IServiceProvider serviceProvider = null) { + // For .NET7.0 onwards activity will be created using activitySource. + // https://github.com/dotnet/aspnetcore/blob/bf3352f2422bf16fa3ca49021f0e31961ce525eb/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs#L327 + // For .NET6.0 and below, we will continue to use legacy way. +#if NET7_0_OR_GREATER + // TODO: Check with .NET team to see if this can be prevented + // as this allows user to override the ActivitySource. + var activitySourceService = serviceProvider?.GetService(); + if (activitySourceService != null) + { + builder.AddSource(activitySourceService.Name); + } + else + { + // For users not using hosting package? + builder.AddSource(HttpInListener.AspNetCoreActivitySourceName); + } +#else builder.AddSource(HttpInListener.ActivitySourceName); builder.AddLegacySource(HttpInListener.ActivityOperationName); // for the activities created by AspNetCore +#endif + return builder.AddInstrumentation(() => instrumentation); } private static TracerProviderBuilder AddAspNetCoreInstrumentation( TracerProviderBuilder builder, AspNetCoreInstrumentationOptions options, - Action configure = null) + Action configure = null, + IServiceProvider serviceProvider = null) { configure?.Invoke(options); return AddAspNetCoreInstrumentation( builder, - new AspNetCoreInstrumentation(new HttpInListener(options))); + new AspNetCoreInstrumentation(new HttpInListener(options)), + serviceProvider); } } } diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs index 21eaa64a3f3..d10f6b12aaf 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs @@ -348,7 +348,7 @@ public async Task ExtractContextIrrespectiveOfSamplingDecision(SamplingDecision var expectedTraceId = ActivityTraceId.CreateRandom(); var expectedParentSpanId = ActivitySpanId.CreateRandom(); var expectedTraceState = "rojo=1,congo=2"; - var activityContext = new ActivityContext(expectedTraceId, expectedParentSpanId, ActivityTraceFlags.Recorded, expectedTraceState); + var activityContext = new ActivityContext(expectedTraceId, expectedParentSpanId, ActivityTraceFlags.Recorded, expectedTraceState, true); var expectedBaggage = Baggage.SetBaggage("key1", "value1").SetBaggage("key2", "value2"); Sdk.SetDefaultTextMapPropagator(new ExtractOnlyPropagator(activityContext, expectedBaggage)); @@ -553,6 +553,47 @@ void ConfigureTestServices(IServiceCollection services) Assert.Equal(shouldEnrichBeCalled, enrichCalled); } +#if NET7_0_OR_GREATER + [Fact] + public async Task UserRegisteredActivitySourceIsUsedForActivityCreationByAspNetCore() + { + var exportedItems = new List(); + void ConfigureTestServices(IServiceCollection services) + { + services.AddOpenTelemetryTracing(options => + { + options.AddAspNetCoreInstrumentation() + .AddInMemoryExporter(exportedItems); + }); + + // Register ActivitySource here so that it will be used + // by ASP.NET Core to create activities + // https://github.com/dotnet/aspnetcore/blob/0e5cbf447d329a1e7d69932c3decd1c70a00fbba/src/Hosting/Hosting/src/Internal/WebHost.cs#L152 + services.AddSingleton(sp => new ActivitySource("UserRegisteredActivitySource")); + } + + // Arrange + using (var client = this.factory + .WithWebHostBuilder(builder => + builder.ConfigureTestServices(ConfigureTestServices)) + .CreateClient()) + { + // Act + var response = await client.GetAsync("/api/values"); + + // Assert + response.EnsureSuccessStatusCode(); // Status Code 200-299 + + WaitForActivityExport(exportedItems, 1); + } + + Assert.Single(exportedItems); + var activity = exportedItems[0]; + + Assert.Equal("UserRegisteredActivitySource", activity.Source.Name); + } +#endif + public void Dispose() { this.tracerProvider?.Dispose(); @@ -575,8 +616,13 @@ private static void WaitForActivityExport(List exportedItems, int coun private static void ValidateAspNetCoreActivity(Activity activityToValidate, string expectedHttpPath) { Assert.Equal(ActivityKind.Server, activityToValidate.Kind); +#if NET7_0_OR_GREATER + Assert.Equal(HttpInListener.AspNetCoreActivitySourceName, activityToValidate.Source.Name); + Assert.Empty(activityToValidate.Source.Version); +#else Assert.Equal(HttpInListener.ActivitySourceName, activityToValidate.Source.Name); Assert.Equal(HttpInListener.Version.ToString(), activityToValidate.Source.Version); +#endif Assert.Equal(expectedHttpPath, activityToValidate.GetTagValue(SemanticConventions.AttributeHttpTarget) as string); }