Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/Sentry/Internal/DefaultSentryMetricEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ private protected override void CaptureMetric<T>(SentryMetricType type, string n
{
if (!SentryMetric.IsSupported(typeof(T)))
{
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
_options.DiagnosticLogger?.LogWarning("{0} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.", typeof(T));
return;
}

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

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

if (configuredMetric is not null)
if (configuredMetric is null)
{
_batchProcessor.Enqueue(configuredMetric);
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.BeforeSend, DataCategory.TraceMetric);
Comment thread
jamescrosswell marked this conversation as resolved.
_options.DiagnosticLogger?.LogInfo("Metric dropped by BeforeSendMetric callback.");
return;
}

_batchProcessor.Enqueue(configuredMetric);
}

/// <inheritdoc />
Expand Down
11 changes: 9 additions & 2 deletions src/Sentry/Internal/DefaultSentryStructuredLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ private protected override void CaptureLog(SentryLogLevel level, string template
}
catch (FormatException e)
{
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.LogItem);
_options.DiagnosticLogger?.LogError(e, "Template string does not match the provided argument. The Log will be dropped.");
return;
}
Expand Down Expand Up @@ -70,6 +71,7 @@ private protected override void CaptureLog(SentryLogLevel level, string template
}
catch (Exception e)
{
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.LogItem);
_options.DiagnosticLogger?.LogError(e, "The configureLog callback threw an exception. The Log will be dropped.");
return;
}
Expand All @@ -93,15 +95,20 @@ protected internal override void CaptureLog(SentryLog log)
}
catch (Exception e)
{
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.LogItem);
_options.DiagnosticLogger?.LogError(e, "The BeforeSendLog callback threw an exception. The Log will be dropped.");
return;
}
}

if (configuredLog is not null)
if (configuredLog is null)
{
_batchProcessor.Enqueue(configuredLog);
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.BeforeSend, DataCategory.LogItem);
Comment thread
jamescrosswell marked this conversation as resolved.
_options.DiagnosticLogger?.LogInfo("Log dropped by BeforeSendLog callback.");
return;
}

_batchProcessor.Enqueue(configuredLog);
}

/// <inheritdoc />
Expand Down
2 changes: 2 additions & 0 deletions src/Sentry/Internal/DiscardReason.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ namespace Sentry.Internal;
{
// See https://develop.sentry.dev/sdk/client-reports/ for list
public static DiscardReason BeforeSend = new("before_send");
public static DiscardReason CallbackError = new("callback_error");
public static DiscardReason BufferOverflow = new("buffer_overflow");
public static DiscardReason CacheOverflow = new("cache_overflow");
public static DiscardReason EventProcessor = new("event_processor");
public static DiscardReason Invalid = new("invalid");
public static DiscardReason NetworkError = new("network_error");
public static DiscardReason QueueOverflow = new("queue_overflow");
public static DiscardReason SendError = new("send_error");
Expand Down
14 changes: 12 additions & 2 deletions src/Sentry/Internal/SentryEventHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,17 @@ internal static class SentryEventHelper

foreach (var processor in processors)
{
processedEvent = processor.DoProcessEvent(processedEvent, effectiveHint);
try
{
processedEvent = processor.DoProcessEvent(processedEvent, effectiveHint);
}
catch (Exception e)
{
options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, dataCategory);
options.LogError(e, "Event processor {0} threw an exception. The event will be dropped.", processor.GetType().Name);
return null;
}

if (processedEvent == null)
{
options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.EventProcessor, dataCategory);
Expand Down Expand Up @@ -94,7 +104,7 @@ internal static class SentryEventHelper
}
catch (Exception e)
{
options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.BeforeSend, DataCategory.Feedback);
options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.Feedback);
options.LogError(e, "The BeforeSendFeedback callback threw an exception. The feedback will be dropped.");
return null;
}
Expand Down
13 changes: 12 additions & 1 deletion src/Sentry/SentryClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,18 @@ public void CaptureTransaction(SentryTransaction transaction, Scope? scope, Sent
var processedTransaction = transaction;
foreach (var processor in scope.GetAllTransactionProcessors())
{
processedTransaction = processor.DoProcessTransaction(transaction, hint);
try
{
processedTransaction = processor.DoProcessTransaction(transaction, hint);
}
catch (Exception e)
{
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.Transaction);
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.Span, spanCount);
_options.LogError(e, "Transaction processor {0} threw an exception. The transaction will be dropped.", processor.GetType().Name);
return;
}

