Sitelet https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3338
Skip to content

[Instrumentation.AspNetCore] Fix http.route for HTTP server spans - #3338

Closed
RassK wants to merge 16 commits into
open-telemetry:mainfrom
RassK:aspnetcore-route-fix
Closed

RassK wants to merge 16 commits into
open-telemetry:mainfrom
RassK:aspnetcore-route-fix

Conversation

@RassK

@RassK RassK commented Oct 23, 2025 •

Copy link
Copy Markdown
Contributor

Follow up to #3160

Changes

Normalizes route template according to spec.

  • Replaces controller, action, area tokens.
  • Removes constraints.
  • Removes unused optional parameters

Merge requirement checklist

  • CONTRIBUTING guidelines followed (license requirements, nullable enabled, static analysis, etc.)
  • Unit tests added/updated
  • Appropriate CHANGELOG.md files updated for non-trivial changes

@github-actions

Copy link
Copy Markdown
Contributor

This PR was marked stale due to lack of activity. It will be closed in 7 days.

@github-actions github-actions Bot added Stale and removed Stale labels Oct 31, 2025
@RassK
RassK force-pushed the aspnetcore-route-fix branch from 54c0044 to d3ab87a Compare November 4, 2025 12:45
@codecov

codecov Bot commented Nov 4, 2025 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.04878% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.78%. Comparing base (a5f3fc0) to head (5bfe512).
⚠️ Report is 470 commits behind head on main.

Files with missing lines Patch % Lines
....AspNetCore/Implementation/RouteAttributeHelper.cs 84.84% 5 Missing ⚠️
...AspNetCore/Implementation/HttpInMetricsListener.cs 0.00% 4 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3338      +/-   ##
==========================================
+ Coverage   68.61%   68.78%   +0.17%     
==========================================
  Files         431      422       -9     
  Lines       16429    16410      -19     
==========================================
+ Hits        11272    11287      +15     
+ Misses       5157     5123      -34     
Flag Coverage Δ
unittests-Instrumentation.AspNet 78.14% <ø> (ø)
unittests-Instrumentation.AspNetCore 72.82% <78.04%> (+1.13%) ⬆️
unittests-Instrumentation.Cassandra ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...tation.AspNetCore/Implementation/HttpInListener.cs 73.28% <100.00%> (-0.19%) ⬇️
...AspNetCore/Implementation/HttpInMetricsListener.cs 0.00% <0.00%> (ø)
....AspNetCore/Implementation/RouteAttributeHelper.cs 84.84% <84.84%> (ø)

... and 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@RassK

RassK commented Nov 4, 2025

Copy link
Copy Markdown
Contributor Author

I'll recheck http route tests readme and update the changelog. So after that, this change should be ready to go.

