Skip to content

Commit 50186be

Browse files
Flash0vergetsentry-botgetsentry-botclaude
authored
feat(metrics): Trace-connected Metrics (Analyzers) (#4840)
Co-authored-by: getsentry-bot <bot@sentry.io> Co-authored-by: getsentry-bot <bot@getsentry.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent ec73b37 commit 50186be

11 files changed

Lines changed: 505 additions & 6 deletions
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
; Shipped analyzer releases
2+
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
3+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
; Unshipped analyzer release
2+
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
3+
4+
### New Rules
5+
6+
Rule ID | Category | Severity | Notes
7+
--------|----------|----------|-------
8+
SENTRY1001 | Sentry | Warning | TraceConnectedMetricsAnalyzer
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.Diagnostics;
3+
using Microsoft.CodeAnalysis.Operations;
4+
5+
namespace Sentry.Compiler.Extensions.Analyzers;
6+
7+
/// <summary>
8+
/// Guide callers to use the public API of <see href="https://develop.sentry.dev/sdk/telemetry/metrics/">Sentry Trace-connected Metrics</see> correctly.
9+
/// </summary>
10+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
11+
public sealed class TraceConnectedMetricsAnalyzer : DiagnosticAnalyzer
12+
{
13+
private const string Title = "Unsupported numeric type of Metric";
14+
private const string MessageFormat = "{0} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.";
15+
private const string Description = "Integers should be a 64-bit signed integer, while doubles should be a 64-bit floating point number.";
16+
17+
private static readonly DiagnosticDescriptor Rule = new(
18+
id: DiagnosticIds.Sentry1001,
19+
title: Title,
20+
messageFormat: MessageFormat,
21+
category: DiagnosticCategories.Sentry,
22+
defaultSeverity: DiagnosticSeverity.Warning,
23+
isEnabledByDefault: true,
24+
description: Description,
25+
helpLinkUri: null
26+
);
27+
28+
/// <inheritdoc />
29+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
30+
31+
/// <inheritdoc />
32+
public override void Initialize(AnalysisContext context)
33+
{
34+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
35+
context.EnableConcurrentExecution();
36+
37+
context.RegisterOperationAction(Execute, OperationKind.Invocation);
38+
}
39+
40+
private static void Execute(OperationAnalysisContext context)
41+
{
42+
Debug.Assert(context.Operation.Language == LanguageNames.CSharp);
43+
Debug.Assert(context.Operation.Kind is OperationKind.Invocation);
44+
45+
context.CancellationToken.ThrowIfCancellationRequested();
46+
47+
if (context.Operation is not IInvocationOperation invocation)
48+
{
49+
return;
50+
}
51+
52+
var method = invocation.TargetMethod;
53+
if (method.DeclaredAccessibility != Accessibility.Public || method.IsStatic || method.Parameters.Length == 0)
54+
{
55+
return;
56+
}
57+
58+
if (!method.IsGenericMethod || method.Arity != 1 || method.TypeArguments.Length != 1)
59+
{
60+
return;
61+
}
62+
63+
if (method.ContainingAssembly is null || method.ContainingAssembly.Name != "Sentry")
64+
{
65+
return;
66+
}
67+
68+
if (method.ContainingNamespace is null || method.ContainingNamespace.Name != "Sentry")
69+
{
70+
return;
71+
}
72+
73+
string fullyQualifiedMetadataName;
74+
if (method.Name is "EmitCounter" or "EmitGauge" or "EmitDistribution")
75+
{
76+
fullyQualifiedMetadataName = "Sentry.SentryMetricEmitter";
77+
}
78+
else if (method.Name is "TryGetValue")
79+
{
80+
fullyQualifiedMetadataName = "Sentry.SentryMetric";
81+
}
82+
else
83+
{
84+
return;
85+
}
86+
87+
var typeArgument = method.TypeArguments[0];
88+
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)
89+
{
90+
return;
91+
}
92+
93+
if (typeArgument is ITypeParameterSymbol)
94+
{
95+
return;
96+
}
97+
98+
var sentryType = context.Compilation.GetTypeByMetadataName(fullyQualifiedMetadataName);
99+
if (sentryType is null)
100+
{
101+
return;
102+
}
103+
104+
if (!SymbolEqualityComparer.Default.Equals(method.ContainingType, sentryType))
105+
{
106+
return;
107+
}
108+
109+
var location = invocation.Syntax.GetLocation();
110+
var diagnostic = Diagnostic.Create(Rule, location, typeArgument.ToDisplayString(SymbolDisplayFormats.FullNameFormat));
111+
context.ReportDiagnostic(diagnostic);
112+
}
113+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Sentry.Compiler.Extensions;
2+
3+
internal static class DiagnosticCategories
4+
{
5+
internal const string Sentry = nameof(Sentry);
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace Sentry.Compiler.Extensions;
2+
3+
internal static class DiagnosticIds
4+
{
5+
internal const string Sentry1001 = "SENTRY1001";
6+
}

src/Sentry.Compiler.Extensions/Sentry.Compiler.Extensions.csproj

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,25 @@
1515
<PrivateAssets>all</PrivateAssets>
1616
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1717
</PackageReference>
18-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" PrivateAssets="all"/>
18+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" PrivateAssets="all" />
19+
</ItemGroup>
20+
21+
<!--
22+
We use Simon Cropp's Polyfill source-only package to access APIs in lower targets.
23+
https://github.com/SimonCropp/Polyfill
24+
-->
25+
<ItemGroup>
26+
<PackageReference Include="Polyfill" Version="1.33.2" PrivateAssets="all" />
1927
</ItemGroup>
2028

2129
<ItemGroup>
2230
<Using Remove="System.Text.Json" />
2331
<Using Remove="System.Text.Json.Serialization" />
2432
</ItemGroup>
33+
34+
<ItemGroup>
35+
<AdditionalFiles Remove="AnalyzerReleases.Shipped.md" />
36+
<AdditionalFiles Remove="AnalyzerReleases.Unshipped.md" />
37+
</ItemGroup>
38+
2539
</Project>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Microsoft.CodeAnalysis;
2+
3+
namespace Sentry.Compiler.Extensions;
4+
5+
internal static class SymbolDisplayFormats
6+
{
7+
internal static SymbolDisplayFormat FullNameFormat { get; } = new(
8+
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
9+
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
10+
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters
11+
);
12+
}

0 commit comments

Comments
 (0)