Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[.NET7.0] AspNetCore ActivitySource Migration #3391

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
// </copyright>

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;
Expand Down Expand Up @@ -42,7 +46,7 @@ public static TracerProviderBuilder AddAspNetCoreInstrumentation(
{
return deferredTracerProviderBuilder.Configure((sp, builder) =>
{
AddAspNetCoreInstrumentation(builder, sp.GetOptions<AspNetCoreInstrumentationOptions>(), configureAspNetCoreInstrumentationOptions);
AddAspNetCoreInstrumentation(builder, sp.GetOptions<AspNetCoreInstrumentationOptions>(), configureAspNetCoreInstrumentationOptions, sp);
});
}

Expand All @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to write a unittest which simulates this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

var activitySourceService = serviceProvider?.GetService<ActivitySource>();
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<AspNetCoreInstrumentationOptions> configure = null)
Action<AspNetCoreInstrumentationOptions> configure = null,
IServiceProvider serviceProvider = null)
{
configure?.Invoke(options);
return AddAspNetCoreInstrumentation(
builder,
new AspNetCoreInstrumentation(new HttpInListener(options)));
new AspNetCoreInstrumentation(new HttpInListener(options)),
serviceProvider);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved
var expectedBaggage = Baggage.SetBaggage("key1", "value1").SetBaggage("key2", "value2");
Sdk.SetDefaultTextMapPropagator(new ExtractOnlyPropagator(activityContext, expectedBaggage));

Expand Down Expand Up @@ -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<Activity>();
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();
Expand All @@ -575,8 +616,13 @@ private static void WaitForActivityExport(List<Activity> 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);
}

Expand Down