Skip to content

Commit cc9ec24

Browse files
fix: telemetry dropped by user callbacks and by validation is now counted in client reports (#5607)
* fix: exceptions thrown from BeforeBreadcrumb, log filters and BeforeScreenshotCapture no longer reach the application Completes the callback-isolation work item from #5535 that #5545 started for TracesSampler. Each callback now runs inside a recovery boundary that logs an error naming the callback and applies the spec's fallback: - BeforeBreadcrumb (Scope.AddBreadcrumb, Android JNI, Cocoa) drops the breadcrumb - ILogEntryFilter.Filter treats a failing filter as "did not filter" - SetBeforeScreenshotCapture skips the screenshot and keeps the event Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ref: name the platform in native BeforeBreadcrumb failure logs, add Android device tests Addresses review feedback on #5606: distinguish native callback failures from managed ones in the diagnostic log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: record client reports for items dropped by user callbacks Work item 2 of #5535. Items lost to a user callback — whether it returned null or threw — were disappearing without a client report, and a throwing event or transaction processor skipped the report entirely by unwinding to the Hub catch-all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: record client reports for logs and metrics dropped by validation A log whose template doesn't match its arguments, and a metric with an unsupported value type or an empty name, were dropped with a diagnostic log and no client report. Records the spec's `invalid` reason, which the SDK did not previously carry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: log when BeforeSendLog or BeforeSendMetric drops an item Every other deliberate drop by a user callback logs at info level naming the callback; logs and metrics were the exception. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: a throwing log entry filter now drops the entry instead of letting it through Review feedback on #5606. Failing open meant a filter written to exclude entries would, once broken, start sending them. Before this PR the exception reached the app and nothing was sent, so dropping keeps that outcome while removing the crash, and matches the filter rule applied to BeforeSend in #5610. Also aligns the internal screenshot callback names with the public SetBeforeScreenshotCapture, and fixes a doc sample naming a method that no longer exists. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ref: evaluate log entry filters once per log call instead of twice ShouldCaptureEvent and ShouldAddBreadcrumb each ran the whole filter chain, so a user filter was invoked twice for every entry that cleared both thresholds — and, since the previous commit, logged its failure twice. The two predicates now test only their level threshold, and the shared guard runs once in Log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: report callback failures with the callback_error discard reason Follows the hooks spec revision in getsentry/sentry-docs#19189 (b53c3009): a throwing callback must be distinguishable from an explicit drop, so every item a failure drops is recorded as callback_error with the item's category. Null returns keep their existing reasons. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: rebuild the pull request merge ref GitHub stopped rebuilding refs/pull/5607/merge, so no pull_request workflow could be created and the PR reported a conflict that git shows does not exist. A new head commit forces the ref to be recomputed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 051ee2c commit cc9ec24

10 files changed

Lines changed: 125 additions & 8 deletions

‎src/Sentry/Internal/DefaultSentryMetricEmitter.cs‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ private protected override void CaptureMetric<T>(SentryMetricType type, string n
2727
{
2828
if (!SentryMetric.IsSupported(typeof(T)))
2929
{
30+
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
3031
_options.DiagnosticLogger?.LogWarning("{0} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.", typeof(T));
3132
return;
3233
}
3334

3435
if (string.IsNullOrEmpty(name))
3536
{
37+
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
3638
_options.DiagnosticLogger?.LogWarning("Name of metrics cannot be null or empty. Metric-Type: {0}; Value-Type: {1}", type.ToString(), typeof(T));
3739
return;
3840
}
@@ -46,12 +48,14 @@ private protected override void CaptureMetric<T>(SentryMetricType type, string n
4648
{
4749
if (!SentryMetric.IsSupported(typeof(T)))
4850
{
51+
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
4952
_options.DiagnosticLogger?.LogWarning("{0} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.", typeof(T));
5053
return;
5154
}
5255

5356
if (string.IsNullOrEmpty(name))
5457
{
58+
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
5559
_options.DiagnosticLogger?.LogWarning("Name of metrics cannot be null or empty. Metric-Type: {0}; Value-Type: {1}", type.ToString(), typeof(T));
5660
return;
5761
}
@@ -76,15 +80,20 @@ private protected override void CaptureMetric<T>(SentryMetric<T> metric) where T
7680
}
7781
catch (Exception e)
7882
{
83+
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.TraceMetric);
7984
_options.DiagnosticLogger?.LogError(e, "The BeforeSendMetric callback threw an exception. The Metric will be dropped.");
8085
return;
8186
}
8287
}
8388

84-
if (configuredMetric is not null)
89+
if (configuredMetric is null)
8590
{
86-
_batchProcessor.Enqueue(configuredMetric);
91+
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.BeforeSend, DataCategory.TraceMetric);
92+
_options.DiagnosticLogger?.LogInfo("Metric dropped by BeforeSendMetric callback.");
93+
return;
8794
}
95+
96+
_batchProcessor.Enqueue(configuredMetric);
8897
}
8998

