Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
5 changes: 3 additions & 2 deletions src/Sentry.Maui/Internal/ScreenshotAttachment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ private ScreenshotAttachment(
AttachmentType type,
IAttachmentContent content,
string fileName,
string? contentType)
: base(type, content, fileName, contentType)
string? contentType,
bool addToTransactions = false)
Comment thread
jamescrosswell marked this conversation as resolved.
Outdated
: base(type, content, fileName, contentType, addToTransactions)
{
}
}
Expand Down
25 changes: 24 additions & 1 deletion src/Sentry/Protocol/Envelopes/Envelope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,16 @@ public static Envelope FromFeedback(
/// <summary>
/// Creates an envelope that contains a single transaction.
/// </summary>
public static Envelope FromTransaction(SentryTransaction transaction)
public static Envelope FromTransaction(SentryTransaction transaction) =>
FromTransaction(transaction, null, null);

/// <summary>
/// Creates an envelope that contains a single transaction and optional attachments.
/// </summary>
public static Envelope FromTransaction(
Comment thread
jamescrosswell marked this conversation as resolved.
SentryTransaction transaction,
IDiagnosticLogger? logger,
Comment thread
dingsdax marked this conversation as resolved.
Outdated
IReadOnlyCollection<SentryAttachment>? attachments)
{
var eventId = transaction.EventId;
var header = CreateHeader(eventId, transaction.DynamicSamplingContext);
Expand All @@ -358,6 +367,20 @@ public static Envelope FromTransaction(SentryTransaction transaction)
}
}

if (attachments is not null)
{
foreach (var attachment in attachments)
{
if (attachment.IsNull())
{
logger?.LogWarning("Encountered a null attachment. Skipping.");
continue;
}
Comment thread
jamescrosswell marked this conversation as resolved.

AddEnvelopeItemFromAttachment(items, attachment, logger);
}
}

return new Envelope(eventId, header, items);
}

Expand Down
20 changes: 20 additions & 0 deletions src/Sentry/SentryAttachment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ public class SentryAttachment
/// </summary>
public string? ContentType { get; }

/// <summary>
/// Whether the attachment should be added to transactions.
/// Defaults to <c>false</c>.
/// </summary>
public bool AddToTransactions { get; }

/// <summary>
/// Initializes an instance of <see cref="SentryAttachment"/>.
/// </summary>
Expand All @@ -73,10 +79,24 @@ public SentryAttachment(
IAttachmentContent content,
string fileName,
string? contentType)
: this(type, content, fileName, contentType, false)
{
}

/// <summary>
/// Initializes an instance of <see cref="SentryAttachment"/>.
/// </summary>
public SentryAttachment(
Comment thread
jamescrosswell marked this conversation as resolved.
AttachmentType type,
IAttachmentContent content,
string fileName,
string? contentType,
bool addToTransactions)
Comment thread
dingsdax marked this conversation as resolved.
{
Type = type;
Content = content;
FileName = fileName;
ContentType = contentType;
AddToTransactions = addToTransactions;
}
}
5 changes: 4 additions & 1 deletion src/Sentry/SentryClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,10 @@ public void CaptureTransaction(SentryTransaction transaction, Scope? scope, Sent
processedTransaction.Redact();
}

CaptureEnvelope(Envelope.FromTransaction(processedTransaction));
// Keep null entries so the null-attachment guard in Envelope.FromTransaction handles them
// (consistent with the event/feedback capture paths); dereferencing them here would throw.
var attachments = hint.Attachments.Where(a => a is null || a.AddToTransactions).ToList();
Comment thread
jamescrosswell marked this conversation as resolved.
CaptureEnvelope(Envelope.FromTransaction(processedTransaction, _options.DiagnosticLogger, attachments));
}

#if NET6_0_OR_GREATER
Expand Down
15 changes: 12 additions & 3 deletions src/Sentry/ViewHierarchyAttachment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,17 @@ public class ViewHierarchyAttachment : SentryAttachment
/// <summary>
/// Initializes an instance of <see cref="ViewHierarchyAttachment"/>.
/// </summary>
/// /// <param name="content">The view hierarchy attachment</param>
public ViewHierarchyAttachment(IAttachmentContent content) :
base(AttachmentType.ViewHierarchy, content, "view-hierarchy.json", "application/json")
/// <param name="content">The view hierarchy attachment</param>
public ViewHierarchyAttachment(IAttachmentContent content)
: this(content, false)
{ }

