-
-
Notifications
You must be signed in to change notification settings - Fork 229
feat(metrics): Trace-connected Metrics (Implementation) #4834
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
base: main
Are you sure you want to change the base?
Conversation
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #4834 +/- ##
==========================================
- Coverage 73.79% 72.59% -1.21%
==========================================
Files 483 495 +12
Lines 17551 17893 +342
Branches 3461 3523 +62
==========================================
+ Hits 12952 12989 +37
- Misses 3746 4048 +302
- Partials 853 856 +3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
src/Sentry/SentryMetric.cs
Outdated
| public void SetAttribute(string key, object value) | ||
| { | ||
| _attributes ??= new Dictionary<string, object>(); | ||
|
|
||
| _attributes[key] = new SentryAttribute(value); | ||
| } |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in e65db5f
…rics Upgrades Sentry .NET SDK from 6.0.0 to 6.1.0-alpha.1 to test the new experimental trace-connected metrics API from PR getsentry/sentry-dotnet#4834. Changes: - Upgrade Sentry SDK to 6.1.0-alpha.1 - Add SentryClientMetrics decorator that emits metrics to Sentry - Enable experimental metrics in all apps (Server, Console, Android) - Use SentryClientMetrics in DI registrations The new metrics API provides: - AddCounter: for counting events (files, uploads, errors) - RecordDistribution: for value distributions (uploaded bytes) - RecordGauge: for point-in-time values (jobs in flight) Metrics are automatically correlated with the active trace/span. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…rics Upgrades Sentry .NET SDK from 6.0.0 to 6.1.0-alpha.1 to test the new experimental trace-connected metrics API from PR getsentry/sentry-dotnet#4834. Changes: - Upgrade Sentry SDK to 6.1.0-alpha.1 - Add SentryClientMetrics decorator that emits metrics to Sentry - Enable experimental metrics in all apps (Server, Console, Android) - Use SentryClientMetrics in DI registrations (Core, Server, Console) - Fix duplicate ClientMetrics registration in Core/Startup.cs The new metrics API provides: - AddCounter: for counting events (files, uploads, errors) - RecordDistribution: for value distributions (uploaded bytes) - RecordGauge: for point-in-time values (jobs in flight) Metrics are automatically correlated with the active trace/span. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…rics Upgrades Sentry .NET SDK from 6.0.0 to 6.1.0-alpha.1 to test the new experimental trace-connected metrics API from PR getsentry/sentry-dotnet#4834. Changes: - Upgrade Sentry SDK to 6.1.0-alpha.1 - Add SentryClientMetrics decorator that emits metrics to Sentry - Make ClientMetrics methods virtual for proper polymorphism - Enable experimental metrics in all apps (Server, Console, Android) - Use SentryClientMetrics in DI registrations (Core, Server, Console) - Fix duplicate ClientMetrics registration in Core/Startup.cs The new metrics API provides: - AddCounter: for counting events (files, uploads, errors) - RecordDistribution: for value distributions (uploaded bytes) - RecordGauge: for point-in-time values (jobs in flight) Metrics are automatically correlated with the active trace/span. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
#252) * feat: upgrade Sentry SDK to 6.1.0-alpha.1 and add trace-connected metrics Upgrades Sentry .NET SDK from 6.0.0 to 6.1.0-alpha.1 to test the new experimental trace-connected metrics API from PR getsentry/sentry-dotnet#4834. Changes: - Upgrade Sentry SDK to 6.1.0-alpha.1 - Add SentryClientMetrics decorator that emits metrics to Sentry - Make ClientMetrics methods virtual for proper polymorphism - Enable experimental metrics in all apps (Server, Console, Android) - Use SentryClientMetrics in DI registrations (Core, Server, Console) - Fix duplicate ClientMetrics registration in Core/Startup.cs The new metrics API provides: - AddCounter: for counting events (files, uploads, errors) - RecordDistribution: for value distributions (uploaded bytes) - RecordGauge: for point-in-time values (jobs in flight) Metrics are automatically correlated with the active trace/span. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: add unit tests for SentryClientMetrics Adds tests to verify that: - SentryClientMetrics emits trace_metric items to Sentry - All metric types (Counter, Distribution, Gauge) are emitted - Base class counters are also incremented (dual emission) - Polymorphism works correctly (override vs new) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: use IHub for SentryClientMetrics to improve testability - SentryClientMetrics now accepts IHub in constructor (defaults to HubAdapter.Instance) - Tests use isolated SDK instances with recording transport - Each test initializes/disposes its own SDK for proper isolation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Delete test/SymbolCollector.Core.Tests/SentryClientMetricsTests.cs --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
| foreach (var attribute in attributes) | ||
| { | ||
| _attributes[attribute.Key] = new SentryAttribute(attribute.Value); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing null value check in SetAttributes methods
Medium Severity
The documentation states that null attribute values should be "ignored", and the public SetAttribute<T> method correctly returns early when value is null. However, the internal SetAttributes methods that accept IEnumerable<KeyValuePair<string, object>> and ReadOnlySpan<KeyValuePair<string, object>> don't check if attribute.Value is null before passing it to new SentryAttribute(attribute.Value). This inconsistency means users passing attribute dictionaries with null values get different behavior than documented, and could cause issues depending on how SentryAttribute handles null.
Additional Locations (1)
Provides hierarchical constants for metric units supported by Sentry Relay, organized into Duration, Information, and Fraction categories. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
|
||
| ### BREAKING CHANGES | ||
|
|
||
| - Rename [Trace-connected Metrics](https://docs.sentry.io/product/explore/metrics/) APIs to avoid implying aggregation ([#4834](https://github.com/getsentry/sentry-dotnet/pull/4834)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 🚫 The changelog entry seems to be part of an already released section
## 6.1.0.
Consider moving the entry to the## Unreleasedsection, please.
| /// <param name="value">The value of the metric.</param> | ||
| /// <typeparam name="T">The numeric type of the metric.</typeparam> | ||
| /// <remarks>Supported numeric value types for <typeparamref name="T"/> are <see langword="byte"/>, <see langword="short"/>, <see langword="int"/>, <see langword="long"/>, <see langword="float"/>, and <see langword="double"/>.</remarks> | ||
| public void EmitCounter<T>(string name, T value) where T : struct |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: discuss identifiers of public method groups
See spec in dev docs Metrics > Metrics Module
-Sentry.metrics.count(..)
+SentrySdk.Metrics.EmitCounter(..);
-Sentry.metrics.gauge(..)
+SentrySdk.Metrics.EmitGauge(..);
-Sentry.metrics.distribution(..)
+SentrySdk.Metrics.EmitDistribution(..);countis both a verb and a noungaugeis both a verb and a noundistributionis a noun only
So my thought was to - in particular for Distribution - use a verb as a prefix to form a verb phrase.
And then - for consistency - use a verb as a prefix for all three method groups.
The respective .NET Metrics (and Open Telemetry) are
using System.Diagnostics.Metrics;
static Meter meter = new Meter(name: "my-meter");
static Counter<int> counter = meter.CreateCounter<int>(name: "my-counter");
static Gauge<int> gauge = meter.CreateGauge<int>(name: "my-gauge");
static Histogram<int> histogram = meter.CreateHistogram<int>(name: "my-histogram");
counter.Add(delta: 1);
gauge.Record(value: 2);
histogram.Record(value: 3);The first draft started with
SentrySdk.Metrics.AddCounter
SentrySdk.Metrics.RecordGauge
SentrySdk.Metrics.RecordDistributionalthough with the verbs consistent with .NET/Open-Telemtry, these verbs do not fit, as they may suggest client-side aggregation, rather than sending the metric as-provided to Sentry with query-time aggregation at Sentry.
Then changing to the verb Emit. This verb is also used in the dev docs. The verb Emit is also used in Logs in Sentry for Go.
So the current naming scheme is, almost, Emit{Type}.
However, currently, instead of EmitCount, I decided to use EmitCounter instead, to be closer to the .NET BLC / System.Diagnostics.DiagnosticSource NuGet package.
An argument can be made to instead be consistent with the spec ... or at least more consistent with the spec. So here are some alternatives up for discussion.
// "type": "counter"
SentrySdk.Metrics.Count() // as in spec: "Sentry.metrics.count()"
SentrySdk.Metrics.EmitCount() // as verb phrase
SentrySdk.Metrics.EmitCounter() // Counter instead of Count, for consistency with the actual type name
// "type": "gauge"
SentrySdk.Metrics.Gauge() // as in spec: "Sentry.metrics.gauge()"
SentrySdk.Metrics.EmitGauge() // as verb phrase
// "type": "distribution"
SentrySdk.Metrics.Distribution() // as in spec: "Sentry.metrics.distribution()"
SentrySdk.Metrics.EmitDistribution() // as verb phraseWhat do you prefer?
UPDATE: oh no ... I just realized we had old metrics before ... should we restore (or adapt parts of) that API shape?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we have to worry about the API shape of the previous metrics product we had. I think the naming as you have it is fine.
| /// <param name="value">The value of the metric.</param> | ||
| /// <typeparam name="T">The numeric type of the metric.</typeparam> | ||
| /// <remarks>Supported numeric value types for <typeparamref name="T"/> are <see langword="byte"/>, <see langword="short"/>, <see langword="int"/>, <see langword="long"/>, <see langword="float"/>, and <see langword="double"/>.</remarks> | ||
| public void EmitGauge<T>(string name, T value) where T : struct |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Overloads and Parameters of Method Groups
The SDK spec Metrics > Metrics Module suggests these parameters for the three methods Sentry.metrics.count, Sentry.metrics.gauge, and Sentry.metrics.distribution:
name, String, requiredvalue, Number, requiredoptions, Object, optionalunit, String, optional, only for distribution and gauge, should not be anenumattributes, Object, optionalscope, Scope, optional
name
I decided to have several overloads per method group, rather than a single method per type. This is also what the Meter type / Instrument types of the net6.0+ BCL APIs do.
value
With .NET being typed, I decided to make the APIs generic, as this is also what the .NET BCL / Open Telemetry / System.Diagnostics.DiagnosticSource NuGet package does.
This supports the numeric types byte, short, int, long, float, double. But don't support decimal (.NET Instruments do support it) as it's 128-bit, and Sentry only supports 64-bit signed integral types and 64-bit floating-point numbers.
options
Instead of an Options-Bag, I went with a method group and respective overloads, as again, they are similar to the System.Diagnostics.Metrics APIs.
However, if we'd like, we could do a struct-based Options-bag, per type (struct CounterOptions, struct GaugeOptions, and struct DistributionOptions).
unit
The unit overload only exists in the Gauge and Distribution method groups.
Typed to string, rather than Sentry.MeasurementUnit, as the SDK spec suggests to:
SDKs must not restrict what can be sent in (e.g. by using an enum) but should offer constants or similar that help customers send in units we support.
We could, however, offer an overload that takes Sentry.MeasurementUnit instead of a string.
Or we could add this overload later on.
The motivation of having a string rather than an enum is that Sentry may start supporting more Units at some time, and SDK users should be able to pass them once supported without upgrading the SDK.
Currently, a "custom" unit is not supported, and Relay will either discard or set the Unit to None if an unsupported unit is passed by the user --- the metric itself will not be discarded in that case.
attributes
Offer both an IEnumerable<KeyValuePair<string, object>>, and a ReadOnlySpan<KeyValuePair<string, object>> overload, again, very similar to "tags" of System.Diagnostics.Metrics.
scope
Just a nullable Scope.
Currently not available, but we could now - or later - add an overload for passing a Scope without a unit:
..(string name, T value, Scope? scope)
What do you think?
UPDATE: oh no ... I just realized we had old metrics before ... should we restore (or adapt parts of) that API shape?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of an Options-Bag, I went with a method group and respective overloads, as again, they are similar to the System.Diagnostics.Metrics APIs.
Using an Options type would mean less overloads to maintain... and if the options type was sealed then we could add members to it later. It's probably easier and less ceremony for SDK users to use various overloads with the options they care about.
It really depends how many dimensions the options type exposes... eventually (like with the SentryOptions) the only practical way to set these is via some kind of callback/delegate.
I think it's fine as is for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently not available, but we could now - or later - add an overload for passing a Scope without a unit:
..(string name, T value, Scope? scope)
What was the rational for omitting this overload currently?
| /// Supported numeric value types for <typeparamref name="T"/> are <see langword="byte"/>, <see langword="short"/>, <see langword="int"/>, <see langword="long"/>, <see langword="float"/>, and <see langword="double"/>. | ||
| /// </remarks> | ||
| /// <seealso href="https://develop.sentry.dev/sdk/telemetry/metrics/"/> | ||
| public void SetBeforeSendMetric<T>(Func<SentryMetric<T>, SentryMetric<T>?> beforeSendMetric) where T : struct |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Design
With the Metric type being generic, supporting byte, short, int, long, float, and double, this API is also quite similar to the MeterListener of System.Diagnostics.Metrics.
Where you can set one callback per metric type via SetMeasurementEventCallback.
See Collect metrics.
What do you think?
UPDATE: oh no ... I just realized we had old metrics before ... should we restore (or adapt parts of) that API shape?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a pain since the SDK user may want a callback like:
if (metric.attributes.contains(a => a.name == "foo"))
{
return null;
}
But they'd need to define that function once for each numeric type our metrics support and if we add support for new numeric types their code might break. It doesn't feel like a great API design.
An alternative option (not saying it's better, but just to consider) would be to have the parameter to the callback be a flattening of the various generic types... e.g.
SentryMetricArgType
{
SentryMetricType type;
string name;
object value;
string? unit;
IEnumerable<KeyValuePair<string, object>> attributes;
Scope? scope;
}
That might be a bit messy: impedance mismatch, mappings, type safety issues etc. but it might be better than the alternative.
A more complex variant would be to pass something like a SentryMetricTypeAccessor that mitigated some of the type safety issues by exposing properties of the metric that the SDK user might want to read via getter methods and exposed properties the SDK user might want to modify (if any) via setter methods... so they could do something like:
options.Metrics.SetBeforeSend(accessor => {
if (accessor.GetMetricValue() is double && accessor.Attributes.Contains("foo"))
{
return null;
}
});
Probably have to play around with different options to see how the implementation and SDK usage feels in practice. I could see what we have in this PR at the moment being pretty onerous for SDK users though, so I think we need to find a better option.
What solution have the other strongly typed SDKs (like Java) opted for here?
| /// <remarks> | ||
| /// Experimental features are subject to binary, source and behavioral breaking changes in future updates. | ||
| /// </remarks> | ||
| public ExperimentalSentryOptions Experimental { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Experimental vs Stable.
Do we want to publish it as experimental first ... or stable directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's quite a lot of code so we could publish it as experimental for one point release... try to get some feedback from SDK customers using it in anger. There may be strong opinions some of the design decisions (e.g. method groups vs. options types/callbacks) so good to have the flexibility to revisit some of those decisions for a wee while if need be.
Otherwise it's more of a commercial question (is Sentry sure we're going to GA this metrics product?). I assume we captured enough learnings from the first metrics product to be pretty confident of the design and cost structure this time around. @dingsdax ?
|
|
||
| ### BREAKING CHANGES | ||
|
|
||
| - Rename [Trace-connected Metrics](https://docs.sentry.io/product/explore/metrics/) APIs to avoid implying aggregation ([#4834](https://github.com/getsentry/sentry-dotnet/pull/4834)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably want to tidy up all of these change log entries when we include metrics in a non-preview release... For people who haven't been using the pre-releases we could just say:
- Add _experimental_ support for [Sentry trace-connected Metrics](https://docs.sentry.io/product/explore/metrics/) ([#4834](https://github.com/getsentry/sentry-dotnet/pull/4834))
|
|
||
| <PropertyGroup> | ||
| <VersionPrefix>6.0.0</VersionPrefix> | ||
| <VersionPrefix>6.1.0-alpha.2</VersionPrefix> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert before merging into the main branch... to whatever the version in the main branch is. This gets set by the release process automatically.
| <VersionPrefix>6.1.0-alpha.2</VersionPrefix> | |
| <VersionPrefix>6.0.0</VersionPrefix> |
|
|
||
| <!-- Ignore our own diagnostic ids - these are meant to be external warnings only --> | ||
| <NoWarn>$(NoWarn);SENTRY0001</NoWarn> | ||
| <NoWarn>$(NoWarn);SENTRYTRACECONNECTEDMETRICS</NoWarn> <!--https://github.com/getsentry/sentry-dotnet/discussions/4838--> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a specific warning for trace connected metrics? In the past we've simply had SENTRY0001 to ensure SDK users deliberately opt into experimental features (like this one) and don't use these by accident in production.
|
|
||
| namespace Sentry.Testing; | ||
|
|
||
| public sealed class InMemorySentryTraceMetrics : SentryTraceMetrics |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does this get used?
Implementation of Trace-connected Metrics
See https://develop.sentry.dev/sdk/telemetry/metrics/
This changeset contains the initial implementation of Trace-connected Metrics for Sentry.
Changes
SentryTraceMetricsand derived), which follow the same pattern and structure from Structured LogsSystem.Diagnostics.DiagnosticSourcepackagePlease see the discussion comments below to discuss these design decisions.
Related changesets
Related issues and discussions
Follow-ups
Enable public beta
dotnetplatform