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

Fix: Avoid replacing Transaction Name on ASP.NET Core by null or empty #1215

Merged
merged 24 commits into from
Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f9a2d49
avoid TransactionName as null.
lucas-zimerman Sep 24, 2021
0374f80
Format code
getsentry-bot Sep 24, 2021
142f671
Test refactor.
lucas-zimerman Sep 24, 2021
9eb6f89
merge
lucas-zimerman Sep 24, 2021
8484bb6
Format code
getsentry-bot Sep 24, 2021
8792d47
Fix tests.
lucas-zimerman Sep 24, 2021
fa91647
Merge branch 'fix/null-transaction-name' of https://github.com/getsen…
lucas-zimerman Sep 24, 2021
af685a2
update changelog.
lucas-zimerman Sep 27, 2021
15fdf55
Update CHANGELOG.md
lucas-zimerman Sep 28, 2021
5e34fd9
refactors.
lucas-zimerman Oct 1, 2021
655890b
fix bug with MVC
lucas-zimerman Oct 1, 2021
b417089
Merge branch 'fix/null-transaction-name' of https://github.com/getsen…
lucas-zimerman Oct 1, 2021
9813eb0
Merge branch 'main' into fix/null-transaction-name
lucas-zimerman Oct 1, 2021
544a8b3
Format code
getsentry-bot Oct 1, 2021
efe8d36
Add transaction to MVC Sample.
lucas-zimerman Oct 1, 2021
83f0831
Update CHANGELOG.md
SimonCropp Oct 1, 2021
d45a7df
Merge branch 'main' into fix/null-transaction-name
lucas-zimerman Oct 4, 2021
e33283c
Merge branch 'main' into fix/null-transaction-name
lucas-zimerman Oct 7, 2021
f6a9543
Merge branch 'main' into fix/null-transaction-name
lucas-zimerman Oct 8, 2021
d91a42e
Merge branch 'main' into fix/null-transaction-name
lucas-zimerman Oct 12, 2021
7cf3073
undo bc.
lucas-zimerman Oct 12, 2021
7d5c623
set transaction name if informed.
lucas-zimerman Oct 13, 2021
478f924
Format code
getsentry-bot Oct 13, 2021
713af04
Merge branch 'main' into fix/null-transaction-name
lucas-zimerman Oct 13, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Fixes

- Avoid replacing Transaction Name on ASP.NET Core by null or empty ([#1215](https://github.com/getsentry/sentry-dotnet/pull/1215))
- Ignore DiagnosticSource Integration if no Sampling available ([#1238](https://github.com/getsentry/sentry-dotnet/pull/1238))

### Features
Expand Down
2 changes: 2 additions & 0 deletions samples/Sentry.Samples.AspNetCore.Mvc/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public static IWebHost BuildWebHost(string[] args) =>

options.MaxBreadcrumbs = 200;

options.TracesSampleRate = 1.0;

// Set a proxy for outgoing HTTP connections
options.HttpProxy = null; // new WebProxy("https://localhost:3128");

Expand Down
2 changes: 2 additions & 0 deletions samples/Sentry.Samples.AspNetCore.Mvc/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)

app.UseStaticFiles();

app.UseSentryTracing();

app.UseMvc(routes =>
{
routes.MapRoute(
Expand Down
9 changes: 8 additions & 1 deletion src/Sentry.AspNetCore/ScopeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,14 @@ public static void Populate(this Scope scope, HttpContext context, SentryAspNetC
scope.SetTag("route.area", area);
}

scope.TransactionName = context.TryGetTransactionName();
// Transaction Name may only be available afterward the creation of the Transaction.
// In this case, the event will update the transaction name if captured during the
// pipeline execution, allowing it to match the correct transaction name as the current
// active transaction.
if (string.IsNullOrEmpty(scope.TransactionName))
{
scope.TransactionName = context.TryGetTransactionName();
}
}
catch (Exception e)
{
Expand Down
45 changes: 29 additions & 16 deletions src/Sentry.AspNetCore/SentryTracingMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Sentry.AspNetCore
/// </summary>
internal class SentryTracingMiddleware
{
private const string UnknownRouteTransactionName = "Unknown Route";
internal const string UnknownRouteTransactionName = "Unknown Route";
private const string OperationName = "http.server";

private readonly RequestDelegate _next;
Expand Down Expand Up @@ -68,12 +68,11 @@ public SentryTracingMiddleware(
// again, to account for the other middlewares that may have
// ran after ours.
var transactionName =
context.TryGetTransactionName() ??
UnknownRouteTransactionName;
context.TryGetTransactionName();

var transactionContext = traceHeader is not null
? new TransactionContext(transactionName, OperationName, traceHeader)
: new TransactionContext(transactionName, OperationName);
? new TransactionContext(transactionName ?? string.Empty, OperationName, traceHeader)
: new TransactionContext(transactionName ?? string.Empty, OperationName);

var customSamplingContext = new Dictionary<string, object?>(3, StringComparer.Ordinal)
{
Expand Down Expand Up @@ -112,13 +111,13 @@ public async Task InvokeAsync(HttpContext context)
}

var transaction = TryStartTransaction(context);
var initialName = transaction?.Name;

// Expose the transaction on the scope so that the user
// can retrieve it and start child spans off of it.
hub.ConfigureScope(scope =>
{
scope.Transaction = transaction;
scope.OnEvaluating += (_, _) => scope.Populate(context, _options);
});

Exception? exception = null;
Expand All @@ -134,22 +133,36 @@ public async Task InvokeAsync(HttpContext context)
{
if (transaction is not null)
{
// The routing middleware may have ran after ours, so
// try to get the transaction name again.
if (context.TryGetTransactionName() is { } transactionName)
// The Transaction name was altered during the pipeline execution,
// That could be done by user interference or by some Event Capture
// That triggers ScopeExtensions.Populate.
if (transaction.Name != initialName)
{
if (!string.Equals(transaction.Name, transactionName, StringComparison.Ordinal))
{
_options.LogDebug(
"Changed transaction name from '{0}' to '{1}' after request pipeline executed.",
transaction.Name,
transactionName);
}
_options.LogDebug(
"transaction name set from '{0}' to '{1}' during request pipeline execution.",
initialName,
transaction.Name);
}
// try to get the transaction name.
else if (context.TryGetTransactionName() is { } transactionName &&
!string.IsNullOrEmpty(transactionName))
{
_options.LogDebug(
"Changed transaction '{0}', name set to '{1}' after request pipeline executed.",
transaction.SpanId,
transactionName);

transaction.Name = transactionName;
}

var status = SpanStatusConverter.FromHttpStatusCode(context.Response.StatusCode);

// If no Name was found for Transaction, fallback to UnknownRoute name.
if (transaction.Name == string.Empty)
{
transaction.Name = UnknownRouteTransactionName;
}

if (exception is null)
{
transaction.Finish(status);
Expand Down
19 changes: 7 additions & 12 deletions src/Sentry/TransactionTracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,19 +222,14 @@ public ISpan StartChild(string operation) =>
/// <inheritdoc />
public void Finish()
{
try
{
Status ??= SpanStatus.UnknownError;
EndTimestamp = DateTimeOffset.UtcNow;
Status ??= SpanStatus.UnknownError;
EndTimestamp = DateTimeOffset.UtcNow;

// Client decides whether to discard this transaction based on sampling
_hub.CaptureTransaction(new Transaction(this));
}
finally
{
// Clear the transaction from the scope
_hub.ConfigureScope(scope => scope.ResetTransaction(this));
}
// Clear the transaction from the scope
_hub.ConfigureScope(scope => scope.ResetTransaction(this));

// Client decides whether to discard this transaction based on sampling
_hub.CaptureTransaction(new Transaction(this));
}

/// <inheritdoc />
Expand Down
104 changes: 104 additions & 0 deletions test/Sentry.AspNetCore.Tests/ScopeExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,62 @@ public ScopeExtensionsTests()
_ = _httpContext.Request.Returns(_httpRequest);
}

private class Fixture
{
public const string ControllerName = "Ctrl";
public const string ActionName = "Actn";

public readonly Scope Scope = new(new SentryOptions());
public HttpContext HttpContext { get; } = Substitute.For<HttpContext>();

public Fixture GetSut(bool addTransaction = true)
{
if (addTransaction)
{
Scope.Transaction = Substitute.For<ITransaction>();
}

var routeFeature = new RoutingFeature
{
RouteData = new RouteData
{
Values =
{
{"controller", ControllerName},
{"action", ActionName}
}
}
};
var features = new FeatureCollection();
features.Set<IRoutingFeature>(routeFeature);
HttpContext.Features.Returns(features);
HttpContext.Request.Method.Returns("GET");
return this;
}

public Fixture GetSutWithEmptyRoute(bool addTransaction = true)
{
if (addTransaction)
{
Scope.Transaction = Substitute.For<ITransaction>();
}
var routeFeature = new RoutingFeature
{
RouteData = new RouteData
{
Values = { { "", null } }
}
};
var features = new FeatureCollection();
features.Set<IRoutingFeature>(routeFeature);
HttpContext.Features.Returns(features);
HttpContext.Request.Method.Returns("GET");
return this;
}
}

private readonly Fixture _fixture = new();

[Fact]
public void Populate_Request_Method_SetToScope()
{
Expand Down Expand Up @@ -268,6 +324,7 @@ public void Populate_PayloadExtractors_DoesNotConsiderInvalidResponse(object exp
[Fact]
public void Populate_RouteData_SetToScope()
{
// Arrange
const string controller = "Ctrl";
const string action = "Actn";
var routeFeature = new RoutingFeature()
Expand All @@ -279,8 +336,10 @@ public void Populate_RouteData_SetToScope()
_ = _httpContext.Features.Returns(features);
_ = _httpContext.Request.Method.Returns("GET");

// Act
_sut.Populate(_httpContext, SentryAspNetCoreOptions);

// Assert
Assert.Equal($"GET {controller}.{action}", _sut.TransactionName);
}

Expand Down Expand Up @@ -364,5 +423,50 @@ public void Populate_TraceIdentifier_WhenRequestIdDoesNotMatch_SetAsTag()

Assert.Equal(expected, _sut.Tags[nameof(HttpContext.TraceIdentifier)]);
}

[Fact]
public void Populate_TransactionAndTransactionNameIsNull_TransactionNameReplaced()
{
// Arrange
var sut = _fixture.GetSut(addTransaction: false);
var scope = sut.Scope;
var expectedTransactionName = $"GET {Fixture.ControllerName}.{Fixture.ActionName}";

// Act
scope.Populate(_fixture.HttpContext, SentryAspNetCoreOptions);

// Assert
Assert.Equal(expectedTransactionName, scope.TransactionName);
}

[Fact]
public void Populate_TransactionIsNullAndRouteNotFound_TransactionNameAsNull()
{
// Arrange
var sut = _fixture.GetSutWithEmptyRoute(addTransaction: false);
var scope = sut.Scope;

// Act
scope.Populate(_fixture.HttpContext, SentryAspNetCoreOptions);

// Assert
Assert.Null(scope.TransactionName);
}

[Fact]
public void Populate_TransactionNameSet_TransactionNameSkipped()
{
// Arrange
var sut = _fixture.GetSut();
var scope = sut.Scope;
var expectedRoute = "MyRoute";
scope.Transaction.Name = expectedRoute;

// Act
scope.Populate(_fixture.HttpContext, SentryAspNetCoreOptions);

// Assert
Assert.Equal(expectedRoute, scope.TransactionName);
}
}
}