9099
/// <inheritdoc />

‎src/Sentry/Internal/DefaultSentryStructuredLogger.cs‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ private protected override void CaptureLog(SentryLogLevel level, string template
3939
}
4040
catch (FormatException e)
4141
{
42+
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.LogItem);
4243
_options.DiagnosticLogger?.LogError(e, "Template string does not match the provided argument. The Log will be dropped.");
4344
return;
4445
}
@@ -70,6 +71,7 @@ private protected override void CaptureLog(SentryLogLevel level, string template
7071
}
7172
catch (Exception e)
7273
{
74+
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.LogItem);
7375
_options.DiagnosticLogger?.LogError(e, "The configureLog callback threw an exception. The Log will be dropped.");
7476
return;
7577
}
@@ -93,15 +95,20 @@ protected internal override void CaptureLog(SentryLog log)
9395
}
9496
catch (Exception e)
9597
{
98+
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.LogItem);
9699
_options.DiagnosticLogger?.LogError(e, "The BeforeSendLog callback threw an exception. The Log will be dropped.");
97100
return;
98101
}
99102
}
100103

101-
if (configuredLog is not null)
104+
if (configuredLog is null)
102105
{
103-
_batchProcessor.Enqueue(configuredLog);
106+
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.BeforeSend, DataCategory.LogItem);
107+
_options.DiagnosticLogger?.LogInfo("Log dropped by BeforeSendLog callback.");
108+
return;
104109
}
110+
111+
_batchProcessor.Enqueue(configuredLog);
105112
}
106113

107114
/// <inheritdoc />

‎src/Sentry/Internal/DiscardReason.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ namespace Sentry.Internal;
44
{
55
// See https://develop.sentry.dev/sdk/client-reports/ for list
66
public static DiscardReason BeforeSend = new("before_send");
7+
public static DiscardReason CallbackError = new("callback_error");
78
public static DiscardReason BufferOverflow = new("buffer_overflow");
89
public static DiscardReason CacheOverflow = new("cache_overflow");
910
public static DiscardReason EventProcessor = new("event_processor");
11+
public static DiscardReason Invalid = new("invalid");
1012
public static DiscardReason NetworkError = new("network_error");
1113
public static DiscardReason QueueOverflow = new("queue_overflow");
1214
public static DiscardReason SendError = new("send_error");

‎src/Sentry/Internal/SentryEventHelper.cs‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,17 @@ internal static class SentryEventHelper
1717

1818
foreach (var processor in processors)
1919
{
20-
processedEvent = processor.DoProcessEvent(processedEvent, effectiveHint);
20+
try
21+
{
22+
processedEvent = processor.DoProcessEvent(processedEvent, effectiveHint);
23+
}
24+
catch (Exception e)
25+
{
26+
options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, dataCategory);
27+
options.LogError(e, "Event processor {0} threw an exception. The event will be dropped.", processor.GetType().Name);
28+
return null;
29+
}
30+
2131
if (processedEvent == null)
2232
{
2333
options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.EventProcessor, dataCategory);
@@ -94,7 +104,7 @@ internal static class SentryEventHelper
94104
}
95105
catch (Exception e)
96106
{
97-
options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.BeforeSend, DataCategory.Feedback);
107+
options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.Feedback);
98108
options.LogError(e, "The BeforeSendFeedback callback threw an exception. The feedback will be dropped.");
99109
return null;
100110
}

