Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
- The SDK now logs a `Warning` instead of an `Error` when being ratelimited ([#4927](https://github.com/getsentry/sentry-dotnet/pull/4927))
- Symbolication now works correctly with Android workloads 10.0.102 and later ([#4998](https://github.com/getsentry/sentry-dotnet/pull/4998))
- `libmonosgen` and `libxamarin` frames no longer show as in-app ([#4960](https://github.com/getsentry/sentry-dotnet/pull/4960))
- The SDK now logs only the first consecutive error when sending envelopes to Spotlight, and uses exponential backoff before subsequent attempts ([#5025](https://github.com/getsentry/sentry-dotnet/pull/5025))

### Dependencies

Expand Down
80 changes: 69 additions & 11 deletions src/Sentry/Http/SpotlightHttpTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal class SpotlightHttpTransport : HttpTransport
private readonly HttpClient _httpClient;
private readonly Uri _spotlightUrl;
private readonly ISystemClock _clock;
private readonly ExponentialBackoff _backoff;

public SpotlightHttpTransport(ITransport inner, SentryOptions options, HttpClient httpClient, Uri spotlightUrl, ISystemClock clock)
: base(options, httpClient)
Expand All @@ -21,6 +22,7 @@ public SpotlightHttpTransport(ITransport inner, SentryOptions options, HttpClien
_spotlightUrl = spotlightUrl;
_inner = inner;
_clock = clock;
_backoff = new ExponentialBackoff(clock);
}

protected internal override HttpRequestMessage CreateRequest(Envelope envelope)
Expand All @@ -38,23 +40,79 @@ public override async Task SendEnvelopeAsync(Envelope envelope, CancellationToke
{
var sentryTask = _inner.SendEnvelopeAsync(envelope, cancellationToken);

try
if (_backoff.ShouldAttempt())
{
// Send to spotlight
using var processedEnvelope = ProcessEnvelope(envelope);
if (processedEnvelope.Items.Count > 0)
try
{
using var request = CreateRequest(processedEnvelope);
using var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
await HandleResponseAsync(response, processedEnvelope, cancellationToken).ConfigureAwait(false);
// Send to spotlight
using var processedEnvelope = ProcessEnvelope(envelope);
if (processedEnvelope.Items.Count > 0)
{
using var request = CreateRequest(processedEnvelope);
using var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
await HandleResponseAsync(response, processedEnvelope, cancellationToken).ConfigureAwait(false);

Comment thread
jamescrosswell marked this conversation as resolved.
_backoff.RecordSuccess();
Comment thread
jamescrosswell marked this conversation as resolved.
}
}
catch (Exception e)
{
int failureCount = _backoff.RecordFailure();
if (failureCount == 1)
{
_options.LogError(e, "Failed sending envelope to Spotlight.");
}
}
}
catch (Exception e)
{
_options.LogError(e, "Failed sending envelope to Spotlight.");
}

// await the Sentry request before returning
await sentryTask.ConfigureAwait(false);
Comment thread
jamescrosswell marked this conversation as resolved.
}

private class ExponentialBackoff
{
private static readonly TimeSpan InitialRetryDelay = TimeSpan.FromSeconds(1);
private static readonly TimeSpan MaxRetryDelay = TimeSpan.FromSeconds(60);

private readonly ISystemClock _clock;

private readonly Lock _lock = new();
private TimeSpan _retryDelay = InitialRetryDelay;
private DateTimeOffset _retryAfter = DateTimeOffset.MinValue;
private int _failureCount;

public ExponentialBackoff(ISystemClock clock)
{
_clock = clock;
}

public bool ShouldAttempt()
{
lock (_lock)
{
return _clock.GetUtcNow() >= _retryAfter;
}
}

public int RecordFailure()
{
lock (_lock)
{
_failureCount++;
_retryAfter = _clock.GetUtcNow() + _retryDelay;
_retryDelay = TimeSpan.FromTicks(Math.Min(_retryDelay.Ticks * 2, MaxRetryDelay.Ticks));
Comment thread
mattico marked this conversation as resolved.
return _failureCount;
}
}

public void RecordSuccess()
{
lock (_lock)
{
_failureCount = 0;
_retryDelay = InitialRetryDelay;
_retryAfter = DateTimeOffset.MinValue;
}
}
Comment thread
cursor[bot] marked this conversation as resolved.
}
}
Loading
Loading