Skip to content

Commit 024972c

Browse files
Fail gracefully with malformed envelopes (#2371)
* Fixed #2357 * Updated Changelog
1 parent 370acc1 commit 024972c

File tree

3 files changed

+80
-34
lines changed

3 files changed

+80
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- NOTE: This is a potentially breaking change, as the `TracesSampleRate` property has been made nullable.
2121
Though extremely uncommon, if you are _retrieving_ the `TracesSampleRate` property for some reason, you will need to account for nulls.
2222
However, there is no change to the behavior or _typical_ usage of either of these properties.
23+
- CachedTransport gracefully handles malformed envelopes during processing ([#2371](https://github.com/getsentry/sentry-dotnet/pull/2371))
2324

2425
### Dependencies
2526

src/Sentry/Internal/Http/CachingTransport.cs

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -290,47 +290,59 @@ private async Task InnerProcessCacheAsync(string file, CancellationToken cancell
290290

291291
_options.LogDebug("Reading cached envelope: {0}", file);
292292

293-
var stream = _fileSystem.OpenFileForReading(file);
293+
try
294+
{
295+
var stream = _fileSystem.OpenFileForReading(file);
294296
#if NETFRAMEWORK || NETSTANDARD2_0
295-
using (stream)
297+
using (stream)
296298
#else
297-
await using (stream.ConfigureAwait(false))
299+
await using (stream.ConfigureAwait(false))
298300
#endif
299-
using (var envelope = await Envelope.DeserializeAsync(stream, cancellation).ConfigureAwait(false))
300-
{
301-
// Don't even try to send it if we are requesting cancellation.
302-
cancellation.ThrowIfCancellationRequested();
303-
304-
try
305301
{
306-
_options.LogDebug("Sending cached envelope: {0}", envelope.TryGetEventId(_options.DiagnosticLogger));
302+
using (var envelope = await Envelope.DeserializeAsync(stream, cancellation).ConfigureAwait(false))
303+
{
304+
// Don't even try to send it if we are requesting cancellation.
305+
cancellation.ThrowIfCancellationRequested();
307306

308-
await _innerTransport.SendEnvelopeAsync(envelope, cancellation).ConfigureAwait(false);
309-
}
310-
// OperationCancel should not log an error
311-
catch (OperationCanceledException ex)
312-
{
313-
_options.LogDebug("Canceled sending cached envelope: {0}, retrying after a delay.", ex, file);
314-
// Let the worker catch, log, wait a bit and retry.
315-
throw;
316-
}
317-
catch (Exception ex) when (ex is HttpRequestException or WebException or SocketException or IOException)
318-
{
319-
_options.LogError("Failed to send cached envelope: {0}, retrying after a delay.", ex, file);
320-
// Let the worker catch, log, wait a bit and retry.
321-
throw;
322-
}
323-
catch (Exception ex) when (ex.Source == "FakeFailingTransport")
324-
{
325-
// HACK: Deliberately sent from unit tests to avoid deleting the file from processing
326-
return;
327-
}
328-
catch (Exception ex)
329-
{
330-
_options.ClientReportRecorder.RecordDiscardedEvents(DiscardReason.CacheOverflow, envelope);
331-
LogFailureWithDiscard(file, ex);
307+
try
308+
{
309+
_options.LogDebug("Sending cached envelope: {0}",
310+
envelope.TryGetEventId(_options.DiagnosticLogger));
311+
312+
await _innerTransport.SendEnvelopeAsync(envelope, cancellation).ConfigureAwait(false);
313+
}
314+
// OperationCancel should not log an error
315+
catch (OperationCanceledException ex)
316+
{
317+
_options.LogDebug("Canceled sending cached envelope: {0}, retrying after a delay.", ex, file);
318+
// Let the worker catch, log, wait a bit and retry.
319+
throw;
320+
}
321+
catch (Exception ex) when (ex is HttpRequestException or WebException or SocketException
322+
or IOException)
323+
{
324+
_options.LogError("Failed to send cached envelope: {0}, retrying after a delay.", ex, file);
325+
// Let the worker catch, log, wait a bit and retry.
326+
throw;
327+
}
328+
catch (Exception ex) when (ex.Source == "FakeFailingTransport")
329+
{
330+
// HACK: Deliberately sent from unit tests to avoid deleting the file from processing
331+
return;
332+
}
333+
catch (Exception ex)
334+
{
335+
_options.ClientReportRecorder.RecordDiscardedEvents(DiscardReason.CacheOverflow, envelope);
336+
LogFailureWithDiscard(file, ex);
337+
}
338+
}
332339
}
333340
}
341+
catch (JsonException ex)
342+
{
343+
// Log deserialization errors
344+
LogFailureWithDiscard(file, ex);
345+
}
334346

335347
// Envelope & file stream must be disposed prior to reaching this point
336348

test/Sentry.Tests/Internals/Http/CachingTransportTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,39 @@ public async Task AwareOfExistingFiles()
292292
innerTransport.GetSentEnvelopes().Should().HaveCount(3);
293293
}
294294

295+
[Fact]
296+
public async Task Handle_Malformed_Envelopes_Gracefully()
297+
{
298+
// Arrange
299+
using var cacheDirectory = new TempDirectory(_fileSystem);
300+
var options = new SentryOptions
301+
{
302+
Dsn = ValidDsn,
303+
DiagnosticLogger = _logger,
304+
Debug = true,
305+
CacheDirectoryPath = cacheDirectory.Path,
306+
FileSystem = _fileSystem
307+
};
308+
var cacheDirectoryPath =
309+
options.TryGetProcessSpecificCacheDirectoryPath() ??
310+
throw new InvalidOperationException("Cache directory or DSN is not set.");
311+
var processingDirectoryPath = Path.Combine(cacheDirectoryPath, "__processing");
312+
var fileName = $"{Guid.NewGuid()}.envelope";
313+
var filePath = Path.Combine(processingDirectoryPath, fileName);
314+
315+
_fileSystem.CreateDirectory(processingDirectoryPath); // Create the processing directory
316+
_fileSystem.CreateFileForWriting(filePath).Dispose(); // Make a malformed envelope... just an empty file
317+
_fileSystem.FileExists(filePath).Should().BeTrue();
318+
319+
// Act
320+
using var innerTransport = new FakeTransport();
321+
await using var transport = CachingTransport.Create(innerTransport, options, startWorker: false);
322+
await transport.FlushAsync(); // Flush the worker to process
323+
324+
// Assert
325+
_fileSystem.FileExists(filePath).Should().BeFalse();
326+
}
327+
295328
[Fact]
296329
public async Task NonTransientExceptionShouldLog()
297330
{

0 commit comments

Comments
 (0)