‎src/Sentry/SentryClient.cs‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,18 @@ public void CaptureTransaction(SentryTransaction transaction, Scope? scope, Sent
212212
var processedTransaction = transaction;
213213
foreach (var processor in scope.GetAllTransactionProcessors())
214214
{
215-
processedTransaction = processor.DoProcessTransaction(processedTransaction, hint);
215+
try
216+
{
217+
processedTransaction = processor.DoProcessTransaction(processedTransaction, hint);
218+
}
219+
catch (Exception e)
220+
{
221+
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.Transaction);
222+
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.Span, spanCount);
223+
_options.LogError(e, "Transaction processor {0} threw an exception. The transaction will be dropped.", processor.GetType().Name);
224+
return;
225+
}
226+
216227
if (processedTransaction == null) // Rejected transaction
217228
{
218229
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.EventProcessor, DataCategory.Transaction);

‎test/Sentry.Tests/SentryClientTests.cs‎

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,23 @@ public void CaptureEvent_EventProcessor_RejectEvent_RecordsDiscard()
387387
.RecordDiscardedEvent(DiscardReason.EventProcessor, DataCategory.Error);
388388
}
389389

390+
[Fact]
391+
public void CaptureEvent_EventProcessorThrows_DropsEventAndRecordsDiscard()
392+
{
393+
var processor = Substitute.For<ISentryEventProcessor>();
394+
processor.Process(Arg.Any<SentryEvent>()).Throws(new InvalidOperationException());
395+
396+
_fixture.SentryOptions.AddEventProcessor(processor);
397+
398+
var sut = _fixture.GetSut();
399+
var id = sut.CaptureEvent(new SentryEvent());
400+
401+
id.Should().Be(SentryId.Empty);
402+
_fixture.BackgroundWorker.DidNotReceive().EnqueueEnvelope(Arg.Any<Envelope>());
403+
_fixture.ClientReportRecorder.Received(1)
404+
.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.Error);
405+
}
406+
390407
[Fact]
391408
public void CaptureEvent_ExceptionFilter_RecordsDiscard()
392409
{
@@ -1126,7 +1143,7 @@ public void CaptureFeedback_BeforeSendFeedbackThrows_FeedbackDropped()
11261143
result.Should().Be(CaptureFeedbackResult.DroppedByBeforeSendFeedback);
11271144
id.Should().Be(SentryId.Empty);
11281145
_ = sut.Worker.DidNotReceive().EnqueueEnvelope(Arg.Any<Envelope>());
1129-
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.BeforeSend, DataCategory.Feedback);
1146+
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.Feedback);
11301147
}
11311148

11321149
[Fact]
@@ -1731,6 +1748,31 @@ public void CaptureTransaction_TransactionProcessorRejectsEvent_RecordDiscardedE
17311748
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(reason, DataCategory.Span, expectedDroppedSpanCount);
17321749
}
17331750

