Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- InApp includes/excludes can now be configured using regular expressions ([#3321](https://github.com/getsentry/sentry-dotnet/pull/3321))

### Dependencies

- Bump CLI from v2.31.0 to v2.31.1 ([#3342](https://github.com/getsentry/sentry-dotnet/pull/3342))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
``` ini
```

BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1265/22H2/2022Update/SunValley2)
12th Gen Intel Core i7-12700K, 1 CPU, 20 logical and 12 physical cores
.NET SDK=7.0.200
[Host] : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT AVX2
Job-OZOGBN : .NET 6.0.14 (6.0.1423.7309), X64 RyuJIT AVX2
BenchmarkDotNet v0.13.12, macOS Sonoma 14.4.1 (23E224) [Darwin 23.4.0]
Apple M2 Max, 1 CPU, 12 logical and 12 physical cores
.NET SDK 8.0.204
[Host] : .NET 6.0.22 (6.0.2223.42425), Arm64 RyuJIT AdvSIMD
Job-SWPLGJ : .NET 6.0.22 (6.0.2223.42425), Arm64 RyuJIT AdvSIMD

Runtime=.NET 6.0 InvocationCount=1 UnrollFactor=1
InvocationCount=1 UnrollFactor=1

```
| Method | N | Mean | Error | StdDev | Allocated |
| Method | N | Mean | Error | StdDev | Allocated |
|------------------ |----- |---------:|---------:|---------:|----------:|
| ConfigureAppFrame | 1000 | 97.30 μs | 1.929 μs | 2.575 μs | 133.44 KB |
| ConfigureAppFrame | 1000 | 62.92 μs | 1.249 μs | 3.064 μs | 976 B |
2 changes: 2 additions & 0 deletions src/Sentry.Profiling/SampleProfileBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ internal void AddSample(TraceEvent data, double timestampMs)
var threadIndex = AddThread(thread);
if (threadIndex < 0)
{
_options.DiagnosticLogger?.LogDebug("Profiler Sample threadIndex is invalid. Skipping.");
return;
}

Expand All @@ -64,6 +65,7 @@ internal void AddSample(TraceEvent data, double timestampMs)
var stackIndex = AddStackTrace(callStackIndex);
if (stackIndex < 0)
{
_options.DiagnosticLogger?.LogDebug("Invalid stackIndex for Profiler Sample. Skipping.");
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Sentry.Profiling/SamplingTransactionProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal class SamplingTransactionProfiler : ITransactionProfiler
private SampleProfilerSession _session;
private readonly double _startTimeMs;
private double _endTimeMs;
private TaskCompletionSource _completionSource = new();
private TaskCompletionSource _completionSource = new(TaskCreationOptions.RunContinuationsAsynchronously);

public SamplingTransactionProfiler(SentryOptions options, SampleProfilerSession session, int timeoutMs, CancellationToken cancellationToken)
{
Expand Down
30 changes: 30 additions & 0 deletions src/Sentry/Internal/DelimitedPrefixOrPatternMatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;

namespace Sentry.Internal;

internal class DelimitedPrefixOrPatternMatcher(char delimiter = '.', StringComparison comparison = StringComparison.OrdinalIgnoreCase)
: IStringOrRegexMatcher
{
public bool IsMatch(StringOrRegex stringOrRegex, string value)
{
if (stringOrRegex._prefix is not null)
{
// Check for a prefix followed by the separator
return stringOrRegex._prefix != null && value.StartsWith(stringOrRegex._prefix, comparison) &&
value.Length > stringOrRegex._prefix.Length && value[stringOrRegex._prefix.Length] == delimiter;
}

// Check for any regex match followed by the separator
if (stringOrRegex._regex is not null)
{
foreach (Match match in stringOrRegex._regex.Matches(value))
{
if (value.Length > match.Value.Length && value[match.Value.Length] == delimiter)
{
return true;
}
}
}
return false;
}
}
6 changes: 6 additions & 0 deletions src/Sentry/Internal/IStringOrRegexMatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Sentry.Internal;

internal interface IStringOrRegexMatcher
{
bool IsMatch(StringOrRegex stringOrRegex, string value);
}
13 changes: 13 additions & 0 deletions src/Sentry/Internal/PrefixOrPatternMatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;

namespace Sentry.Internal;

internal class PrefixOrPatternMatcher(StringComparison comparison = StringComparison.OrdinalIgnoreCase)
: IStringOrRegexMatcher
{
public bool IsMatch(StringOrRegex stringOrRegex, string value)
{
return (stringOrRegex._prefix != null && value.StartsWith(stringOrRegex._prefix, comparison)) ||
stringOrRegex?._regex?.IsMatch(value) == true;
}
}
82 changes: 82 additions & 0 deletions src/Sentry/Internal/StringOrRegex.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
namespace Sentry.Internal;

/// <summary>
/// Stores either a plain string or a Regular Expression, typically to match against filters in the SentryOptions
/// </summary>
internal class StringOrRegex
{
internal readonly Regex? _regex;
internal readonly string? _prefix;

/// <summary>
/// Constructs a <see cref="StringOrRegex"/> instance.
/// </summary>
/// <param name="stringOrRegex">The prefix or regular expression pattern to match on.</param>
public StringOrRegex(string stringOrRegex)
{
_prefix = stringOrRegex;
}

/// <summary>
/// Constructs a <see cref="StringOrRegex"/> instance.
/// </summary>
/// <param name="regex"></param>
/// <remarks>
/// Use this constructor when you want the match to be performed using a regular expression.
/// </remarks>
public StringOrRegex(Regex regex) => _regex = regex;

/// <summary>
/// Implicitly converts a <see cref="string"/> to a <see cref="StringOrRegex"/>.
/// </summary>
/// <param name="stringOrRegex"></param>
public static implicit operator StringOrRegex(string stringOrRegex)
{
return new StringOrRegex(stringOrRegex);
}

/// <summary>
/// Implicitly converts a <see cref="Regex"/> to a <see cref="StringOrRegex"/>.
/// </summary>
/// <param name="regex"></param>
public static implicit operator StringOrRegex(Regex regex)
{
return new StringOrRegex(regex);
}

/// <inheritdoc />
public override string ToString() => _prefix ?? _regex?.ToString() ?? "";

/// <inheritdoc />
public override bool Equals(object? obj)
{
return
(obj is StringOrRegex pattern)
&& pattern.ToString() == ToString();
}

/// <inheritdoc />
public override int GetHashCode()
{
return ToString().GetHashCode();
}
}

internal static class StringOrRegexExtensions
{
public static bool MatchesAny(this string parameter, List<StringOrRegex>? patterns, IStringOrRegexMatcher matcher)
{
if (patterns is null)
{
return false;
}
foreach (var stringOrRegex in patterns)
{
if (matcher.IsMatch(stringOrRegex, parameter))
{
return true;
}
}
return false;
}
}
91 changes: 83 additions & 8 deletions src/Sentry/SentryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class SentryOptions
#endif
{
private Dictionary<string, string>? _defaultTags;
private const RegexOptions DefaultRegexOptions = RegexOptions.Compiled | RegexOptions.CultureInvariant;

/// <summary>
/// If set, the <see cref="SentryScopeManager"/> will ignore <see cref="IsGlobalModeEnabled"/>
Expand Down Expand Up @@ -235,31 +236,29 @@ internal HttpClient GetHttpClient()
public ISentryScopeStateProcessor SentryScopeStateProcessor { get; set; } = new DefaultSentryScopeStateProcessor();

/// <summary>
/// A list of namespaces (or prefixes) considered not part of application code
/// A list of prefixes or patterns used to filter namespaces that are not considered part of application code
/// </summary>
/// <remarks>
/// Sentry by default filters the stacktrace to display only application code.
/// A user can optionally click to see all which will include framework and libraries.
/// A <see cref="string.StartsWith(string)"/> is executed
/// </remarks>
/// <example>
/// 'System.', 'Microsoft.'
/// </example>
internal List<string>? InAppExclude { get; set; }
internal List<StringOrRegex>? InAppExclude { get; set; }

/// <summary>
/// A list of namespaces (or prefixes) considered part of application code
/// A list of prefixes or patterns used to filter namespaces that are considered part of application code
/// </summary>
/// <remarks>
/// Sentry by default filters the stacktrace to display only application code.
/// A user can optionally click to see all which will include framework and libraries.
/// A <see cref="string.StartsWith(string)"/> is executed
/// </remarks>
/// <example>
/// 'System.CustomNamespace', 'Microsoft.Azure.App'
/// </example>
/// <seealso href="https://docs.sentry.io/platforms/dotnet/guides/aspnet/configuration/options/#in-app-include"/>
internal List<string>? InAppInclude { get; set; }
internal List<StringOrRegex>? InAppInclude { get; set; }

/// <summary>
/// Whether to include default Personal Identifiable information
Expand Down Expand Up @@ -1358,14 +1357,54 @@ public void AddInAppExclude(string prefix)
{
if (InAppExclude == null)
{
InAppExclude = new() { prefix };
InAppExclude = [prefix];
}
else
{
InAppExclude.Add(prefix);
}
}

/// <summary>
/// Add a regex to identify frames that are not 'InApp' in stacktraces. It will usually be more convenient to use
/// the <see cref="AddInAppExcludeRegex"/> method, however you can use this overload if you want to control the
/// <see cref="RegexOptions"/> used when constructing the Regular Expression. For performance reasons, it's
/// recommend that you use <see cref="RegexOptions.Compiled"/> when creating the regex and unless you have specific
/// reason consider using <see cref="RegexOptions.CultureInvariant"/> as well.
/// </summary>
/// <param name="regex">The regular expression to use to match stacktrace frames.</param>
/// <remarks>
/// Sentry by default filters the stacktrace to display only application code.
/// A user can optionally click to see all which will include framework and libraries.
/// If a frame has not already been determined to be InApp per any of the prefixes or
/// patterns configured in <see cref="InAppInclude"/>, it will be considered InApp only
/// if it does not match any of the prefixes or patterns in <see cref="InAppExclude"/>.
/// </remarks>
public void AddInAppExclude(Regex regex)
{
if (InAppExclude == null)
{
InAppExclude = [regex];
}
else
{
InAppExclude.Add(regex);
}
}

/// <summary>
/// Add a regex pattern used to identify frames that are not 'InApp' in stacktraces.
/// </summary>
/// <param name="pattern">The regular expression to use to match stacktrace frames</param>
/// <remarks>
/// Sentry by default filters the stacktrace to display only application code.
/// A user can optionally click to see all which will include framework and libraries.
/// If a frame has not already been determined to be InApp per any of the prefixes or
/// patterns configured in <see cref="InAppInclude"/>, it will be considered InApp only
/// if it does not match any of the prefixes or patterns in <see cref="InAppExclude"/>.
/// </remarks>
public void AddInAppExcludeRegex(string pattern) => AddInAppExclude(new Regex(pattern, DefaultRegexOptions));

/// <summary>
/// Add prefix to include as in 'InApp' stacktrace.
/// </summary>
Expand All @@ -1382,14 +1421,50 @@ public void AddInAppInclude(string prefix)
{
if (InAppInclude == null)
{
InAppInclude = new() { prefix };
InAppInclude = [prefix];
}
else
{
InAppInclude.Add(prefix);
}
}

/// <summary>
/// Add a regex pattern used to identify 'InApp' frames in stacktraces. It will usually be more convenient to use
/// the <see cref="AddInAppIncludeRegex"/> method, however you can use this overload if you want to control the
/// <see cref="RegexOptions"/> used when constructing the Regular Expression. For performance reasons, it's
/// recommend that you use <see cref="RegexOptions.Compiled"/> when creating the regex and unless you have specific
/// reason consider using <see cref="RegexOptions.CultureInvariant"/> as well.
/// </summary>
/// <param name="regex">The regular expression to use to match stacktrace frames.</param>
/// <remarks>
/// Sentry by default filters the stacktrace to display only application code.
/// A user can optionally click to see all which will include framework and libraries.
/// Frames from namespaces matching this regular expression will be considered "InApp"
/// </remarks>
public void AddInAppInclude(Regex regex)
{
if (InAppInclude == null)
{
InAppInclude = [regex];
}
else
{
InAppInclude.Add(regex);
}
}

/// <summary>
/// Add a regex pattern used to identify 'InApp' frames in stacktraces.
/// </summary>
/// <param name="pattern">A regular expression to use to match stacktrace frames.</param>
/// <remarks>
/// Sentry by default filters the stacktrace to display only application code.
/// A user can optionally click to see all which will include framework and libraries.
/// Frames from namespaces matching this regular expression will be considered "InApp"
/// </remarks>
public void AddInAppIncludeRegex(string pattern) => AddInAppInclude(new Regex(pattern, DefaultRegexOptions));

/// <summary>
/// Add an exception processor.
/// </summary>
Expand Down
Loading