diff --git a/src/Sentry/Protocol/Envelopes/Envelope.cs b/src/Sentry/Protocol/Envelopes/Envelope.cs
index 0174dbd32a..edb432dc5b 100644
--- a/src/Sentry/Protocol/Envelopes/Envelope.cs
+++ b/src/Sentry/Protocol/Envelopes/Envelope.cs
@@ -155,8 +155,10 @@ internal async Task SerializeAsync(
{
try
{
- await item.SerializeAsync(stream, logger, cancellationToken).ConfigureAwait(false);
- await stream.WriteNewlineAsync(cancellationToken).ConfigureAwait(false);
+ if (await item.SerializeAsync(stream, logger, dropEmptyAttachment: true, cancellationToken).ConfigureAwait(false))
+ {
+ await stream.WriteNewlineAsync(cancellationToken).ConfigureAwait(false);
+ }
}
catch (Exception e)
{
@@ -180,8 +182,10 @@ internal void Serialize(Stream stream, IDiagnosticLogger? logger, ISystemClock c
{
try
{
- item.Serialize(stream, logger);
- stream.WriteNewline();
+ if (item.Serialize(stream, logger, dropEmptyAttachment: true))
+ {
+ stream.WriteNewline();
+ }
}
catch (Exception e)
{
diff --git a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
index 364d15087e..3b1e84f7a6 100644
--- a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
+++ b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
@@ -175,7 +175,11 @@ private static void SerializeHeader(
}
///
- public async Task SerializeAsync(Stream stream, IDiagnosticLogger? logger,
+ public Task SerializeAsync(Stream stream, IDiagnosticLogger? logger,
+ CancellationToken cancellationToken = default) =>
+ SerializeAsync(stream, logger, dropEmptyAttachment: false, cancellationToken);
+
+ internal async Task SerializeAsync(Stream stream, IDiagnosticLogger? logger, bool dropEmptyAttachment,
CancellationToken cancellationToken = default)
{
// Always calculate the length of the payload, as Sentry will reject envelopes that have incorrect lengths
@@ -189,6 +193,11 @@ public async Task SerializeAsync(Stream stream, IDiagnosticLogger? logger,
await using (payloadBuffer.ConfigureAwait(false))
#endif
{
+ if (dropEmptyAttachment && IsEmptyAttachmentOfUnknownLength(payloadBuffer, logger))
+ {
+ return false;
+ }
+
// Write to the outbound stream asynchronously. It's likely either an HttpRequestStream or a FileStream.
// Header
@@ -199,11 +208,15 @@ public async Task SerializeAsync(Stream stream, IDiagnosticLogger? logger,
// Payload
await payloadBuffer.CopyToAsync(stream, cancellationToken).ConfigureAwait(false);
+ return true;
}
}
///
- public void Serialize(Stream stream, IDiagnosticLogger? logger)
+ public void Serialize(Stream stream, IDiagnosticLogger? logger) =>
+ Serialize(stream, logger, dropEmptyAttachment: false);
+
+ internal bool Serialize(Stream stream, IDiagnosticLogger? logger, bool dropEmptyAttachment)
{
// Always calculate the length of the payload, as Sentry will reject envelopes that have incorrect lengths
// in item headers. Don't trust any previously calculated value to be correct.
@@ -211,6 +224,11 @@ public void Serialize(Stream stream, IDiagnosticLogger? logger)
using var payloadBuffer = BufferPayload(logger);
+ if (dropEmptyAttachment && IsEmptyAttachmentOfUnknownLength(payloadBuffer, logger))
+ {
+ return false;
+ }
+
// Header
var headerWithLength = Header.ToDict();
headerWithLength[LengthKey] = payloadBuffer.Length;
@@ -219,6 +237,18 @@ public void Serialize(Stream stream, IDiagnosticLogger? logger)
// Payload
payloadBuffer.CopyTo(stream);
+ return true;
+ }
+
+ private bool IsEmptyAttachmentOfUnknownLength(MemoryStream payloadBuffer, IDiagnosticLogger? logger)
+ {
+ if (payloadBuffer.Length != 0 || TryGetLength() is not null || Payload is not BufferedStreamSerializable)
+ {
+ return false;
+ }
+
+ logger?.LogWarning("Did not add '{0}' to envelope because the attachment was empty.", TryGetFileName());
+ return true;
}
///
diff --git a/test/Sentry.Tests/Protocol/Envelopes/AttachmentSerializationTests.cs b/test/Sentry.Tests/Protocol/Envelopes/AttachmentSerializationTests.cs
index 061fdaddca..69b2d7229d 100644
--- a/test/Sentry.Tests/Protocol/Envelopes/AttachmentSerializationTests.cs
+++ b/test/Sentry.Tests/Protocol/Envelopes/AttachmentSerializationTests.cs
@@ -389,4 +389,70 @@ public void FromEvent_EmptyByteAttachment_DoesNotAddAttachment()
.Should()
.Be(0);
}
+
+ [Fact]
+ public async Task Serialization_EmptyNonSeekableStreamAttachment_DropsAttachment()
+ {
+ // Arrange
+ using var envelope = CreateEventEnvelopeWithEmptyNonSeekableAttachment();
+
+ // Act
+ var serialized = await envelope.SerializeToStringAsync(_testOutputLogger, _fakeClock);
+
+ // Assert
+ serialized.Should().NotContain("empty.jpg").And.NotContain("\n\n");
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes(serialized));
+ using var deserialized = await Envelope.DeserializeAsync(stream);
+ deserialized.Items.Should().ContainSingle()
+ .Which.TryGetType().Should().Be(EnvelopeItem.TypeValueEvent);
+ }
+
+ [Fact]
+ public async Task Serialization_EmptyNonSeekableStreamAttachmentSynchronously_DropsAttachment()
+ {
+ // Arrange
+ using var envelope = CreateEventEnvelopeWithEmptyNonSeekableAttachment();
+
+ // Act
+ var serialized = envelope.SerializeToString(_testOutputLogger, _fakeClock);
+
+ // Assert
+ serialized.Should().NotContain("empty.jpg").And.NotContain("\n\n");
+ using var stream = new MemoryStream(Encoding.UTF8.GetBytes(serialized));
+ using var deserialized = await Envelope.DeserializeAsync(stream);
+ deserialized.Items.Should().ContainSingle()
+ .Which.TryGetType().Should().Be(EnvelopeItem.TypeValueEvent);
+ }
+
+ [Fact]
+ public async Task Serialization_EmptyAttachmentOfKnownLengthAfterRoundTrip_KeepsAttachment()
+ {
+ // Arrange
+ var attachment = new SentryAttachment(
+ AttachmentType.Default,
+ new StreamAttachmentContent(new MemoryStream()),
+ "empty.log",
+ "text/plain");
+ using var envelope = Envelope.FromAttachment(SentryId.Create(), attachment, _testOutputLogger);
+ using var cached = new MemoryStream(Encoding.UTF8.GetBytes(
+ await envelope.SerializeToStringAsync(_testOutputLogger, _fakeClock)));
+ using var deserialized = await Envelope.DeserializeAsync(cached);
+
+ // Act
+ var serialized = await deserialized.SerializeToStringAsync(_testOutputLogger, _fakeClock);
+
+ // Assert
+ serialized.Should().Contain("empty.log");
+ }
+
+ private Envelope CreateEventEnvelopeWithEmptyNonSeekableAttachment()
+ {
+ var attachment = new SentryAttachment(
+ AttachmentType.Default,
+ new StreamAttachmentContent(new NonSeekableReadStream(new MemoryStream())),
+ "empty.jpg",
+ "image/jpeg");
+
+ return Envelope.FromEvent(new SentryEvent(), _testOutputLogger, [attachment]);
+ }
}