1751+
[Fact]
1752+
public void CaptureTransaction_TransactionProcessorThrows_DropsTransactionAndRecordsDiscard()
1753+
{
1754+
// Arrange
1755+
var processor = Substitute.For<ISentryTransactionProcessorWithHint>();
1756+
processor.Process(Arg.Any<SentryTransaction>(), Arg.Any<SentryHint>()).Throws(new InvalidOperationException());
1757+
_fixture.SentryOptions.AddTransactionProcessor(processor);
1758+
1759+
var hub = Substitute.For<IHub>();
1760+
var transaction = new TransactionTracer(hub, "test name", "test operation");
1761+
transaction.StartChild("span1");
1762+
transaction.StartChild("span2");
1763+
transaction.EndTimestamp = DateTimeOffset.Now; // finished
1764+
1765+
// Act
1766+
_fixture.GetSut().CaptureTransaction(new SentryTransaction(transaction));
1767+
1768+
// Assert
1769+
_fixture.BackgroundWorker.DidNotReceive().EnqueueEnvelope(Arg.Any<Envelope>());
1770+
var reason = DiscardReason.CallbackError;
1771+
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(reason, DataCategory.Transaction);
1772+
var expectedDroppedSpanCount = transaction.Spans.Count + 1;
1773+
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(reason, DataCategory.Span, expectedDroppedSpanCount);
1774+
}
1775+
17341776
[Fact]
17351777
public void CaptureTransaction_BeforeSendTransaction_GetsHint()
17361778
{

‎test/Sentry.Tests/SentryMetricEmitterTests.Types.cs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ public void Emit_Decimal_DoesNotCaptureEnvelope(SentryMetricType type)
163163
var entry = _fixture.DiagnosticLogger.Dequeue();
164164
entry.Level.Should().Be(SentryLevel.Warning);
165165
entry.Message.Should().Be("{0} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.");
166+
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
166167
entry.Exception.Should().BeNull();
167168
entry.Args.Should().BeEquivalentTo([typeof(decimal)]);
168169
}
@@ -183,6 +184,7 @@ public void Emit_Half_DoesNotCaptureEnvelope(SentryMetricType type)
183184
var entry = _fixture.DiagnosticLogger.Dequeue();
184185
entry.Level.Should().Be(SentryLevel.Warning);
185186
entry.Message.Should().Be("{0} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.");
187+
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
186188
entry.Exception.Should().BeNull();
187189
entry.Args.Should().BeEquivalentTo([typeof(Half)]);
188190
}
@@ -203,6 +205,7 @@ public void Emit_Enum_DoesNotCaptureEnvelope(SentryMetricType type)
203205
var entry = _fixture.DiagnosticLogger.Dequeue();
204206
entry.Level.Should().Be(SentryLevel.Warning);
205207
entry.Message.Should().Be("{0} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.");
208+
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
206209
entry.Exception.Should().BeNull();
207210
entry.Args.Should().BeEquivalentTo([typeof(StringComparison)]);
208211
}
@@ -222,6 +225,7 @@ public void Emit_Name_Null_DoesNotCaptureEnvelope(SentryMetricType type, string
222225
var entry = _fixture.DiagnosticLogger.Dequeue();
223226
entry.Level.Should().Be(SentryLevel.Warning);
224227
entry.Message.Should().Be("Name of metrics cannot be null or empty. Metric-Type: {0}; Value-Type: {1}");
228+
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
225229
entry.Exception.Should().BeNull();
226230
entry.Args.Should().BeEquivalentTo<object>([arg0, arg1]);
227231
}
@@ -241,6 +245,7 @@ public void Emit_Name_Empty_DoesNotCaptureEnvelope(SentryMetricType type, string
241245
var entry = _fixture.DiagnosticLogger.Dequeue();
242246
entry.Level.Should().Be(SentryLevel.Warning);
243247
entry.Message.Should().Be("Name of metrics cannot be null or empty. Metric-Type: {0}; Value-Type: {1}");
248+
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
244249
entry.Exception.Should().BeNull();
245250
entry.Args.Should().BeEquivalentTo<object>([arg0, arg1]);
246251
}

‎test/Sentry.Tests/SentryMetricEmitterTests.Values.cs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ private void AssertEmittedUnit(MeasurementUnit unit, string expected)
131131

132132
captured.Should().NotBeNull();
133133
captured.Unit.Should().Be(expected);
134+
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
134135
}
135136

136137
[Fact]
@@ -148,6 +149,7 @@ public void Emit_Unit_MeasurementUnit_None()
148149

149150
captured.Should().NotBeNull();
150151
captured.Unit.Should().Be("none");
152+
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
151153
}
152154

153155
[Fact]
@@ -165,6 +167,7 @@ public void Emit_Unit_MeasurementUnit_Custom()
165167

