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

Add SentryScopeStateProcessor #603

Merged
merged 13 commits into from
Nov 22, 2020
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## vNext

* Add SentryScopeStateProcessor #603

## 3.0.0-alpha.5

* Replaced `BaseScope` with `IScope`. (#590) @Tyrrrz
Expand Down
28 changes: 27 additions & 1 deletion samples/Sentry.Samples.Console.Customized/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ await SentrySdk.ConfigureScopeAsync(async scope =>
{
client.DefaultRequestHeaders.TryAddWithoutValidation("CustomHeader", new[] { "my value" });
};

// Control/override how to apply the State object into the scope
o.SentryScopeStateProcessor = new MyCustomerScopeStateProcessor();
}))
{
// Ignored by its type due to the setting above
Expand Down Expand Up @@ -222,7 +225,7 @@ public AdminPartMiddleware(ISentryClient adminClient, dynamic middleware)

public void Invoke(dynamic request)
{
using (SentrySdk.PushScope())
using (SentrySdk.PushScope(new SpecialContextObject()))
{
SentrySdk.AddBreadcrumb(request.Path, "request-path");

Expand Down Expand Up @@ -265,4 +268,27 @@ protected override void ProcessException(ArgumentException exception, SentryEven
sentryEvent.SetTag("parameter-name", exception.ParamName);
}
}

private class MyCustomerScopeStateProcessor : ISentryScopeStateProcessor
{
private readonly ISentryScopeStateProcessor _fallback = new DefaultSentryScopeStateProcessor();

public void Apply(IScope scope, object state)
{
if (state is SpecialContextObject specialState)
{
scope.SetTag("SpecialContextObject", specialState.A + specialState.B);
}
else
{
_fallback.Apply(scope, state);
}
}
}

private class SpecialContextObject
{
public string A { get; } = "hello";
public string B { get; } = "world";
}
}
10 changes: 8 additions & 2 deletions src/Sentry/Internal/SentryScopeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,14 @@ public IDisposable PushScope<TState>(TState state)

if (scope.Key.Locked)
{
// TODO: keep state on current scope?
_options.DiagnosticLogger?.LogDebug("Locked scope. No new scope pushed.");
_options?.DiagnosticLogger?.LogDebug("Locked scope. No new scope pushed.");

// Apply to current scope
if (state != null)
{
scope.Key.Apply(state);
}

return DisabledHub.Instance;
}

Expand Down
51 changes: 51 additions & 0 deletions src/Sentry/Protocol/DefaultSentryScopeStateProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sentry.Protocol
{
/// <summary>
/// Defines the logic for applying state onto a scope.
/// </summary>
public class DefaultSentryScopeStateProcessor : ISentryScopeStateProcessor
{
/// <summary>
/// Applies state onto a scope.
/// </summary>
public void Apply(IScope scope, object state)
{
switch (state)
{
case string scopeString:
// TODO: find unique key to support multiple single-string scopes
scope.SetTag("scope", scopeString);
break;
case IEnumerable<KeyValuePair<string, string>> keyValStringString:
scope.SetTags(keyValStringString
.Where(kv => !string.IsNullOrEmpty(kv.Value)));
break;
case IEnumerable<KeyValuePair<string, object>> keyValStringObject:
{
scope.SetTags(keyValStringObject
.Select(k => new KeyValuePair<string, string>(
k.Key,
k.Value?.ToString()!))
.Where(kv => !string.IsNullOrEmpty(kv.Value)));

break;
}
#if HAS_VALUE_TUPLE
case ValueTuple<string, string> tupleStringString:
if (!string.IsNullOrEmpty(tupleStringString.Item2))
{
scope.SetTag(tupleStringString.Item1, tupleStringString.Item2);
}
break;
#endif
default:
scope.SetExtra("state", state);
break;
}
}
}
}
5 changes: 5 additions & 0 deletions src/Sentry/Protocol/IScopeOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ namespace Sentry.Protocol
/// </summary>
public interface IScopeOptions
{
/// <summary>
/// Configured scope processor.
/// </summary>
ISentryScopeStateProcessor SentryScopeStateProcessor { get; set; }

/// <summary>
/// Gets or sets the maximum breadcrumbs.
/// </summary>
Expand Down
13 changes: 13 additions & 0 deletions src/Sentry/Protocol/ISentryScopeStateProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Sentry.Protocol
{
/// <summary>
/// Defines the logic for applying state onto a scope.
/// </summary>
public interface ISentryScopeStateProcessor
{
/// <summary>
/// Applies state onto a scope.
/// </summary>
void Apply(IScope scope, object state);
}
}
37 changes: 3 additions & 34 deletions src/Sentry/ScopeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Sentry.Extensibility;
using System.Linq;
using Sentry.Internal;
using Sentry.Protocol;
using Constants = Sentry.Protocol.Constants;
Expand Down Expand Up @@ -354,39 +354,8 @@ public static void Apply(this IScope from, IScope to)
/// <param name="state">The state object to apply.</param>
public static void Apply(this IScope scope, object state)
{
switch (state)
{
case string scopeString:
// TODO: find unique key to support multiple single-string scopes
scope.SetTag("scope", scopeString);
break;
case IEnumerable<KeyValuePair<string, string>> keyValStringString:
scope.SetTags(keyValStringString
.Where(kv => !string.IsNullOrEmpty(kv.Value)));
break;
case IEnumerable<KeyValuePair<string, object>> keyValStringObject:
{
scope.SetTags(keyValStringObject
.Select(k => new KeyValuePair<string, string>(
k.Key,
k.Value?.ToString()!))
.Where(kv => !string.IsNullOrEmpty(kv.Value)));

break;
}
#if HAS_VALUE_TUPLE
case ValueTuple<string, string> tupleStringString:
if (!string.IsNullOrEmpty(tupleStringString.Item2))
{
scope.SetTag(tupleStringString.Item1, tupleStringString.Item2);
}

break;
#endif
default:
scope.SetExtra("state", state);
break;
}
var processor = scope.ScopeOptions?.SentryScopeStateProcessor ?? new DefaultSentryScopeStateProcessor();
processor.Apply(scope, state);
}

/// <summary>
Expand Down
3 changes: 3 additions & 0 deletions src/Sentry/SentryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ public class SentryOptions : IScopeOptions

internal ISentryHttpClientFactory? SentryHttpClientFactory { get; set; }

/// <inheritdoc />
public ISentryScopeStateProcessor SentryScopeStateProcessor { get; set; } = new DefaultSentryScopeStateProcessor();

/// <summary>
/// A list of namespaces (or prefixes) considered not part of application code
/// </summary>
Expand Down