if (processedTransaction == null) // Rejected transaction
{
_options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.EventProcessor, DataCategory.Transaction);
Expand Down
44 changes: 43 additions & 1 deletion test/Sentry.Tests/SentryClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,23 @@ public void CaptureEvent_EventProcessor_RejectEvent_RecordsDiscard()
.RecordDiscardedEvent(DiscardReason.EventProcessor, DataCategory.Error);
}

[Fact]
public void CaptureEvent_EventProcessorThrows_DropsEventAndRecordsDiscard()
{
var processor = Substitute.For<ISentryEventProcessor>();
processor.Process(Arg.Any<SentryEvent>()).Throws(new InvalidOperationException());

_fixture.SentryOptions.AddEventProcessor(processor);

var sut = _fixture.GetSut();
var id = sut.CaptureEvent(new SentryEvent());

id.Should().Be(SentryId.Empty);
_fixture.BackgroundWorker.DidNotReceive().EnqueueEnvelope(Arg.Any<Envelope>());
_fixture.ClientReportRecorder.Received(1)
.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.Error);
}

[Fact]
public void CaptureEvent_ExceptionFilter_RecordsDiscard()
{
Expand Down Expand Up @@ -1126,7 +1143,7 @@ public void CaptureFeedback_BeforeSendFeedbackThrows_FeedbackDropped()
result.Should().Be(CaptureFeedbackResult.DroppedByBeforeSendFeedback);
id.Should().Be(SentryId.Empty);
_ = sut.Worker.DidNotReceive().EnqueueEnvelope(Arg.Any<Envelope>());
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.BeforeSend, DataCategory.Feedback);
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.Feedback);
}

[Fact]
Expand Down Expand Up @@ -1695,6 +1712,31 @@ public void CaptureTransaction_TransactionProcessorRejectsEvent_RecordDiscardedE
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(reason, DataCategory.Span, expectedDroppedSpanCount);
}

[Fact]
public void CaptureTransaction_TransactionProcessorThrows_DropsTransactionAndRecordsDiscard()
{
// Arrange
var processor = Substitute.For<ISentryTransactionProcessorWithHint>();
processor.Process(Arg.Any<SentryTransaction>(), Arg.Any<SentryHint>()).Throws(new InvalidOperationException());
_fixture.SentryOptions.AddTransactionProcessor(processor);

var hub = Substitute.For<IHub>();
var transaction = new TransactionTracer(hub, "test name", "test operation");
transaction.StartChild("span1");
transaction.StartChild("span2");
transaction.EndTimestamp = DateTimeOffset.Now; // finished

// Act
_fixture.GetSut().CaptureTransaction(new SentryTransaction(transaction));

// Assert
_fixture.BackgroundWorker.DidNotReceive().EnqueueEnvelope(Arg.Any<Envelope>());
var reason = DiscardReason.CallbackError;
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(reason, DataCategory.Transaction);
var expectedDroppedSpanCount = transaction.Spans.Count + 1;
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(reason, DataCategory.Span, expectedDroppedSpanCount);
}