@martincostello martincostello mentioned this pull request Nov 4, 2025
3 of 4 tasks
@RassK
RassK marked this pull request as ready for review November 5, 2025 16:37
@RassK
RassK requested a review from a team as a code owner November 5, 2025 16:37
| :broken_heart: | ConventionalRouting | [Non-default action with query string](#metrics__conventionalrouting-non-default-action-with-query-string) |
| :green_heart: | ConventionalRouting | [Not Found (404)](#metrics__conventionalrouting-not-found-404) |
| :green_heart: | ConventionalRouting | [Route template with parameter constraint](#metrics__conventionalrouting-route-template-with-parameter-constraint) |
| :broken_heart: | ConventionalRouting | [Route template with parameter constraint](#metrics__conventionalrouting-route-template-with-parameter-constraint) |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is some green_heart to broken_heart changes across all net targets. Is it intentional?

@RassK RassK Nov 5, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, you can recheck some ideal route changes. It's caused because of the different ideal route (cleaned constraints, excess symbols like '/', removing optionals if not present, etc)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We shouldn't introduce more broken_hearts. If you're making a behavior change that you think is correct then the test harness should be adjusted to produce a green_heart. The main thing these markdown files were meant to highlight is remaining gaps we should aim to get fixed.

For code that we do not control, my hope was that we could push on the ASP.NET team to consider making changes.

Also behavior must remain identical (even if the behavior is not ideal) between the attributes added to metrics and those added to spans. Is this still true with this PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There is no way that hearts will remain the same, when the expectation was also wrong.
New expectations are in line with AspNet package, spec and consistent output.

There is also no way that spans and metrics can have identical behaviour. Since one part is what we control and another is uncontrollable. I don't know what is more ideal, either ASP.NET Core has to adapt faster or SDK apis are extended for packages around API to be able to override the src behaviour.

"currentHttpRoute": null,
"expectedHttpRoute": "/MinimalApiUsingMapGroup/"
"currentMetricRoute": "/MinimalApiUsingMapGroup/",
"expectedHttpRoute": "MinimalApiUsingMapGroup"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why remove preceeding /?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There are 2 ways, either to include / always or not to include. The same route must not produce 2 different versions.
So if there is no extra meaning (and a need), then there is no point to include it. Same in ASP.NET MVC5.

}
}

private static string GetRoutePattern(RoutePattern routePattern, RouteValueDictionary routeValues)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This always runs, right? Should it only run if the route needs to change? i.e. there are area/controller/action/page attributes and a value is present in the requests route values collection.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Correct. It would be a nice optimization. Also connected to #3338 (comment) (so it runs only if the framework provides the values).

"currentHttpRoute": null,
"expectedHttpRoute": "SomePath/{id}/{num:int}"
"currentMetricRoute": "SomePath/{id}/{num:int}",
"expectedHttpRoute": "SomePath/{id}/{num}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The route constraint is being lost

@RassK RassK Nov 25, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is intentional change:

pros:

  • produces similar routes as asp.net mvc5
  • reduces cardinality
  • simplifies the template

negs:

  • no visibility into same routes but different constraints (possibly very rare case)

"currentHttpRoute": null,
"expectedHttpRoute": "/MinimalApi/{id}"
"currentMetricRoute": "/MinimalApi/{id}",
"expectedHttpRoute": "MinimalApi/{id}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What happens if a minimal api route has a parameter called action? Will it be rewritten? There is nothing stopping a minimal API having a parameter with that name and it isn't a static part of the route.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

currently yes, unless there is a framework marker so we can check if it was included by the framework.

@github-actions github-actions Bot removed the Stale label Nov 15, 2025
@github-actions

This comment was marked as outdated.

@github-actions github-actions Bot added the Stale label Nov 25, 2025
@Kielek Kielek removed the Stale label Nov 25, 2025
@RassK

RassK commented Nov 25, 2025

Copy link
Copy Markdown
Contributor Author

My opinion about these topics:

  • He would like to avoid differences in the http.route related parameters between metrics and the traces. The main reason behind this, is the fact that some backends are using it to correlate these signals based on this attribute.

That is already so broken and off the spec that I'm not sure which parts could be trusted for machine processing.
There is practically no cardinality at all for some apps.

  • @JamesNK, is there any chance that you can advice how to fix http.route spans for metrics? Do you see any options to modify these attributes on OTel instrumentation side? If not, can you apply changes directly in the AspNetCore?

Since SDK is exporting them, then additional processor should be available for API users as well as SDK users already have that option.

  • Not directly related, but AspNetCore is creating activities, but without needed attributes, so we here recreating such activities in this package. Again, @JamesNK, do you see any option to fully manage these activities+attributes by the AspNetCore directly?

Same as above.

@github-actions

github-actions Bot commented Dec 3, 2025

Copy link
Copy Markdown
Contributor

This PR was marked stale due to lack of activity. It will be closed in 7 days.

@github-actions github-actions Bot added the Stale label Dec 3, 2025
@JamesNK

JamesNK commented Dec 3, 2025

Copy link
Copy Markdown
Contributor

I’m away. I’ll reply next week

@Kielek Kielek removed the Stale label Dec 3, 2025
@github-actions

Copy link
Copy Markdown
Contributor

This PR was marked stale due to lack of activity. It will be closed in 7 days.

@github-actions github-actions Bot added the Stale label Dec 11, 2025
@martincostello martincostello added keep-open Prevents issues and pull requests being closed as stale and removed Stale labels Dec 11, 2025
@JamesNK

JamesNK commented Dec 23, 2025

Copy link
Copy Markdown
Contributor

I updated OTEL http.route usage in ASP.NET Core to replace static parameters. It's currently added to metrics, and will be used with Activity tags soon.

The PR to created improved routes is here: dotnet/aspnetcore#64854

I think the approach I used (using RequiredValues collection) is better. The rest of the route unchanged except the replaced parameter. And it doesn't rely on hardcoded parameter names, e.g. it won't try to replace a "controller" parameter in a minimal API route.

@RassK RassK closed this Jan 7, 2026
@RassK RassK reopened this Jan 7, 2026
@RassK

RassK commented Jan 7, 2026 •

Copy link
Copy Markdown
Contributor Author

I know the spec is quite weak about framework specifics in the http.route. The goal is only to get a "statistically interesting unique collection" of routes.

I would personally hate to see "unnecessary paddings" like :int, ?, {id?} when id is missing. I'm not sure that they give much value contextually as statistically interesting tokens. ASP.NET Fx (like mvc4) does not do those, so not adding them would unify values further.

src: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.50.0/specification/trace/api.md#span

The span name SHOULD be the most general string that identifies a (statistically) interesting class of Spans, rather than individual Span instances while still being human-readable.

Secondly those tokens should be present then also in the Activity DisplayName, which makes it even more, not making sense.
Also because The span name SHOULD be the most general string would not be true.

Thirdly, those tokens can add cardinality is some cases.

src: https://opentelemetry.io/docs/specs/semconv/http/http-spans/#name
HTTP span names SHOULD be {method} {target} if there is a (low-cardinality) target available. If there is no (low-cardinality) {target} available, HTTP span names SHOULD be {method}.

CC: @JamesNK @alanwest @martincostello @Kielek

@JamesNK

JamesNK commented Jan 14, 2026 •

Copy link
Copy Markdown
Contributor

I would personally hate to see "unnecessary paddings" like :int, ?, {id?} when id is missing. I'm not sure that they give much value contextually as statistically interesting tokens. ASP.NET Fx (like mvc4) does not do those, so not adding them would unify values further.

People will expect http.route to be the route they've definied in their app. I'm ok with modifying conventional routes with constant values because in that case you're defining a route convention that applies to many actions. But rewriting the route that is in an attribute or in MapGet violates the principal of least surprise. People expect http.route == [HttpGet("my-route")] or MapGet("my-route", () => {}).

And things like constraints are important information. This is possible:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/product/{id:alpha}", (string id) => "alpha");
app.MapGet("/product/{id:int}", (string id) => "int");

app.Run();

Stripping the constraint would make the matched route ambigious.

The span name SHOULD be the most general string that identifies a (statistically) interesting class of Spans, rather than individual Span instances while still being human-readable.

Constraints are important information. And optional parameters are part of the route, even if they're not matched.

Even if we wanted to optionally add optional parameters to the route string, it would mean a performance cost because it couldn't be statically determined. The change I made to ASP.NET Core means that the final route template is generated when the endpoint is created. There is no per-request overhead.

@RassK

RassK commented Jan 21, 2026 •

Copy link
Copy Markdown
Contributor Author

This basically means that spec must separate HTTP span name from including http.route. I do agree it's important information (as of http.route) but it also clutters HTTP span name. This way there is no reason to replace "static partials" in the http.route, but HTTP span name for sure requires it.

@martincostello

Copy link
Copy Markdown
Member

@RassK Should we close this PR? It's been open for over 6 months with zero forward progress.

@RassK

RassK commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@RassK Should we close this PR? It's been open for over 6 months with zero forward progress.

Yes, I can update it if there is any progress. So far seems it's stuck in spec and even if there is going to be any progress, then most probably it needs native support to avoid doing performance hitting work in hot paths.

@martincostello martincostello removed the keep-open Prevents issues and pull requests being closed as stale label Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:instrumentation.aspnetcore Things related to OpenTelemetry.Instrumentation.AspNetCore

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants