Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

- Release of Azure Functions (Isolated Worker/Out-of-Process) support ([#2686](https://github.com/getsentry/sentry-dotnet/pull/2686))

### Fixes
- Scope is now correctly applied to Transactions when using OpenTelemetry on ASP.NET Core ([#2690](https://github.com/getsentry/sentry-dotnet/pull/2690))

### Dependencies

- Bump CLI from v2.20.7 to v2.21.1 ([#2645](https://github.com/getsentry/sentry-dotnet/pull/2645), [#2647](https://github.com/getsentry/sentry-dotnet/pull/2647))
Expand Down
7 changes: 7 additions & 0 deletions src/Sentry.AspNetCore/SentryMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.Extensions.Options;
using Sentry.AspNetCore.Extensions;
using Sentry.Extensibility;
using Sentry.Internal;
using Sentry.Reflection;

namespace Sentry.AspNetCore;
Expand Down Expand Up @@ -132,6 +133,12 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var originalMethod = context.Request.Method;
await next(context).ConfigureAwait(false);
if (_options.Instrumenter == Instrumenter.OpenTelemetry && Activity.Current is {} activity)
{
// The middleware pipeline finishes up before the Otel Activity.OnEnd callback is invoked so we need
// so save a copy of the scope that can be restored by our SentrySpanProcessor
hub.ConfigureScope(scope => activity.SetFused(scope.Clone()));
}

// When an exception was handled by other component (i.e: UseExceptionHandler feature).
var exceptionFeature = context.Features.Get<IExceptionHandlerFeature?>();
Expand Down
7 changes: 7 additions & 0 deletions src/Sentry.OpenTelemetry/SentrySpanProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using OpenTelemetry;
using Sentry.Extensibility;
using Sentry.Internal;
using Sentry.Internal.Extensions;
using Sentry.Internal.OpenTelemetry;

Expand Down Expand Up @@ -156,6 +157,12 @@ public override void OnEnd(Activity data)

// Transactions set otel attributes (and resource attributes) as context.
transaction.Contexts["otel"] = GetOtelContext(attributes);

var activityScope = data.GetFused<Scope>();
if (activityScope is { } savedScope && _hub is IHubEx hub)
{
hub.RestoreScope(savedScope);
}
}
else
{
Expand Down
5 changes: 5 additions & 0 deletions src/Sentry/Extensibility/HubAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ public void AddBreadcrumb(
SentryId IHubEx.CaptureEventInternal(SentryEvent evt, Hint? hint, Scope? scope)
=> SentrySdk.CaptureEventInternal(evt, hint, scope);

/// <summary>
/// Forwards the call to <see cref="SentrySdk"/>
/// </summary>
public void RestoreScope(Scope savedScope) => SentrySdk.RestoreScope(savedScope);

/// <summary>
/// Forwards the call to <see cref="SentrySdk"/>.
/// </summary>
Expand Down
11 changes: 6 additions & 5 deletions src/Sentry/Internal/Hub.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Sentry.Extensibility;
using Sentry.Infrastructure;
using Sentry.Integrations;
using Sentry.Internal.ScopeStack;

namespace Sentry.Internal;

Expand Down Expand Up @@ -97,6 +98,8 @@ public async Task ConfigureScopeAsync(Func<Scope, Task> configureScope)

public IDisposable PushScope<TState>(TState state) => ScopeManager.PushScope(state);

public void RestoreScope(Scope savedScope) => ScopeManager.RestoreScope(savedScope);

[Obsolete]
public void WithScope(Action<Scope> scopeCallback) => ScopeManager.WithScope(scopeCallback);

Expand Down Expand Up @@ -486,8 +489,7 @@ public void CaptureTransaction(Transaction transaction, Hint? hint)
try
{
// Apply scope data
var currentScopeAndClient = ScopeManager.GetCurrent();
var scope = currentScopeAndClient.Key;
var (scope, client) = ScopeManager.GetCurrent();
scope.Evaluate();
scope.Apply(transaction);

Expand All @@ -513,7 +515,6 @@ public void CaptureTransaction(Transaction transaction, Hint? hint)
}
}

var client = currentScopeAndClient.Value;
client.CaptureTransaction(processedTransaction, hint);
}
catch (Exception e)
Expand Down Expand Up @@ -543,8 +544,8 @@ public async Task FlushAsync(TimeSpan timeout)
{
try
{
var currentScope = ScopeManager.GetCurrent();
await currentScope.Value.FlushAsync(timeout).ConfigureAwait(false);
var (_, client) = ScopeManager.GetCurrent();
await client.FlushAsync(timeout).ConfigureAwait(false);
}
catch (Exception e)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Sentry/Internal/IHubEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ namespace Sentry.Internal;
internal interface IHubEx : IHub
{
SentryId CaptureEventInternal(SentryEvent evt, Hint? hint, Scope? scope = null);
void RestoreScope(Scope savedScope);

T? WithScope<T>(Func<Scope, T?> scopeCallback);
Task WithScopeAsync(Func<Scope, Task> scopeCallback);
Task<T?> WithScopeAsync<T>(Func<Scope, Task<T?>> scopeCallback);
Expand Down
2 changes: 2 additions & 0 deletions src/Sentry/Internal/IInternalScopeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ namespace Sentry.Internal;
internal interface IInternalScopeManager : ISentryScopeManager, IDisposable
{
KeyValuePair<Scope, ISentryClient> GetCurrent();
void RestoreScope(Scope savedScope);

IScopeStackContainer ScopeStackContainer { get; }

// TODO: Move The following to ISentryScopeManager in a future major version.
Expand Down
42 changes: 25 additions & 17 deletions src/Sentry/Internal/SentryScopeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,18 @@ public SentryScopeManager(SentryOptions options, ISentryClient rootClient)
NewStack = () => new[] { new KeyValuePair<Scope, ISentryClient>(new Scope(options), rootClient) };
}

public KeyValuePair<Scope, ISentryClient> GetCurrent()
{
var current = ScopeAndClientStack;
return current[^1];
}
public KeyValuePair<Scope, ISentryClient> GetCurrent() => ScopeAndClientStack[^1];

public void ConfigureScope(Action<Scope>? configureScope)
{
var scope = GetCurrent();
configureScope?.Invoke(scope.Key);
var (scope, _) = GetCurrent();
configureScope?.Invoke(scope);
}

public Task ConfigureScopeAsync(Func<Scope, Task>? configureScope)
{
var scope = GetCurrent();
return configureScope?.Invoke(scope.Key) ?? Task.CompletedTask;
var (scope, _) = GetCurrent();
return configureScope?.Invoke(scope) ?? Task.CompletedTask;
}

public IDisposable PushScope() => PushScope<object>(null);
Expand Down Expand Up @@ -91,40 +87,52 @@ public IDisposable PushScope<TState>(TState? state)
ScopeAndClientStack = newScopeAndClientStack;
return scopeSnapshot;
}
public void RestoreScope(Scope savedScope)
{
var currentScopeAndClientStack = ScopeAndClientStack;
var (previousScope, client) = currentScopeAndClientStack[^1];

_options.LogDebug("Scope restored");
var newScopeAndClientStack = new KeyValuePair<Scope, ISentryClient>[currentScopeAndClientStack.Length + 1];
Array.Copy(currentScopeAndClientStack, newScopeAndClientStack, currentScopeAndClientStack.Length);
newScopeAndClientStack[^1] = new KeyValuePair<Scope, ISentryClient>(savedScope, client);

ScopeAndClientStack = newScopeAndClientStack;
}

public void WithScope(Action<Scope> scopeCallback)
{
using (PushScope())
{
var scope = GetCurrent();
scopeCallback.Invoke(scope.Key);
var (scope, _) = GetCurrent();
scopeCallback.Invoke(scope);
}
}

public T? WithScope<T>(Func<Scope, T?> scopeCallback)
{
using (PushScope())
{
var scope = GetCurrent();
return scopeCallback.Invoke(scope.Key);
var (scope, _) = GetCurrent();
return scopeCallback.Invoke(scope);
}
}

public async Task WithScopeAsync(Func<Scope, Task> scopeCallback)
{
using (PushScope())
{
var scope = GetCurrent();
await scopeCallback.Invoke(scope.Key).ConfigureAwait(false);
var (scope, _) = GetCurrent();
await scopeCallback.Invoke(scope).ConfigureAwait(false);
}
}

public async Task<T?> WithScopeAsync<T>(Func<Scope, Task<T?>> scopeCallback)
{
using (PushScope())
{
var scope = GetCurrent();
return await scopeCallback.Invoke(scope.Key).ConfigureAwait(false);
var (scope, _) = GetCurrent();
return await scopeCallback.Invoke(scope).ConfigureAwait(false);
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/Sentry/SentrySdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,17 @@ public void Dispose()
[DebuggerStepThrough]
public static IDisposable PushScope<TState>(TState state) => CurrentHub.PushScope(state);

/// <summary>
/// Restores a previously saved/cloned scope to the top of the stack
/// </summary>
public static void RestoreScope(Scope savedScope)
{
if (CurrentHub is IHubEx hub)
{
hub.RestoreScope(savedScope);
}
}

/// <summary>
/// Creates a new scope that will terminate when disposed.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/Sentry/TransactionTracer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Sentry.Extensibility;
using Sentry.Internal;
using Sentry.Internal.ScopeStack;
using Sentry.Protocol;

namespace Sentry;
Expand Down
2 changes: 2 additions & 0 deletions test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,7 @@ namespace Sentry
public static void PauseSession() { }
public static System.IDisposable PushScope() { }
public static System.IDisposable PushScope<TState>(TState state) { }
public static void RestoreScope(Sentry.Scope savedScope) { }
public static void ResumeSession() { }
public static void StartSession() { }
public static Sentry.ITransaction StartTransaction(Sentry.ITransactionContext context) { }
Expand Down Expand Up @@ -1301,6 +1302,7 @@ namespace Sentry.Extensibility
public void PauseSession() { }
public System.IDisposable PushScope() { }
public System.IDisposable PushScope<TState>(TState state) { }
public void RestoreScope(Sentry.Scope savedScope) { }
public void ResumeSession() { }
public void StartSession() { }
public Sentry.ITransaction StartTransaction(Sentry.ITransactionContext context, System.Collections.Generic.IReadOnlyDictionary<string, object?> customSamplingContext) { }
Expand Down
2 changes: 2 additions & 0 deletions test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ namespace Sentry
public static void PauseSession() { }
public static System.IDisposable PushScope() { }
public static System.IDisposable PushScope<TState>(TState state) { }
public static void RestoreScope(Sentry.Scope savedScope) { }
public static void ResumeSession() { }
public static void StartSession() { }
public static Sentry.ITransaction StartTransaction(Sentry.ITransactionContext context) { }
Expand Down Expand Up @@ -1302,6 +1303,7 @@ namespace Sentry.Extensibility
public void PauseSession() { }
public System.IDisposable PushScope() { }
public System.IDisposable PushScope<TState>(TState state) { }
public void RestoreScope(Sentry.Scope savedScope) { }
public void ResumeSession() { }
public void StartSession() { }
public Sentry.ITransaction StartTransaction(Sentry.ITransactionContext context, System.Collections.Generic.IReadOnlyDictionary<string, object?> customSamplingContext) { }
Expand Down
2 changes: 2 additions & 0 deletions test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ namespace Sentry
public static void PauseSession() { }
public static System.IDisposable PushScope() { }
public static System.IDisposable PushScope<TState>(TState state) { }
public static void RestoreScope(Sentry.Scope savedScope) { }
public static void ResumeSession() { }
public static void StartSession() { }
public static Sentry.ITransaction StartTransaction(Sentry.ITransactionContext context) { }
Expand Down Expand Up @@ -1302,6 +1303,7 @@ namespace Sentry.Extensibility
public void PauseSession() { }
public System.IDisposable PushScope() { }
public System.IDisposable PushScope<TState>(TState state) { }
public void RestoreScope(Sentry.Scope savedScope) { }
public void ResumeSession() { }
public void StartSession() { }
public Sentry.ITransaction StartTransaction(Sentry.ITransactionContext context, System.Collections.Generic.IReadOnlyDictionary<string, object?> customSamplingContext) { }
Expand Down
2 changes: 2 additions & 0 deletions test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,7 @@ namespace Sentry
public static void PauseSession() { }
public static System.IDisposable PushScope() { }
public static System.IDisposable PushScope<TState>(TState state) { }
public static void RestoreScope(Sentry.Scope savedScope) { }
public static void ResumeSession() { }
public static void StartSession() { }
public static Sentry.ITransaction StartTransaction(Sentry.ITransactionContext context) { }
Expand Down Expand Up @@ -1300,6 +1301,7 @@ namespace Sentry.Extensibility
public void PauseSession() { }
public System.IDisposable PushScope() { }
public System.IDisposable PushScope<TState>(TState state) { }
public void RestoreScope(Sentry.Scope savedScope) { }
public void ResumeSession() { }
public void StartSession() { }
public Sentry.ITransaction StartTransaction(Sentry.ITransactionContext context, System.Collections.Generic.IReadOnlyDictionary<string, object?> customSamplingContext) { }
Expand Down