Description
Component
OpenTelemetry.Instrumentation.AspNetCore
OpenTelemetry.Instrumentation.Http
Package Version
Package Name | Version |
---|---|
OpenTelemetry | 1.9.0 |
OpenTelemetry.Instrumentation.AspNetCore | 1.9.0 |
OpenTelemetry.Instrumentation.Http | 1.9.0 |
Runtime Version
net8.0
Description
It should be possible to set the Activity.DisplayName
in application code to provide a more useful span name than the default auto-instrumented value. For both OpenTelemetry.Instrumentation.AspNetCore
and OpenTelemetry.Instrumentation.Http
any value stored in .DisplayName
before the Activity is stopped is subsequently overwritten by the instrumentation library.
Steps to Reproduce
Here's a test that should pass IMO:
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenTelemetry.Trace;
using Xunit;
public class DisplayNameOverwrittenTest
{
[Fact]
public async Task ActivityDisplayName_CanBeSetInHandler()
{
var recordedActivities = new List<Activity>();
var builder = new HostBuilder()
.ConfigureWebHost(webBuilder =>
{
webBuilder
.UseTestServer()
.ConfigureServices(services =>
{
services.AddOpenTelemetry()
.WithTracing(builder =>
{
builder.AddAspNetCoreInstrumentation();
builder.AddInMemoryExporter(recordedActivities);
});
services.AddRouting();
})
.Configure(app =>
{
app.UseRouting();
app.UseEndpoints(SetupEndpoints);
});
});
void SetupEndpoints(IEndpointRouteBuilder routeBuilder)
{
routeBuilder.Map("{**path}",
context =>
{
// Here I'm setting the Activity.DisplayName to something more useful for this specific route
var activity = context.Features.Get<IHttpActivityFeature>()?.Activity;
if (activity?.IsAllDataRequested == true)
{
var request = context.Request;
activity.DisplayName = $"{request.Method.ToUpperInvariant()} {request.Path}";
}
context.Response.StatusCode = 200;
return Task.CompletedTask;
});
}
using var host = await builder.StartAsync();
using var client = host.GetTestClient();
var response = await client.GetAsync("/foo/bar");
var requestActivity = Assert.Single(recordedActivities);
Assert.Equal("GET /foo/bar", requestActivity.DisplayName);
}
}
Expected Result
Passing test
Actual Result
The test fails because Activity.DisplayName
is overwritten when the Activity is stopped.
DisplayNameOverwrittenTest.ActivityDisplayName_CanBeSetInHandler
Source: DisplayNameOverwrittenTest.cs line 22
Duration: 87 ms
Message:
Assert.Equal() Failure: Strings differ
↓ (pos 4)
Expected: "GET /foo/bar"
Actual: "GET {**path}"
↑ (pos 4)
Additional Context
A workaround is to store the .DisplayName
value you want in Activity.SetCustomProperty()
, then add a processor that reads that value and if present sets the Activity.DisplayName
after it has stopped.
The same logic is present in OpenTelemetry.Instrumentation.Http. I haven't checked the other instrumentation libraries, but I wouldn't be surprised in this pattern is pervasive.