[Fact]
public void CaptureTransaction_BeforeSendTransaction_GetsHint()
{
Expand Down
5 changes: 5 additions & 0 deletions test/Sentry.Tests/SentryMetricEmitterTests.Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ public void Emit_Decimal_DoesNotCaptureEnvelope(SentryMetricType type)
var entry = _fixture.DiagnosticLogger.Dequeue();
entry.Level.Should().Be(SentryLevel.Warning);
entry.Message.Should().Be("{0} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.");
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
entry.Exception.Should().BeNull();
entry.Args.Should().BeEquivalentTo([typeof(decimal)]);
}
Expand All @@ -183,6 +184,7 @@ public void Emit_Half_DoesNotCaptureEnvelope(SentryMetricType type)
var entry = _fixture.DiagnosticLogger.Dequeue();
entry.Level.Should().Be(SentryLevel.Warning);
entry.Message.Should().Be("{0} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.");
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
entry.Exception.Should().BeNull();
entry.Args.Should().BeEquivalentTo([typeof(Half)]);
}
Expand All @@ -203,6 +205,7 @@ public void Emit_Enum_DoesNotCaptureEnvelope(SentryMetricType type)
var entry = _fixture.DiagnosticLogger.Dequeue();
entry.Level.Should().Be(SentryLevel.Warning);
entry.Message.Should().Be("{0} is unsupported type for Sentry Metrics. The only supported types are byte, short, int, long, float, and double.");
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
entry.Exception.Should().BeNull();
entry.Args.Should().BeEquivalentTo([typeof(StringComparison)]);
}
Expand All @@ -222,6 +225,7 @@ public void Emit_Name_Null_DoesNotCaptureEnvelope(SentryMetricType type, string
var entry = _fixture.DiagnosticLogger.Dequeue();
entry.Level.Should().Be(SentryLevel.Warning);
entry.Message.Should().Be("Name of metrics cannot be null or empty. Metric-Type: {0}; Value-Type: {1}");
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
entry.Exception.Should().BeNull();
entry.Args.Should().BeEquivalentTo<object>([arg0, arg1]);
}
Expand All @@ -241,6 +245,7 @@ public void Emit_Name_Empty_DoesNotCaptureEnvelope(SentryMetricType type, string
var entry = _fixture.DiagnosticLogger.Dequeue();
entry.Level.Should().Be(SentryLevel.Warning);
entry.Message.Should().Be("Name of metrics cannot be null or empty. Metric-Type: {0}; Value-Type: {1}");
_fixture.ClientReportRecorder.Received(1).RecordDiscardedEvent(DiscardReason.Invalid, DataCategory.TraceMetric);
entry.Exception.Should().BeNull();
entry.Args.Should().BeEquivalentTo<object>([arg0, arg1]);
}
Expand Down
9 changes: 9 additions & 0 deletions test/Sentry.Tests/SentryMetricEmitterTests.Values.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ private void AssertEmittedUnit(MeasurementUnit unit, string expected)

captured.Should().NotBeNull();
captured.Unit.Should().Be(expected);
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
}

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

captured.Should().NotBeNull();
captured.Unit.Should().Be("none");
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
}

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

captured.Should().NotBeNull();
captured.Unit.Should().Be("custom_unit");
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
}

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

captured.Should().NotBeNull();
captured.Unit.Should().BeEmpty();
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
}

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

captured.Should().NotBeNull();
captured.Unit.Should().BeNull();
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
}

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

captured.Should().NotBeNull();
captured.Unit.Should().BeNull();
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
}

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

captured.Should().NotBeNull();
captured.Unit.Should().Be("custom_unit");
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
}

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

captured.Should().NotBeNull();
captured.Unit.Should().BeEmpty();
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
}

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

captured.Should().NotBeNull();
captured.Unit.Should().BeNull();
_fixture.DiagnosticLogger.Dequeue().Message.Should().Be("Metric dropped by BeforeSendMetric callback.");
}

[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.")]
Expand Down
10 changes: 10 additions & 0 deletions test/Sentry.Tests/SentryMetricEmitterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ public Fixture()
{
DiagnosticLogger = new InMemoryDiagnosticLogger();
Hub = Substitute.For<IHub>();
ClientReportRecorder = Substitute.For<IClientReportRecorder>();
Options = new SentryOptions
{
Debug = true,
DiagnosticLogger = DiagnosticLogger,
ClientReportRecorder = ClientReportRecorder,
};
Clock = new MockClock(new DateTimeOffset(2025, 04, 22, 14, 51, 00, 789, TimeSpan.FromHours(2)));
BatchSize = 2;
Expand All @@ -39,6 +41,7 @@ public Fixture()

public InMemoryDiagnosticLogger DiagnosticLogger { get; }
public IHub Hub { get; }
public IClientReportRecorder ClientReportRecorder { get; }
public SentryOptions Options { get; }
public ISystemClock Clock { get; }
public int BatchSize { get; set; }
Expand Down Expand Up @@ -137,6 +140,12 @@ public void Emit_WhenBeforeSendMetricReturnsNull_DoesNotCaptureEnvelope()

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

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

[Fact]
Expand Down
Loading
Loading