From de065c716d224fd69062ce86e135c9aaeeb7e981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:04:11 +0100 Subject: [PATCH 1/3] feat(metrics): Trace-connected Metrics (Analyzers) --- .../AnalyzerReleases.Shipped.md | 3 + .../AnalyzerReleases.Unshipped.md | 8 + .../TraceConnectedMetricsAnalyzer.cs | 107 +++++++++ .../DiagnosticCategories.cs | 6 + .../DiagnosticIds.cs | 6 + .../Sentry.Compiler.Extensions.csproj | 8 +- .../SymbolDisplayFormats.cs | 12 + .../TraceConnectedMetricsAnalyzerTests.cs | 209 ++++++++++++++++++ .../Sentry.Compiler.Extensions.Tests.csproj | 10 +- .../SolutionTransforms.cs | 37 ++++ .../TargetFramework.cs | 23 ++ 11 files changed, 423 insertions(+), 6 deletions(-) create mode 100644 src/Sentry.Compiler.Extensions/AnalyzerReleases.Shipped.md create mode 100644 src/Sentry.Compiler.Extensions/AnalyzerReleases.Unshipped.md create mode 100644 src/Sentry.Compiler.Extensions/Analyzers/TraceConnectedMetricsAnalyzer.cs create mode 100644 src/Sentry.Compiler.Extensions/DiagnosticCategories.cs create mode 100644 src/Sentry.Compiler.Extensions/DiagnosticIds.cs create mode 100644 src/Sentry.Compiler.Extensions/SymbolDisplayFormats.cs create mode 100644 test/Sentry.Compiler.Extensions.Tests/Analyzers/TraceConnectedMetricsAnalyzerTests.cs create mode 100644 test/Sentry.Compiler.Extensions.Tests/SolutionTransforms.cs create mode 100644 test/Sentry.Compiler.Extensions.Tests/TargetFramework.cs diff --git a/src/Sentry.Compiler.Extensions/AnalyzerReleases.Shipped.md b/src/Sentry.Compiler.Extensions/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000000..60b59dd99b --- /dev/null +++ b/src/Sentry.Compiler.Extensions/AnalyzerReleases.Shipped.md @@ -0,0 +1,3 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + diff --git a/src/Sentry.Compiler.Extensions/AnalyzerReleases.Unshipped.md b/src/Sentry.Compiler.Extensions/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000000..f8fc0f9c86 --- /dev/null +++ b/src/Sentry.Compiler.Extensions/AnalyzerReleases.Unshipped.md @@ -0,0 +1,8 @@ +; Unshipped analyzer release +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +SENTRY1001 | Support | Warning | TraceConnectedMetricsAnalyzer \ No newline at end of file diff --git a/src/Sentry.Compiler.Extensions/Analyzers/TraceConnectedMetricsAnalyzer.cs b/src/Sentry.Compiler.Extensions/Analyzers/TraceConnectedMetricsAnalyzer.cs new file mode 100644 index 0000000000..7af73b2c34 --- /dev/null +++ b/src/Sentry.Compiler.Extensions/Analyzers/TraceConnectedMetricsAnalyzer.cs @@ -0,0 +1,107 @@ +using System; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Sentry.Compiler.Extensions.Analyzers; + +/// +/// Guide consumers to use the public API of Sentry Trace-connected Metrics correctly. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class TraceConnectedMetricsAnalyzer : DiagnosticAnalyzer +{ + private static readonly string Title = "Unsupported numeric type of Metric"; + private static readonly string MessageFormat = "{0} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double."; + private static readonly string Description = "Integers should be a 64-bit signed integer, while doubles should be a 64-bit floating point number."; + + private static readonly DiagnosticDescriptor Rule = new( + id: DiagnosticIds.Sentry1001, + title: Title, + messageFormat: MessageFormat, + category: DiagnosticCategories.Support, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: Description, + helpLinkUri: null + ); + + /// + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterOperationAction(Execute, OperationKind.Invocation); + } + + private static void Execute(OperationAnalysisContext context) + { + Debug.Assert(context.Operation.Language == LanguageNames.CSharp); + Debug.Assert(context.Operation.Kind is OperationKind.Invocation); + + context.CancellationToken.ThrowIfCancellationRequested(); + + if (context.Operation is not IInvocationOperation invocation) + { + return; + } + + var method = invocation.TargetMethod; + if (method.DeclaredAccessibility != Accessibility.Public || method.IsAbstract || method.IsVirtual || method.IsStatic || !method.ReturnsVoid || method.Parameters.Length == 0) + { + return; + } + + if (!method.IsGenericMethod || method.Arity != 1 || method.TypeArguments.Length != 1) + { + return; + } + + if (!method.Name.Equals("EmitCounter", StringComparison.Ordinal) && + !method.Name.Equals("EmitGauge", StringComparison.Ordinal) && + !method.Name.Equals("EmitDistribution", StringComparison.Ordinal)) + { + return; + } + + if (method.ContainingAssembly is null || method.ContainingAssembly.Name != "Sentry") + { + return; + } + + if (method.ContainingNamespace is null || method.ContainingNamespace.Name != "Sentry") + { + return; + } + + var typeArgument = method.TypeArguments[0]; + if (typeArgument.SpecialType is SpecialType.System_Byte or SpecialType.System_Int16 or SpecialType.System_Int32 or SpecialType.System_Int64 or SpecialType.System_Single or SpecialType.System_Double) + { + return; + } + + if (typeArgument is ITypeParameterSymbol) + { + return; + } + + var sentryType = context.Compilation.GetTypeByMetadataName("Sentry.SentryTraceMetrics"); + if (sentryType is null) + { + return; + } + + if (!SymbolEqualityComparer.Default.Equals(method.ContainingType, sentryType)) + { + return; + } + + var location = invocation.Syntax.GetLocation(); + var diagnostic = Diagnostic.Create(Rule, location, typeArgument.ToDisplayString(SymbolDisplayFormats.FullNameFormat)); + context.ReportDiagnostic(diagnostic); + } +} diff --git a/src/Sentry.Compiler.Extensions/DiagnosticCategories.cs b/src/Sentry.Compiler.Extensions/DiagnosticCategories.cs new file mode 100644 index 0000000000..3fe408be60 --- /dev/null +++ b/src/Sentry.Compiler.Extensions/DiagnosticCategories.cs @@ -0,0 +1,6 @@ +namespace Sentry.Compiler.Extensions; + +internal static class DiagnosticCategories +{ + internal const string Support = nameof(Support); +} diff --git a/src/Sentry.Compiler.Extensions/DiagnosticIds.cs b/src/Sentry.Compiler.Extensions/DiagnosticIds.cs new file mode 100644 index 0000000000..fa2f8a3d94 --- /dev/null +++ b/src/Sentry.Compiler.Extensions/DiagnosticIds.cs @@ -0,0 +1,6 @@ +namespace Sentry.Compiler.Extensions; + +internal static class DiagnosticIds +{ + internal const string Sentry1001 = "SENTRY1001"; +} diff --git a/src/Sentry.Compiler.Extensions/Sentry.Compiler.Extensions.csproj b/src/Sentry.Compiler.Extensions/Sentry.Compiler.Extensions.csproj index 9fb31748a3..6ec6654892 100644 --- a/src/Sentry.Compiler.Extensions/Sentry.Compiler.Extensions.csproj +++ b/src/Sentry.Compiler.Extensions/Sentry.Compiler.Extensions.csproj @@ -15,11 +15,17 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + diff --git a/src/Sentry.Compiler.Extensions/SymbolDisplayFormats.cs b/src/Sentry.Compiler.Extensions/SymbolDisplayFormats.cs new file mode 100644 index 0000000000..85af2df64b --- /dev/null +++ b/src/Sentry.Compiler.Extensions/SymbolDisplayFormats.cs @@ -0,0 +1,12 @@ +using Microsoft.CodeAnalysis; + +namespace Sentry.Compiler.Extensions; + +internal static class SymbolDisplayFormats +{ + internal static SymbolDisplayFormat FullNameFormat { get; } = new SymbolDisplayFormat( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters + ); +} diff --git a/test/Sentry.Compiler.Extensions.Tests/Analyzers/TraceConnectedMetricsAnalyzerTests.cs b/test/Sentry.Compiler.Extensions.Tests/Analyzers/TraceConnectedMetricsAnalyzerTests.cs new file mode 100644 index 0000000000..91249b450c --- /dev/null +++ b/test/Sentry.Compiler.Extensions.Tests/Analyzers/TraceConnectedMetricsAnalyzerTests.cs @@ -0,0 +1,209 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; +using Sentry.Compiler.Extensions.Analyzers; + +using Verifier = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier; + +namespace Sentry.Compiler.Extensions.Tests.Analyzers; + +public class TraceConnectedMetricsAnalyzerTests +{ + [Fact] + public async Task NoCode_NoDiagnostics() + { + await Verifier.VerifyAnalyzerAsync(""); + } + + [Fact] + public async Task NoInvocations_NoDiagnostics() + { + var test = new CSharpAnalyzerTest + { + TestState = + { + ReferenceAssemblies = TargetFramework.ReferenceAssemblies, + AdditionalReferences = { typeof(SentryTraceMetrics).Assembly }, + Sources = + { + """ + #nullable enable + using Sentry; + + public class AnalyzerTest + { + public void Test(IHub hub) + { + var metrics = SentrySdk.Experimental.Metrics; + + _ = metrics.GetType(); + + #pragma warning disable SENTRYTRACECONNECTEDMETRICS + _ = hub.Metrics.GetType(); + #pragma warning restore SENTRYTRACECONNECTEDMETRICS + + _ = SentrySdk.Experimental.Metrics.Equals(null); + _ = SentrySdk.Experimental.Metrics.GetHashCode(); + _ = SentrySdk.Experimental.Metrics.GetType(); + _ = SentrySdk.Experimental.Metrics.ToString(); + } + } + """ + }, + ExpectedDiagnostics = { }, + } + }; + + await test.RunAsync(); + } + + [Fact] + public async Task SupportedInvocations_NoDiagnostics() + { + var test = new CSharpAnalyzerTest + { + TestState = + { + ReferenceAssemblies = TargetFramework.ReferenceAssemblies, + AdditionalReferences = { typeof(SentryTraceMetrics).Assembly }, + Sources = + { + """ + #nullable enable + using Sentry; + + public class AnalyzerTest + { + public void Test(IHub hub) + { + var scope = new Scope(new SentryOptions()); + var metrics = SentrySdk.Experimental.Metrics; + + #pragma warning disable SENTRYTRACECONNECTEDMETRICS + metrics.EmitCounter("name", 1); + hub.Metrics.EmitCounter("name", 1f); + SentrySdk.Experimental.Metrics.EmitCounter("name", 1.1d, [], scope); + + metrics.EmitGauge("name", 2); + hub.Metrics.EmitGauge("name", 2f); + SentrySdk.Experimental.Metrics.EmitGauge("name", 2.2d, "unit", [], scope); + + metrics.EmitDistribution("name", 3); + hub.Metrics.EmitDistribution("name", 3f); + SentrySdk.Experimental.Metrics.EmitDistribution("name", 3.3d, "unit", [], scope); + #pragma warning restore SENTRYTRACECONNECTEDMETRICS + } + } + + public static class Extensions + { + public static void EmitCounter(this SentryTraceMetrics metrics) where T : struct + { + metrics.EmitCounter("default", default(T), [], null); + } + + public static void EmitCounter(this SentryTraceMetrics metrics, string name) where T : struct + { + metrics.EmitCounter(name, default(T), [], null); + } + + public static void EmitGauge(this SentryTraceMetrics metrics) where T : struct + { + metrics.EmitGauge("default", default(T), null, [], null); + } + + public static void EmitGauge(this SentryTraceMetrics metrics, string name) where T : struct + { + metrics.EmitGauge(name, default(T), null, [], null); + } + + public static void EmitDistribution(this SentryTraceMetrics metrics) where T : struct + { + metrics.EmitDistribution("default", default(T), null, [], null); + } + + public static void EmitDistribution(this SentryTraceMetrics metrics, string name) where T : struct + { + metrics.EmitDistribution(name, default(T), null, [], null); + } + } + """ + }, + ExpectedDiagnostics = { }, + }, + SolutionTransforms = { SolutionTransforms.Nullable }, + }; + + await test.RunAsync(); + } + + [Fact] + public async Task UnsupportedInvocations_ReportDiagnostics() + { + var test = new CSharpAnalyzerTest + { + TestState = + { + ReferenceAssemblies = TargetFramework.ReferenceAssemblies, + AdditionalReferences = { typeof(SentryTraceMetrics).Assembly }, + Sources = + { + """ + #nullable enable + using System; + using Sentry; + + public class AnalyzerTest + { + public void Test(IHub hub) + { + var scope = new Scope(new SentryOptions()); + var metrics = SentrySdk.Experimental.Metrics; + + #pragma warning disable SENTRYTRACECONNECTEDMETRICS + {|#0:metrics.EmitCounter("name", (uint)1)|#0}; + {|#1:hub.Metrics.EmitCounter("name", (StringComparison)1f)|#1}; + {|#2:SentrySdk.Experimental.Metrics.EmitCounter("name", 1.1m, [], scope)|#2}; + + {|#3:metrics.EmitGauge("name", (uint)2)|#3}; + {|#4:hub.Metrics.EmitGauge("name", (StringComparison)2f)|#4}; + {|#5:SentrySdk.Experimental.Metrics.EmitGauge("name", 2.2m, "unit", [], scope)|#5}; + + {|#6:metrics.EmitDistribution("name", (uint)3)|#6}; + {|#7:hub.Metrics.EmitDistribution("name", (StringComparison)3f)|#7}; + {|#8:SentrySdk.Experimental.Metrics.EmitDistribution("name", 3.3m, "unit", [], scope)|#8}; + #pragma warning restore SENTRYTRACECONNECTEDMETRICS + } + } + """ + }, + ExpectedDiagnostics = + { + CreateDiagnostic(0, typeof(uint)), + CreateDiagnostic(1, typeof(StringComparison)), + CreateDiagnostic(2, typeof(decimal)), + CreateDiagnostic(3, typeof(uint)), + CreateDiagnostic(4, typeof(StringComparison)), + CreateDiagnostic(5, typeof(decimal)), + CreateDiagnostic(6, typeof(uint)), + CreateDiagnostic(7, typeof(StringComparison)), + CreateDiagnostic(8, typeof(decimal)), + }, + } + }; + + await test.RunAsync(); + } + + private static DiagnosticResult CreateDiagnostic(int markupKey, Type type) + { + Assert.NotNull(type.FullName); + + return Verifier.Diagnostic("SENTRY1001") + .WithSeverity(DiagnosticSeverity.Warning) + .WithArguments(type.FullName) + .WithMessage($"{type.FullName} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.") + .WithMessageFormat("{0} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.") + .WithLocation(markupKey); + } +} diff --git a/test/Sentry.Compiler.Extensions.Tests/Sentry.Compiler.Extensions.Tests.csproj b/test/Sentry.Compiler.Extensions.Tests/Sentry.Compiler.Extensions.Tests.csproj index 3265c3c21c..159e1c3ccc 100644 --- a/test/Sentry.Compiler.Extensions.Tests/Sentry.Compiler.Extensions.Tests.csproj +++ b/test/Sentry.Compiler.Extensions.Tests/Sentry.Compiler.Extensions.Tests.csproj @@ -7,14 +7,14 @@ - - - - + + + + - + diff --git a/test/Sentry.Compiler.Extensions.Tests/SolutionTransforms.cs b/test/Sentry.Compiler.Extensions.Tests/SolutionTransforms.cs new file mode 100644 index 0000000000..bc4bfb44ce --- /dev/null +++ b/test/Sentry.Compiler.Extensions.Tests/SolutionTransforms.cs @@ -0,0 +1,37 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Sentry.Compiler.Extensions.Tests; + +internal static class SolutionTransforms +{ + private static readonly ImmutableDictionary s_nullableWarnings = GetNullableWarningsFromCompiler(); + + internal static Func Nullable { get; } = static (solution, projectId) => + { + var project = solution.GetProject(projectId); + Assert.NotNull(project); + + var compilationOptions = project.CompilationOptions; + Assert.NotNull(compilationOptions); + + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(compilationOptions.SpecificDiagnosticOptions.SetItems(s_nullableWarnings)); + + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + return solution; + }; + + private static ImmutableDictionary GetNullableWarningsFromCompiler() + { + string[] args = { "/warnaserror:nullable" }; + var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, Environment.CurrentDirectory, Environment.CurrentDirectory, null); + var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions; + + // Workaround for https://github.com/dotnet/roslyn/issues/41610 + nullableWarnings = nullableWarnings + .SetItem("CS8632", ReportDiagnostic.Error) + .SetItem("CS8669", ReportDiagnostic.Error); + + return nullableWarnings; + } +} diff --git a/test/Sentry.Compiler.Extensions.Tests/TargetFramework.cs b/test/Sentry.Compiler.Extensions.Tests/TargetFramework.cs new file mode 100644 index 0000000000..3e8191b8c4 --- /dev/null +++ b/test/Sentry.Compiler.Extensions.Tests/TargetFramework.cs @@ -0,0 +1,23 @@ +using Microsoft.CodeAnalysis.Testing; + +namespace Sentry.Compiler.Extensions.Tests; + +internal static class TargetFramework +{ + internal static ReferenceAssemblies ReferenceAssemblies + { + get + { +#if NET8_0 + return ReferenceAssemblies.Net.Net80; +#elif NET9_0 + return ReferenceAssemblies.Net.Net90; +#elif NET10_0 + return ReferenceAssemblies.Net.Net100; +#else +#warning Target Framework not implemented. + throw new NotImplementedException(); +#endif + } + } +} From d2a02e1ea604279a67a8124024caa0620c0a782b Mon Sep 17 00:00:00 2001 From: Flash0ver <38893694+Flash0ver@users.noreply.github.com> Date: Mon, 19 Jan 2026 22:58:56 +0100 Subject: [PATCH 2/3] ref: align public compiler extensions with internal analyzer project --- .../Sentry.Compiler.Extensions.csproj | 12 ++++++++++-- .../Sentry.Compiler.Extensions.Tests.csproj | 4 ++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Sentry.Compiler.Extensions/Sentry.Compiler.Extensions.csproj b/src/Sentry.Compiler.Extensions/Sentry.Compiler.Extensions.csproj index 6ec6654892..8e5a6c88aa 100644 --- a/src/Sentry.Compiler.Extensions/Sentry.Compiler.Extensions.csproj +++ b/src/Sentry.Compiler.Extensions/Sentry.Compiler.Extensions.csproj @@ -18,14 +18,22 @@ + + + + + - - + + diff --git a/test/Sentry.Compiler.Extensions.Tests/Sentry.Compiler.Extensions.Tests.csproj b/test/Sentry.Compiler.Extensions.Tests/Sentry.Compiler.Extensions.Tests.csproj index 159e1c3ccc..e005a20dc9 100644 --- a/test/Sentry.Compiler.Extensions.Tests/Sentry.Compiler.Extensions.Tests.csproj +++ b/test/Sentry.Compiler.Extensions.Tests/Sentry.Compiler.Extensions.Tests.csproj @@ -19,4 +19,8 @@ + + + + From b87bc4dd3b9aa427f3ec425826b567dfdfe775ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20P=C3=B6lz?= <38893694+Flash0ver@users.noreply.github.com> Date: Tue, 20 Jan 2026 00:23:11 +0100 Subject: [PATCH 3/3] feat: also analyze SetBeforeSendMetric invocation --- .../TraceConnectedMetricsAnalyzer.cs | 19 +++-- .../TraceConnectedMetricsAnalyzerTests.cs | 85 ++++++++++++++----- 2 files changed, 77 insertions(+), 27 deletions(-) diff --git a/src/Sentry.Compiler.Extensions/Analyzers/TraceConnectedMetricsAnalyzer.cs b/src/Sentry.Compiler.Extensions/Analyzers/TraceConnectedMetricsAnalyzer.cs index 7af73b2c34..ff8dd50f95 100644 --- a/src/Sentry.Compiler.Extensions/Analyzers/TraceConnectedMetricsAnalyzer.cs +++ b/src/Sentry.Compiler.Extensions/Analyzers/TraceConnectedMetricsAnalyzer.cs @@ -61,19 +61,26 @@ private static void Execute(OperationAnalysisContext context) return; } - if (!method.Name.Equals("EmitCounter", StringComparison.Ordinal) && - !method.Name.Equals("EmitGauge", StringComparison.Ordinal) && - !method.Name.Equals("EmitDistribution", StringComparison.Ordinal)) + if (method.ContainingAssembly is null || method.ContainingAssembly.Name != "Sentry") { return; } - if (method.ContainingAssembly is null || method.ContainingAssembly.Name != "Sentry") + if (method.ContainingNamespace is null || method.ContainingNamespace.Name != "Sentry") { return; } - if (method.ContainingNamespace is null || method.ContainingNamespace.Name != "Sentry") + string fullyQualifiedMetadataName; + if (method.Name is "EmitCounter" or "EmitGauge" or "EmitDistribution") + { + fullyQualifiedMetadataName = "Sentry.SentryTraceMetrics"; + } + else if (method.Name is "SetBeforeSendMetric") + { + fullyQualifiedMetadataName = "Sentry.SentryOptions+ExperimentalSentryOptions"; + } + else { return; } @@ -89,7 +96,7 @@ private static void Execute(OperationAnalysisContext context) return; } - var sentryType = context.Compilation.GetTypeByMetadataName("Sentry.SentryTraceMetrics"); + var sentryType = context.Compilation.GetTypeByMetadataName(fullyQualifiedMetadataName); if (sentryType is null) { return; diff --git a/test/Sentry.Compiler.Extensions.Tests/Analyzers/TraceConnectedMetricsAnalyzerTests.cs b/test/Sentry.Compiler.Extensions.Tests/Analyzers/TraceConnectedMetricsAnalyzerTests.cs index 91249b450c..a07f701b55 100644 --- a/test/Sentry.Compiler.Extensions.Tests/Analyzers/TraceConnectedMetricsAnalyzerTests.cs +++ b/test/Sentry.Compiler.Extensions.Tests/Analyzers/TraceConnectedMetricsAnalyzerTests.cs @@ -32,7 +32,12 @@ public async Task NoInvocations_NoDiagnostics() public class AnalyzerTest { - public void Test(IHub hub) + public void Init(SentryOptions options) + { + options.Experimental.EnableMetrics = false; + } + + public void Emit(IHub hub) { var metrics = SentrySdk.Experimental.Metrics; @@ -74,7 +79,14 @@ public async Task SupportedInvocations_NoDiagnostics() public class AnalyzerTest { - public void Test(IHub hub) + public void Init(SentryOptions options) + { + options.Experimental.SetBeforeSendMetric(static SentryMetric? (SentryMetric metric) => metric); + options.Experimental.SetBeforeSendMetric(BeforeSendMetric); + options.Experimental.SetBeforeSendMetric(OnBeforeSendMetric); + } + + public void Emit(IHub hub) { var scope = new Scope(new SentryOptions()); var metrics = SentrySdk.Experimental.Metrics; @@ -93,6 +105,16 @@ public void Test(IHub hub) SentrySdk.Experimental.Metrics.EmitDistribution("name", 3.3d, "unit", [], scope); #pragma warning restore SENTRYTRACECONNECTEDMETRICS } + + private static SentryMetric? BeforeSendMetric(SentryMetric metric) where T : struct + { + return metric; + } + + private static SentryMetric? OnBeforeSendMetric(SentryMetric metric) + { + return metric; + } } public static class Extensions @@ -155,39 +177,60 @@ public async Task UnsupportedInvocations_ReportDiagnostics() public class AnalyzerTest { - public void Test(IHub hub) + public void Init(SentryOptions options) + { + {|#0:options.Experimental.SetBeforeSendMetric(static SentryMetric? (SentryMetric metric) => metric)|#0}; + {|#1:options.Experimental.SetBeforeSendMetric(BeforeSendMetric)|#1}; + {|#2:options.Experimental.SetBeforeSendMetric(OnBeforeSendMetric)|#2}; + } + + public void Emit(IHub hub) { var scope = new Scope(new SentryOptions()); var metrics = SentrySdk.Experimental.Metrics; #pragma warning disable SENTRYTRACECONNECTEDMETRICS - {|#0:metrics.EmitCounter("name", (uint)1)|#0}; - {|#1:hub.Metrics.EmitCounter("name", (StringComparison)1f)|#1}; - {|#2:SentrySdk.Experimental.Metrics.EmitCounter("name", 1.1m, [], scope)|#2}; + {|#10:metrics.EmitCounter("name", (uint)1)|#10}; + {|#11:hub.Metrics.EmitCounter("name", (StringComparison)1f)|#11}; + {|#12:SentrySdk.Experimental.Metrics.EmitCounter("name", 1.1m, [], scope)|#12}; - {|#3:metrics.EmitGauge("name", (uint)2)|#3}; - {|#4:hub.Metrics.EmitGauge("name", (StringComparison)2f)|#4}; - {|#5:SentrySdk.Experimental.Metrics.EmitGauge("name", 2.2m, "unit", [], scope)|#5}; + {|#13:metrics.EmitGauge("name", (uint)2)|#13}; + {|#14:hub.Metrics.EmitGauge("name", (StringComparison)2f)|#14}; + {|#15:SentrySdk.Experimental.Metrics.EmitGauge("name", 2.2m, "unit", [], scope)|#15}; - {|#6:metrics.EmitDistribution("name", (uint)3)|#6}; - {|#7:hub.Metrics.EmitDistribution("name", (StringComparison)3f)|#7}; - {|#8:SentrySdk.Experimental.Metrics.EmitDistribution("name", 3.3m, "unit", [], scope)|#8}; + {|#16:metrics.EmitDistribution("name", (uint)3)|#16}; + {|#17:hub.Metrics.EmitDistribution("name", (StringComparison)3f)|#17}; + {|#18:SentrySdk.Experimental.Metrics.EmitDistribution("name", 3.3m, "unit", [], scope)|#18}; #pragma warning restore SENTRYTRACECONNECTEDMETRICS } + + private static SentryMetric? BeforeSendMetric(SentryMetric metric) where T : struct + { + return metric; + } + + private static SentryMetric? OnBeforeSendMetric(SentryMetric metric) + { + return metric; + } } """ }, ExpectedDiagnostics = { - CreateDiagnostic(0, typeof(uint)), - CreateDiagnostic(1, typeof(StringComparison)), - CreateDiagnostic(2, typeof(decimal)), - CreateDiagnostic(3, typeof(uint)), - CreateDiagnostic(4, typeof(StringComparison)), - CreateDiagnostic(5, typeof(decimal)), - CreateDiagnostic(6, typeof(uint)), - CreateDiagnostic(7, typeof(StringComparison)), - CreateDiagnostic(8, typeof(decimal)), + CreateDiagnostic(0, typeof(sbyte)), + CreateDiagnostic(1, typeof(ushort)), + CreateDiagnostic(2, typeof(ulong)), + + CreateDiagnostic(10, typeof(uint)), + CreateDiagnostic(11, typeof(StringComparison)), + CreateDiagnostic(12, typeof(decimal)), + CreateDiagnostic(13, typeof(uint)), + CreateDiagnostic(14, typeof(StringComparison)), + CreateDiagnostic(15, typeof(decimal)), + CreateDiagnostic(16, typeof(uint)), + CreateDiagnostic(17, typeof(StringComparison)), + CreateDiagnostic(18, typeof(decimal)), }, } };