/// <summary>
/// Initializes an instance of <see cref="ViewHierarchyAttachment"/>.
/// </summary>
/// <param name="content">The view hierarchy attachment</param>
/// <param name="addToTransactions">Whether the attachment should be added to transactions.</param>
public ViewHierarchyAttachment(IAttachmentContent content, bool addToTransactions)
: base(AttachmentType.ViewHierarchy, content, "view-hierarchy.json", "application/json", addToTransactions)
{ }
}
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,8 @@ namespace Sentry
public class SentryAttachment
{
public SentryAttachment(Sentry.AttachmentType type, Sentry.IAttachmentContent content, string fileName, string? contentType) { }
public SentryAttachment(Sentry.AttachmentType type, Sentry.IAttachmentContent content, string fileName, string? contentType, bool addToTransactions) { }
public bool AddToTransactions { get; }
public Sentry.IAttachmentContent Content { get; }
public string? ContentType { get; }
public string FileName { get; }
Expand Down Expand Up @@ -1415,6 +1417,7 @@ namespace Sentry
public class ViewHierarchyAttachment : Sentry.SentryAttachment
{
public ViewHierarchyAttachment(Sentry.IAttachmentContent content) { }
public ViewHierarchyAttachment(Sentry.IAttachmentContent content, bool addToTransactions) { }
}
public abstract class ViewHierarchyNode : Sentry.ISentryJsonSerializable
{
Expand Down Expand Up @@ -2018,6 +2021,7 @@ namespace Sentry.Protocol.Envelopes
public static Sentry.Protocol.Envelopes.Envelope FromFeedback(Sentry.SentryEvent @event, Sentry.Extensibility.IDiagnosticLogger? logger = null, System.Collections.Generic.IReadOnlyCollection<Sentry.SentryAttachment>? attachments = null, Sentry.SessionUpdate? sessionUpdate = null) { }
public static Sentry.Protocol.Envelopes.Envelope FromSession(Sentry.SessionUpdate sessionUpdate) { }
public static Sentry.Protocol.Envelopes.Envelope FromTransaction(Sentry.SentryTransaction transaction) { }
public static Sentry.Protocol.Envelopes.Envelope FromTransaction(Sentry.SentryTransaction transaction, Sentry.Extensibility.IDiagnosticLogger? logger, System.Collections.Generic.IReadOnlyCollection<Sentry.SentryAttachment>? attachments) { }
}
public sealed class EnvelopeItem : Sentry.Protocol.Envelopes.ISerializable, System.IDisposable
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,8 @@ namespace Sentry
public class SentryAttachment
{
public SentryAttachment(Sentry.AttachmentType type, Sentry.IAttachmentContent content, string fileName, string? contentType) { }
public SentryAttachment(Sentry.AttachmentType type, Sentry.IAttachmentContent content, string fileName, string? contentType, bool addToTransactions) { }
public bool AddToTransactions { get; }
public Sentry.IAttachmentContent Content { get; }
public string? ContentType { get; }
public string FileName { get; }
Expand Down Expand Up @@ -1415,6 +1417,7 @@ namespace Sentry
public class ViewHierarchyAttachment : Sentry.SentryAttachment
{
public ViewHierarchyAttachment(Sentry.IAttachmentContent content) { }
public ViewHierarchyAttachment(Sentry.IAttachmentContent content, bool addToTransactions) { }
}
public abstract class ViewHierarchyNode : Sentry.ISentryJsonSerializable
{
Expand Down Expand Up @@ -2018,6 +2021,7 @@ namespace Sentry.Protocol.Envelopes
public static Sentry.Protocol.Envelopes.Envelope FromFeedback(Sentry.SentryEvent @event, Sentry.Extensibility.IDiagnosticLogger? logger = null, System.Collections.Generic.IReadOnlyCollection<Sentry.SentryAttachment>? attachments = null, Sentry.SessionUpdate? sessionUpdate = null) { }
public static Sentry.Protocol.Envelopes.Envelope FromSession(Sentry.SessionUpdate sessionUpdate) { }
public static Sentry.Protocol.Envelopes.Envelope FromTransaction(Sentry.SentryTransaction transaction) { }
public static Sentry.Protocol.Envelopes.Envelope FromTransaction(Sentry.SentryTransaction transaction, Sentry.Extensibility.IDiagnosticLogger? logger, System.Collections.Generic.IReadOnlyCollection<Sentry.SentryAttachment>? attachments) { }
}
public sealed class EnvelopeItem : Sentry.Protocol.Envelopes.ISerializable, System.IDisposable
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,8 @@ namespace Sentry
public class SentryAttachment
{
public SentryAttachment(Sentry.AttachmentType type, Sentry.IAttachmentContent content, string fileName, string? contentType) { }
public SentryAttachment(Sentry.AttachmentType type, Sentry.IAttachmentContent content, string fileName, string? contentType, bool addToTransactions) { }
public bool AddToTransactions { get; }
public Sentry.IAttachmentContent Content { get; }
public string? ContentType { get; }
public string FileName { get; }
Expand Down Expand Up @@ -1415,6 +1417,7 @@ namespace Sentry
public class ViewHierarchyAttachment : Sentry.SentryAttachment
{
public ViewHierarchyAttachment(Sentry.IAttachmentContent content) { }
public ViewHierarchyAttachment(Sentry.IAttachmentContent content, bool addToTransactions) { }
}
public abstract class ViewHierarchyNode : Sentry.ISentryJsonSerializable
{
Expand Down Expand Up @@ -2018,6 +2021,7 @@ namespace Sentry.Protocol.Envelopes
public static Sentry.Protocol.Envelopes.Envelope FromFeedback(Sentry.SentryEvent @event, Sentry.Extensibility.IDiagnosticLogger? logger = null, System.Collections.Generic.IReadOnlyCollection<Sentry.SentryAttachment>? attachments = null, Sentry.SessionUpdate? sessionUpdate = null) { }
public static Sentry.Protocol.Envelopes.Envelope FromSession(Sentry.SessionUpdate sessionUpdate) { }
public static Sentry.Protocol.Envelopes.Envelope FromTransaction(Sentry.SentryTransaction transaction) { }
public static Sentry.Protocol.Envelopes.Envelope FromTransaction(Sentry.SentryTransaction transaction, Sentry.Extensibility.IDiagnosticLogger? logger, System.Collections.Generic.IReadOnlyCollection<Sentry.SentryAttachment>? attachments) { }
}
public sealed class EnvelopeItem : Sentry.Protocol.Envelopes.ISerializable, System.IDisposable
{
Expand Down
4 changes: 4 additions & 0 deletions test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ namespace Sentry
public class SentryAttachment
{
public SentryAttachment(Sentry.AttachmentType type, Sentry.IAttachmentContent content, string fileName, string? contentType) { }
public SentryAttachment(Sentry.AttachmentType type, Sentry.IAttachmentContent content, string fileName, string? contentType, bool addToTransactions) { }
public bool AddToTransactions { get; }
public Sentry.IAttachmentContent Content { get; }
public string? ContentType { get; }
public string FileName { get; }
Expand Down Expand Up @@ -1396,6 +1398,7 @@ namespace Sentry
public class ViewHierarchyAttachment : Sentry.SentryAttachment
{
public ViewHierarchyAttachment(Sentry.IAttachmentContent content) { }
public ViewHierarchyAttachment(Sentry.IAttachmentContent content, bool addToTransactions) { }
}
public abstract class ViewHierarchyNode : Sentry.ISentryJsonSerializable
{
Expand Down Expand Up @@ -1994,6 +1997,7 @@ namespace Sentry.Protocol.Envelopes
public static Sentry.Protocol.Envelopes.Envelope FromFeedback(Sentry.SentryEvent @event, Sentry.Extensibility.IDiagnosticLogger? logger = null, System.Collections.Generic.IReadOnlyCollection<Sentry.SentryAttachment>? attachments = null, Sentry.SessionUpdate? sessionUpdate = null) { }
public static Sentry.Protocol.Envelopes.Envelope FromSession(Sentry.SessionUpdate sessionUpdate) { }
public static Sentry.Protocol.Envelopes.Envelope FromTransaction(Sentry.SentryTransaction transaction) { }
public static Sentry.Protocol.Envelopes.Envelope FromTransaction(Sentry.SentryTransaction transaction, Sentry.Extensibility.IDiagnosticLogger? logger, System.Collections.Generic.IReadOnlyCollection<Sentry.SentryAttachment>? attachments) { }
}
public sealed class EnvelopeItem : Sentry.Protocol.Envelopes.ISerializable, System.IDisposable
{
Expand Down
5 changes: 3 additions & 2 deletions test/Sentry.Tests/AttachmentHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ namespace Sentry.Tests
{
internal static class AttachmentHelper
{
internal static SentryAttachment FakeAttachment(string name = "test.txt")
internal static SentryAttachment FakeAttachment(string name = "test.txt", bool addToTransactions = false)
=> new(
AttachmentType.Default,
new StreamAttachmentContent(new MemoryStream(new byte[] { 1 })),
name,
null
null,
addToTransactions
);
}
}
22 changes: 22 additions & 0 deletions test/Sentry.Tests/AttachmentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,28 @@ public void GetStream_ReturnsBytesContent()
}
}

public class ViewHierarchyAttachmentTests
{
[Fact]
public void Ctor_DefaultsAddToTransactionsToFalse()
{
var attachment = new ViewHierarchyAttachment(new ByteAttachmentContent(new byte[] { 1 }));
Assert.False(attachment.AddToTransactions);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void Ctor_ForwardsAddToTransactions(bool addToTransactions)
{
var attachment = new ViewHierarchyAttachment(
new ByteAttachmentContent(new byte[] { 1 }),
addToTransactions);

Assert.Equal(addToTransactions, attachment.AddToTransactions);
}
}

public class FileAttachmentContentTests
{
[Fact]
Expand Down
28 changes: 28 additions & 0 deletions test/Sentry.Tests/Protocol/Envelopes/EnvelopeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,34 @@ public async Task Roundtrip_WithEvent_WithAttachment_Success()
envelopeRoundtrip.Items[1].TryGetOrRecalculateLength().Should().Be(attachment.Content.GetStream().Length);
}

[Fact]
public void FromTransaction_WithNullAttachment_SkipsItAndLogsWarning()
{
// Arrange
var tracer = new TransactionTracer(DisabledHub.Instance, "name", "op");
var transaction = new SentryTransaction(tracer);

using var attachmentStream = new MemoryStream(new byte[] { 1, 2, 3 });
var validAttachment = new SentryAttachment(
AttachmentType.Default,
new StreamAttachmentContent(attachmentStream),
"file.txt",
null);

var attachments = new List<SentryAttachment> { null!, validAttachment };
var logger = new InMemoryDiagnosticLogger();

// Act
using var envelope = Envelope.FromTransaction(transaction, logger, attachments);

// Assert
// Only the transaction item and the single valid attachment - the null is skipped.
envelope.Items.Count(item => item.TryGetType() == "attachment").Should().Be(1);
logger.Entries.Should().ContainSingle(e =>
e.Level == SentryLevel.Warning &&
e.Message == "Encountered a null attachment. Skipping.");
}

[Fact]
public async Task Roundtrip_WithEvent_WithSession_Success()
{
Expand Down
69 changes: 69 additions & 0 deletions test/Sentry.Tests/SentryClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,75 @@ public void CaptureTransaction_ScopeContainsAttachments_GetAppliedToHint()
hint.Attachments.Should().Contain(attachments);
}

[Fact]
public void CaptureTransaction_AttachmentWithAddToTransactionsTrue_IncludedInEnvelope()
{
// Arrange
var transaction = new SentryTransaction("name", "operation")
{
IsSampled = true,
EndTimestamp = DateTimeOffset.Now
};
var attachment = AttachmentHelper.FakeAttachment("include.txt", addToTransactions: true);
var scope = new Scope(_fixture.SentryOptions);
scope.AddAttachment(attachment);
var sut = _fixture.GetSut();

// Act
sut.CaptureTransaction(transaction, scope, null);

// Assert
sut.Worker.Received(1).EnqueueEnvelope(Arg.Is<Envelope>(envelope =>
envelope.Items.Count(item => item.TryGetType() == "attachment") == 1));
}

[Fact]
public void CaptureTransaction_AttachmentWithAddToTransactionsFalse_ExcludedFromEnvelope()
{
// Arrange
var transaction = new SentryTransaction("name", "operation")
{
IsSampled = true,
EndTimestamp = DateTimeOffset.Now
};
var scope = new Scope(_fixture.SentryOptions);
scope.AddAttachment(AttachmentHelper.FakeAttachment("exclude.txt")); // default: AddToTransactions = false
var sut = _fixture.GetSut();

// Act
sut.CaptureTransaction(transaction, scope, null);

// Assert
sut.Worker.Received(1).EnqueueEnvelope(Arg.Is<Envelope>(envelope =>
envelope.Items.Count(item => item.TryGetType() == "attachment") == 0));
}

[Fact]
public void CaptureTransaction_NullAttachmentInHint_DoesNotThrowAndSkipsNull()
{
// Arrange
var transaction = new SentryTransaction("name", "operation")
{
IsSampled = true,
EndTimestamp = DateTimeOffset.Now
};
var scope = new Scope(_fixture.SentryOptions);
var sut = _fixture.GetSut();

// A null entry in the hint's attachments must not crash transaction capture.
var hint = new SentryHint();
hint.Attachments.Add(null!);
hint.Attachments.Add(AttachmentHelper.FakeAttachment("include.txt", addToTransactions: true));

// Act
var capture = () => sut.CaptureTransaction(transaction, scope, hint);

// Assert
capture.Should().NotThrow();
sut.Worker.Received(1).EnqueueEnvelope(Arg.Is<Envelope>(envelope =>
envelope.Items.Count(item => item.TryGetType() == "attachment") == 1));
}

[SkippableFact]
public void CaptureTransaction_UserIsNull_SetsFallbackUserId()
{
Expand Down
Loading