|
| 1 | +# Metrics — Sentry .NET SDK |
| 2 | + |
| 3 | +> Minimum SDK: `Sentry` ≥ 6.1.0 |
| 4 | +> Roslyn Analyzer (`SENTRY1001`): `Sentry.Compiler.Extensions` (ships with `Sentry` NuGet) |
| 5 | +
|
| 6 | +--- |
| 7 | + |
| 8 | +## Overview |
| 9 | + |
| 10 | +Sentry Trace-connected Metrics lets you emit counters, gauges, and distributions that are automatically linked to your Sentry traces. Metrics are batched and flushed periodically (every 5 seconds or 100 items). |
| 11 | + |
| 12 | +Three metric types: |
| 13 | + |
| 14 | +| Type | Method | Purpose | |
| 15 | +|------|--------|---------| |
| 16 | +| Counter | `EmitCounter` | Increment a value (e.g. request count, items processed) | |
| 17 | +| Gauge | `EmitGauge` | Track a current value (e.g. queue depth, active connections) | |
| 18 | +| Distribution | `EmitDistribution` | Record a measured value for statistical analysis (e.g. response time, payload size) | |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## Enabling Metrics |
| 23 | + |
| 24 | +Metrics are enabled via `SentryOptions.EnableMetrics`: |
| 25 | + |
| 26 | +```csharp |
| 27 | +SentrySdk.Init(options => |
| 28 | +{ |
| 29 | + options.Dsn = "___YOUR_DSN___"; |
| 30 | + options.EnableMetrics = true; |
| 31 | +}); |
| 32 | +``` |
| 33 | + |
| 34 | +> **Without `EnableMetrics = true`**, all `EmitCounter` / `EmitGauge` / `EmitDistribution` calls are no-ops. |
| 35 | +
|
| 36 | +--- |
| 37 | + |
| 38 | +## Emitting Metrics |
| 39 | + |
| 40 | +Access the metrics API via `SentrySdk.Metrics` or `hub.Metrics`: |
| 41 | + |
| 42 | +### Counter |
| 43 | + |
| 44 | +```csharp |
| 45 | +// Simple counter — increment by 1 |
| 46 | +SentrySdk.Metrics.EmitCounter("orders.completed", 1); |
| 47 | + |
| 48 | +// Counter with attributes and scope |
| 49 | +SentrySdk.Metrics.EmitCounter("orders.completed", 1, |
| 50 | + new Dictionary<string, object> { ["region"] = "eu-west" }, |
| 51 | + scope); |
| 52 | +``` |
| 53 | + |
| 54 | +### Gauge |
| 55 | + |
| 56 | +```csharp |
| 57 | +// Simple gauge |
| 58 | +SentrySdk.Metrics.EmitGauge("queue.depth", 42); |
| 59 | + |
| 60 | +// Gauge with unit |
| 61 | +SentrySdk.Metrics.EmitGauge("cpu.usage", 0.85, MeasurementUnit.Fraction.Ratio); |
| 62 | +``` |
| 63 | + |
| 64 | +### Distribution |
| 65 | + |
| 66 | +```csharp |
| 67 | +// Simple distribution |
| 68 | +SentrySdk.Metrics.EmitDistribution("response.time", 120.5); |
| 69 | + |
| 70 | +// Distribution with unit and attributes |
| 71 | +SentrySdk.Metrics.EmitDistribution("payload.size", 4096L, |
| 72 | + MeasurementUnit.Information.Byte, |
| 73 | + new Dictionary<string, object> { ["endpoint"] = "/api/orders" }, |
| 74 | + scope); |
| 75 | +``` |
| 76 | + |
| 77 | +--- |
| 78 | + |
| 79 | +## Supported Numeric Types |
| 80 | + |
| 81 | +The metrics API accepts a generic `T` constrained to `struct`, but only the following numeric types are supported at runtime: |
| 82 | + |
| 83 | +| Type | C# keyword | Supported | |
| 84 | +|------|-----------|-----------| |
| 85 | +| `System.Byte` | `byte` | ✅ Yes | |
| 86 | +| `System.Int16` | `short` | ✅ Yes | |
| 87 | +| `System.Int32` | `int` | ✅ Yes | |
| 88 | +| `System.Int64` | `long` | ✅ Yes | |
| 89 | +| `System.Single` | `float` | ✅ Yes | |
| 90 | +| `System.Double` | `double` | ✅ Yes | |
| 91 | +| `System.UInt32` | `uint` | ❌ No | |
| 92 | +| `System.UInt64` | `ulong` | ❌ No | |
| 93 | +| `System.Decimal` | `decimal` | ❌ No | |
| 94 | +| `System.Int128` | `Int128` | ❌ No | |
| 95 | + |
| 96 | +Unsupported types are silently dropped at runtime (no-op with a debug diagnostic log message). The Roslyn analyzer `SENTRY1001` catches these at compile time instead. |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## `SENTRY1001` — Roslyn Diagnostic Analyzer |
| 101 | + |
| 102 | +The SDK ships a compile-time Roslyn analyzer that reports a **warning** when a metrics API is called with an unsupported numeric type. |
| 103 | + |
| 104 | +**Diagnostic ID:** `SENTRY1001` |
| 105 | +**Category:** `Sentry` |
| 106 | +**Severity:** Warning |
| 107 | +**Message:** `{type} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.` |
| 108 | + |
| 109 | +**Triggers on:** |
| 110 | +- `SentryMetricEmitter.EmitCounter<T>()` with unsupported `T` |
| 111 | +- `SentryMetricEmitter.EmitGauge<T>()` with unsupported `T` |
| 112 | +- `SentryMetricEmitter.EmitDistribution<T>()` with unsupported `T` |
| 113 | +- `SentryMetric.TryGetValue<T>()` with unsupported `T` |
| 114 | + |
| 115 | +```csharp |
| 116 | +// ✅ No warning — supported types |
| 117 | +SentrySdk.Metrics.EmitCounter("my.counter", 1); // int → OK |
| 118 | +SentrySdk.Metrics.EmitCounter("my.counter", 1L); // long → OK |
| 119 | +SentrySdk.Metrics.EmitCounter("my.counter", 1.5f); // float → OK |
| 120 | +SentrySdk.Metrics.EmitCounter("my.counter", 1.5); // double → OK |
| 121 | +
|
| 122 | +// ⚠️ SENTRY1001 — unsupported types |
| 123 | +SentrySdk.Metrics.EmitCounter("my.counter", 1m); // decimal |
| 124 | +SentrySdk.Metrics.EmitCounter("my.counter", (ulong)100); // ulong |
| 125 | +SentrySdk.Metrics.EmitCounter("my.counter", (uint)1); // uint |
| 126 | +``` |
| 127 | + |
| 128 | +**To suppress** (not recommended): Add `#pragma warning disable SENTRY1001` or `[SuppressMessage]`. However, the metric will still be silently dropped at runtime. |
| 129 | + |
| 130 | +--- |
| 131 | + |
| 132 | +## `SetBeforeSendMetric` Callback |
| 133 | + |
| 134 | +Filter or modify metrics before they are sent to Sentry. Return `null` to drop a metric. |
| 135 | + |
| 136 | +```csharp |
| 137 | +SentrySdk.Init(options => |
| 138 | +{ |
| 139 | + options.Dsn = "___YOUR_DSN___"; |
| 140 | + options.EnableMetrics = true; |
| 141 | + options.SetBeforeSendMetric(static (SentryMetric metric) => |
| 142 | + { |
| 143 | + // Drop metrics with negative values |
| 144 | + if (metric.TryGetValue<double>(out var value) && value < 0) |
| 145 | + { |
| 146 | + return null; |
| 147 | + } |
| 148 | + |
| 149 | + return metric; |
| 150 | + }); |
| 151 | +}); |
| 152 | +``` |
| 153 | + |
| 154 | +**Signature:** `void SetBeforeSendMetric(Func<SentryMetric, SentryMetric?> callback)` |
| 155 | + |
| 156 | +--- |
| 157 | + |
| 158 | +## `SentryMetric.TryGetValue<T>` |
| 159 | + |
| 160 | +Extract the numeric value from a `SentryMetric` with a type check. Returns `false` if the metric's value type does not match `T`. The same supported-type rules apply — using an unsupported type triggers `SENTRY1001`. |
| 161 | + |
| 162 | +```csharp |
| 163 | +options.SetBeforeSendMetric(static (SentryMetric metric) => |
| 164 | +{ |
| 165 | + // ✅ Supported — double |
| 166 | + if (metric.TryGetValue<double>(out var doubleValue)) |
| 167 | + { |
| 168 | + Console.WriteLine($"Metric value: {doubleValue}"); |
| 169 | + } |
| 170 | + |
| 171 | + // ✅ Supported — long |
| 172 | + if (metric.TryGetValue<long>(out var longValue) && longValue > 1000) |
| 173 | + { |
| 174 | + return null; // drop high-value metrics |
| 175 | + } |
| 176 | + |
| 177 | + return metric; |
| 178 | +}); |
| 179 | +``` |
| 180 | + |
| 181 | +--- |
| 182 | + |
| 183 | +## `SentryMetric` Properties |
| 184 | + |
| 185 | +| Property | Type | Description | |
| 186 | +|----------|------|-------------| |
| 187 | +| `Timestamp` | `DateTimeOffset` | When the metric was recorded | |
| 188 | +| `TraceId` | `SentryId` | Trace ID linking metric to a trace | |
| 189 | +| `SpanId` | `SpanId?` | Span that was active when metric was emitted | |
| 190 | +| `Type` | `SentryMetricType` | `Counter`, `Gauge`, or `Distribution` | |
| 191 | +| `Name` | `string` | Hierarchical name (e.g. `api.response_time`) | |
| 192 | +| `Unit` | `string?` | Unit of measurement (only for Gauge/Distribution) | |
| 193 | + |
| 194 | +--- |
| 195 | + |
| 196 | +## Config Options |
| 197 | + |
| 198 | +| Option | Type | Default | Notes | |
| 199 | +|--------|------|---------|-------| |
| 200 | +| `EnableMetrics` | `bool` | `false` | Must be `true` to emit metrics | |
| 201 | +| `SetBeforeSendMetric` | `Func<SentryMetric, SentryMetric?>` | — | Filter/modify metrics before send; return `null` to drop | |
| 202 | + |
| 203 | +--- |
| 204 | + |
| 205 | +## Troubleshooting |
| 206 | + |
| 207 | +| Issue | Cause | Solution | |
| 208 | +|-------|-------|----------| |
| 209 | +| Metrics not appearing in Sentry | `EnableMetrics` not set | Set `options.EnableMetrics = true` in `SentrySdk.Init()` | |
| 210 | +| `SENTRY1001` compiler warning | Unsupported numeric type | Use `byte`, `short`, `int`, `long`, `float`, or `double` instead | |
| 211 | +| Metric emitted but silently dropped | Unsupported type at runtime (no analyzer) | Ensure the `Sentry.Compiler.Extensions` analyzer is loaded; check build output for `SENTRY1001` | |
| 212 | +| `SetBeforeSendMetric` drops all metrics | Callback returns `null` unconditionally | Verify your filter logic; return `metric` for metrics you want to keep | |
| 213 | +| `TryGetValue<T>` returns `false` | Type mismatch between emitted type and queried type | Use the same type that was passed to `EmitCounter`/`EmitGauge`/`EmitDistribution` | |
0 commit comments