166168
captured.Should().NotBeNull();
167169
captured.Unit.Should().Be("custom_unit");
170+
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
168171
}
169172

170173
[Fact]
@@ -182,6 +185,7 @@ public void Emit_Unit_MeasurementUnit_Empty()
182185

183186
captured.Should().NotBeNull();
184187
captured.Unit.Should().BeEmpty();
188+
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
185189
}
186190

187191
[Fact]
@@ -199,6 +203,7 @@ public void Emit_Unit_MeasurementUnit_Null()
199203

200204
captured.Should().NotBeNull();
201205
captured.Unit.Should().BeNull();
206+
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
202207
}
203208

204209
[Fact]
@@ -216,6 +221,7 @@ public void Emit_Unit_MeasurementUnit_Default()
216221

217222
captured.Should().NotBeNull();
218223
captured.Unit.Should().BeNull();
224+
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
219225
}
220226

221227
[Fact]
@@ -234,6 +240,7 @@ public void Emit_Unit_String_Custom()
234240

235241
captured.Should().NotBeNull();
236242
captured.Unit.Should().Be("custom_unit");
243+
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
237244
}
238245

239246
[Fact]
@@ -252,6 +259,7 @@ public void Emit_Unit_String_Empty()
252259

253260
captured.Should().NotBeNull();
254261
captured.Unit.Should().BeEmpty();
262+
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
255263
}
256264

257265
[Fact]
@@ -270,6 +278,7 @@ public void Emit_Unit_String_Null()
270278

271279
captured.Should().NotBeNull();
272280
captured.Unit.Should().BeNull();
281+
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
273282
}
274283

275284
[SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance", Justification = "The generic SentryMetric type is internal. Testing via the public abstract base type.")]

‎test/Sentry.Tests/SentryMetricEmitterTests.cs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ public Fixture()
1313
{
1414
DiagnosticLogger = new InMemoryDiagnosticLogger();
1515
Hub = Substitute.For<IHub>();
16+
ClientReportRecorder = Substitute.For<IClientReportRecorder>();
1617
Options = new SentryOptions
1718
{
1819
Debug = true,
1920
DiagnosticLogger = DiagnosticLogger,
21+
ClientReportRecorder = ClientReportRecorder,
2022
};
2123
Clock = new MockClock(new DateTimeOffset(2025, 04, 22, 14, 51, 00, 789, TimeSpan.FromHours(2)));
2224
BatchSize = 2;
@@ -39,6 +41,7 @@ public Fixture()
3941

4042
public InMemoryDiagnosticLogger DiagnosticLogger { get; }
4143
public IHub Hub { get; }
44+
public IClientReportRecorder ClientReportRecorder { get; }
4245
public SentryOptions Options { get; }
4346
public ISystemClock Clock { get; }
4447
public int BatchSize { get; set; }
@@ -137,6 +140,12 @@ public void Emit_WhenBeforeSendMetricReturnsNull_DoesNotCaptureEnvelope()
137140

138141
_fixture.Hub.Received(0).CaptureEnvelope(Arg.Any<Envelope>());
139142
invocations.Should().Be(1);
143+
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.BeforeSend, DataCategory.TraceMetric);
144+
var entry = _fixture.DiagnosticLogger.Dequeue();
145+
entry.Level.Should().Be(SentryLevel.Info);
146+
entry.Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
147+
entry.Exception.Should().BeNull();
148+
entry.Args.Should().BeEmpty();
140149
}
141150

142151
[Fact]
@@ -153,6 +162,7 @@ public void Emit_InvalidBeforeSendMetric_DoesNotCaptureEnvelope()
153162
entry.Message.Should().Be("The BeforeSendMetric callback threw an exception. The Metric will be dropped.");
154163
entry.Exception.Should().BeOfType<InvalidOperationException>();
155164
entry.Args.Should().BeEmpty();
165+
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.TraceMetric);
156166
}
157167

158168
[Fact]

0 commit comments

Comments
 (0)