Skip to content

Commit

Permalink
[.NET7.0] AspNetCore ActivitySource Migration (#3391)
Browse files Browse the repository at this point in the history
  • Loading branch information
vishweshbankwar authored Jul 28, 2022
1 parent 3a90287 commit 7c88389
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 5 deletions.
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.
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);
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

0 comments on commit 7c88389

Please sign in to comment.