Skip to content

feat: Add AlwaysCallTransactionNameProvider option#5159

Open
jamescrosswell wants to merge 5 commits into
mainfrom
tnp-5123
Open

feat: Add AlwaysCallTransactionNameProvider option#5159
jamescrosswell wants to merge 5 commits into
mainfrom
tnp-5123

Conversation

@jamescrosswell
Copy link
Copy Markdown
Collaborator

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.08%. Comparing base (0ca07a7) to head (33c0d69).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5159      +/-   ##
==========================================
- Coverage   74.20%   74.08%   -0.12%     
==========================================
  Files         501      501              
  Lines       18113    18118       +5     
  Branches     3521     3524       +3     
==========================================
- Hits        13440    13422      -18     
- Misses       3811     3837      +26     
+ Partials      862      859       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread AGENTS.md
@jamescrosswell jamescrosswell marked this pull request as ready for review April 23, 2026 08:09
Comment thread src/Sentry.AspNetCore/SentryTracingMiddleware.cs Outdated
Comment thread src/Sentry.AspNetCore/SentryAspNetCoreOptions.cs
Comment thread src/Sentry.AspNetCore/SentryTracingMiddleware.cs
Submodule modules/sentry-cocoa was not changed in this PR, so reset to main's version.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment on lines +184 to 192
if (transaction.Name.Length == 0 || forceCustomName)
{
var method = context.Request.Method.ToUpperInvariant();

// If we've set a TransactionNameProvider, use that here
var customTransactionName = context.TryGetCustomTransactionName();
if (!string.IsNullOrEmpty(customTransactionName))
if (customName.Value is { } customTransactionName)
{
transaction.Name = $"{method} {customTransactionName}";
tracer.NameSource = TransactionNameSource.Custom;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: When AlwaysCallTransactionNameProvider is true, a manually set transaction name is incorrectly overwritten by the name from the provider.
Severity: MEDIUM

Suggested Fix

Modify the condition at line 183 to prevent overwriting the transaction name if it has already been manually changed. The check should incorporate a condition like transaction.Name == initialName before allowing the TransactionNameProvider to overwrite the name when AlwaysCallTransactionNameProvider is true.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/Sentry.AspNetCore/SentryTracingMiddleware.cs#L184-L192

Potential issue: When `_options.AlwaysCallTransactionNameProvider` is set to `true`, the
logic at line 184 will overwrite any manually set transaction name with the name
provided by the `TransactionNameProvider`. The condition `forceCustomName` at line 183
evaluates to true based only on the option being enabled and the provider returning a
value, without checking if the transaction name was already changed from its initial
value during the request. This behavior contradicts the option's documentation, which
implies it should only override the initial route-derived name, not a name that was set
manually mid-request.

Copy link
Copy Markdown
Collaborator Author

@jamescrosswell jamescrosswell May 6, 2026

Choose a reason for hiding this comment

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

This is something that can be controlled via the implementation of the customTransactionName function that the SDK user supplies (they can check if a value exists and return null if they don't want to override this). I think it's more flexible if we leave it like this - otherwise they wouldn't be able to use the customTransactionName to override values.

@Flash0ver thoughts?

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.

Hmm ... how could the user control this?

With the TransactionNameProvider receiving HttpContext as in and returning string? as out, how could the user delegate perform this check without the actual ITransactionTracer?

/// returns <c>null</c> or <see cref="string.Empty"/>, the routed name is preserved (or the URL-path
/// fallback applies when no route was resolved).
/// </remarks>
public bool AlwaysCallTransactionNameProvider { get; set; }
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.

thought: rename

I'm not sure I like the name:

To me, AlwaysCall.. reads more of an implementation detail, in particular the ..Call.. part, rather than the semantic effect.

I'll try some alternatives, let me know what you think:

  • OverrideTransactionName / OverwriteTransactionName
    • doesn't imply the use of the user-provided TransactionNameProvider
  • OverrideTransactionNameWithTransactionNameProvider
    • too long
  • PreferTransactionNameProvider
    • implies the TransactionNameProvider, and also precedence
  • ForceTransactionNameProvider
    • might be too strong, because when the user-delegate returns null, it's not overwritten

I kinda like PreferTransactionNameProvider.
What do you think?
Or should we stick with AlwaysCallTransactionNameProvider?

Comment on lines +182 to +184
var customName = new Lazy<string?>(context.TryGetCustomTransactionName);
var forceCustomName = _options.AlwaysCallTransactionNameProvider && customName.Value is not null;
if (transaction.Name.Length == 0 || forceCustomName)
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.

thought: I find the Lazy here a bit hard to read

And it's also a bit of an overkill, since there's no thread contention.
If I get it right, it's used as a cache to run the user-callback only once.

I wonder if we can rewrite it a bit without the Lazy<> 🤔.
If it would be harder to read without the Lazy<>, then let's use the hint that it is not accessed concurrently:

new Lazy<string?>(context.TryGetCustomTransactionName, LazyThreadSafetyMode.None)

Comment on lines +182 to +184
var customName = new Lazy<string?>(context.TryGetCustomTransactionName);
var forceCustomName = _options.AlwaysCallTransactionNameProvider && customName.Value is not null;
if (transaction.Name.Length == 0 || forceCustomName)
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.

question: string-check ... potential NRE

The "TransactionName"-check changed:

-transaction.Name == string.Empty
+transaction.Name.Length == 0

From a defensive perspective:
Are we sure that transaction.Name here is never null?

Comment on lines +184 to 192
if (transaction.Name.Length == 0 || forceCustomName)
{
var method = context.Request.Method.ToUpperInvariant();

// If we've set a TransactionNameProvider, use that here
var customTransactionName = context.TryGetCustomTransactionName();
if (!string.IsNullOrEmpty(customTransactionName))
if (customName.Value is { } customTransactionName)
{
transaction.Name = $"{method} {customTransactionName}";
tracer.NameSource = TransactionNameSource.Custom;
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.

Hmm ... how could the user control this?

With the TransactionNameProvider receiving HttpContext as in and returning string? as out, how could the user delegate perform this check without the actual ITransactionTracer?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TransactionNameProvider is never called for routed requests

3 participants