Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features ✨

- feat: Add exponential backoff and log deduplication to Spotlight transport by @mattico in [#5025](https://github.com/getsentry/sentry-dotnet/pull/5025)

## 6.6.0

### Features ✨
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