From 556ba4c73814c89633e9db9d83eea19288796198 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 2 May 2023 16:34:09 +1200 Subject: [PATCH 01/25] Added SentryOptions.SetBeforeSend - Marked SentryOptions.BeforeSend property obsolete - Added Hint class and Tests to be used as a parameter to SetBeforeSend --- .../Program.cs | 17 +- .../Program.cs | 17 +- src/Sentry/Extensibility/DisabledHub.cs | 5 + src/Sentry/Extensibility/HubAdapter.cs | 28 +- src/Sentry/Hint.cs | 88 ++++++ src/Sentry/ISentryClient.cs | 9 + src/Sentry/Internal/Hub.cs | 9 +- src/Sentry/Internal/IHubEx.cs | 2 +- src/Sentry/Platforms/Android/SentrySdk.cs | 2 +- src/Sentry/Scope.cs | 1 - src/Sentry/SentryClient.cs | 17 +- src/Sentry/SentryOptions.cs | 21 +- src/Sentry/SentrySdk.cs | 18 +- .../ErrorProcessorTests.cs | 31 +- .../SentryMauiAppBuilderExtensionsTests.cs | 17 +- .../SerilogAspNetSentrySdkTestFixture.cs | 11 +- ...sactionEndedAsCrashed.Core3_1.verified.txt | 181 +++++++++++ ...ctionEndedAsCrashed.DotNet6_0.verified.txt | 181 +++++++++++ ...ctionEndedAsCrashed.DotNet7_0.verified.txt | 181 +++++++++++ ...sactionEndedAsCrashed.Mono4_0.verified.txt | 181 +++++++++++ ...nsactionEndedAsCrashed.Net4_8.verified.txt | 181 +++++++++++ test/Sentry.Tests/HintTests.cs | 297 ++++++++++++++++++ test/Sentry.Tests/Sentry.Tests.csproj | 23 ++ test/Sentry.Tests/SentryClientTests.cs | 10 +- test/Sentry.Tests/SentryClientTests.verify.cs | 2 +- 25 files changed, 1454 insertions(+), 76 deletions(-) create mode 100644 src/Sentry/Hint.cs create mode 100644 test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Core3_1.verified.txt create mode 100644 test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet6_0.verified.txt create mode 100644 test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet7_0.verified.txt create mode 100644 test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Mono4_0.verified.txt create mode 100644 test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Net4_8.verified.txt create mode 100644 test/Sentry.Tests/HintTests.cs diff --git a/samples/Sentry.Samples.Console.Customized/Program.cs b/samples/Sentry.Samples.Console.Customized/Program.cs index 37b37b5201..a2e4810e79 100644 --- a/samples/Sentry.Samples.Console.Customized/Program.cs +++ b/samples/Sentry.Samples.Console.Customized/Program.cs @@ -42,16 +42,17 @@ await SentrySdk.ConfigureScopeAsync(async scope => // o.SampleRate = 0.5f; // Randomly drop (don't send to Sentry) half of events // Modifications to event before it goes out. Could replace the event altogether - o.BeforeSend = @event => - { - // Drop an event altogether: - if (@event.Tags.ContainsKey("SomeTag")) + o.SetBeforeSend(@event => { - return null; - } + // Drop an event altogether: + if (@event.Tags.ContainsKey("SomeTag")) + { + return null; + } - return @event; - }; + return @event; + } + ); // Allows inspecting and modifying, returning a new or simply rejecting (returning null) o.BeforeBreadcrumb = crumb => diff --git a/samples/Sentry.Samples.Console.Profiling/Program.cs b/samples/Sentry.Samples.Console.Profiling/Program.cs index 870ee78cd4..f547ef92b8 100644 --- a/samples/Sentry.Samples.Console.Profiling/Program.cs +++ b/samples/Sentry.Samples.Console.Profiling/Program.cs @@ -43,16 +43,17 @@ await SentrySdk.ConfigureScopeAsync(async scope => // o.SampleRate = 0.5f; // Randomly drop (don't send to Sentry) half of events // Modifications to event before it goes out. Could replace the event altogether - o.BeforeSend = @event => - { - // Drop an event altogether: - if (@event.Tags.ContainsKey("SomeTag")) + o.SetBeforeSend(@event => { - return null; - } + // Drop an event altogether: + if (@event.Tags.ContainsKey("SomeTag")) + { + return null; + } - return @event; - }; + return @event; + } + ); // Allows inspecting and modifying, returning a new or simply rejecting (returning null) o.BeforeBreadcrumb = crumb => diff --git a/src/Sentry/Extensibility/DisabledHub.cs b/src/Sentry/Extensibility/DisabledHub.cs index 0b76a639fc..f5564d023f 100644 --- a/src/Sentry/Extensibility/DisabledHub.cs +++ b/src/Sentry/Extensibility/DisabledHub.cs @@ -114,6 +114,11 @@ public void BindClient(ISentryClient client) /// public SentryId CaptureEvent(SentryEvent evt, Scope? scope = null) => SentryId.Empty; + /// + /// No-Op. + /// + public SentryId CaptureEvent(SentryEvent evt, Hint? hint, Scope? scope = null) => SentryId.Empty; + /// /// No-Op. /// diff --git a/src/Sentry/Extensibility/HubAdapter.cs b/src/Sentry/Extensibility/HubAdapter.cs index d4917bb08b..b5b5f7caf4 100644 --- a/src/Sentry/Extensibility/HubAdapter.cs +++ b/src/Sentry/Extensibility/HubAdapter.cs @@ -163,6 +163,12 @@ public void AddBreadcrumb( data, level); + /// + /// Forwards the call to + /// + SentryId IHubEx.CaptureEventInternal(SentryEvent evt, Hint? hint, Scope? scope) + => SentrySdk.CaptureEventInternal(evt, hint, scope); + /// /// Forwards the call to . /// @@ -170,26 +176,21 @@ public void AddBreadcrumb( public SentryId CaptureEvent(SentryEvent evt) => SentrySdk.CaptureEvent(evt); - /// - /// Forwards the call to - /// - SentryId IHubEx.CaptureEventInternal(SentryEvent evt, Scope? scope) - => SentrySdk.CaptureEventInternal(evt, scope); - /// /// Forwards the call to . /// [DebuggerStepThrough] - public SentryId CaptureException(Exception exception) - => SentrySdk.CaptureException(exception); + [EditorBrowsable(EditorBrowsableState.Never)] + public SentryId CaptureEvent(SentryEvent evt, Scope? scope) + => SentrySdk.CaptureEvent(evt, scope); /// /// Forwards the call to . /// [DebuggerStepThrough] [EditorBrowsable(EditorBrowsableState.Never)] - public SentryId CaptureEvent(SentryEvent evt, Scope? scope) - => SentrySdk.CaptureEvent(evt, scope); + public SentryId CaptureEvent(SentryEvent evt, Hint? hint, Scope? scope) + => SentrySdk.CaptureEvent(evt, hint, scope); /// /// Forwards the call to . @@ -199,6 +200,13 @@ public SentryId CaptureEvent(SentryEvent evt, Scope? scope) public SentryId CaptureEvent(SentryEvent evt, Action configureScope) => SentrySdk.CaptureEvent(evt, configureScope); + /// + /// Forwards the call to . + /// + [DebuggerStepThrough] + public SentryId CaptureException(Exception exception) + => SentrySdk.CaptureException(exception); + /// /// Forwards the call to . /// diff --git a/src/Sentry/Hint.cs b/src/Sentry/Hint.cs new file mode 100644 index 0000000000..087f147b0d --- /dev/null +++ b/src/Sentry/Hint.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Net.Mail; +using Sentry.Internal.Extensions; + +namespace Sentry; + +/// +/// A hint for the to decide whether an event should be sent or cached. It also +/// holds data that should be injected into the event. +/// +public class Hint : ICollection, IEnumerable> +{ + private readonly Dictionary _internalStorage = new(); + private readonly List _attachments = new(); + + public object? this[string name] + { + get => _internalStorage.GetValueOrDefault(name); + set => _internalStorage[name] = value; + } + + public void AddAttachments(params Attachment[] attachments) + { + if (attachments is not null) + { + _attachments.AddRange(attachments); + } + } + + public void AddAttachments(ICollection attachments) + { + if (attachments is not null) + { + _attachments.AddRange(attachments); + } + } + + public ICollection Attachments => _attachments; + + public void Clear() => _internalStorage.Clear(); + + public bool ContainsKey(string key) => _internalStorage.ContainsKey(key); + + public void CopyTo(Array array, int index) => ((ICollection)_internalStorage).CopyTo(array, index); + + public int Count => _internalStorage.Count; + + IEnumerator IEnumerable.GetEnumerator() => _internalStorage.GetEnumerator(); + + public IEnumerator> GetEnumerator() + => ((IEnumerable>)_internalStorage).GetEnumerator(); + + public T? GetAs(string name) where T : class? + => (this[name] is T typedHintValue) + ? typedHintValue + : null; + + public bool IsSynchronized => ((ICollection)_internalStorage).IsSynchronized; + + public void Remove(string name) => _internalStorage.Remove(name); + + public Attachment? Screenshot { get; set; } + + public object SyncRoot => ((ICollection)_internalStorage).SyncRoot; + + public Attachment? ViewHierarchy { get; set; } + + /// + /// Creates a new Hint with one or more attachments. + /// + /// + /// + public static Hint WithAttachments(params Attachment[] attachment) + => Hint.WithAttachments(attachment.ToList()); + + /// + /// Creates a new Hint with attachments. + /// + /// + /// + public static Hint WithAttachments(ICollection attachments) + { + var hint = new Hint(); + hint.AddAttachments(attachments); + return hint; + } +} diff --git a/src/Sentry/ISentryClient.cs b/src/Sentry/ISentryClient.cs index d229d99e31..98c7c806f3 100644 --- a/src/Sentry/ISentryClient.cs +++ b/src/Sentry/ISentryClient.cs @@ -18,6 +18,15 @@ public interface ISentryClient /// The Id of the event. SentryId CaptureEvent(SentryEvent evt, Scope? scope = null); + /// + /// Capture the event + /// + /// The event to be captured. + /// An optional hint providing high level context for the source of the event + /// An optional scope to be applied to the event. + /// The Id of the event. + SentryId CaptureEvent(SentryEvent evt, Hint? hint, Scope? scope = null); + /// /// Captures a user feedback. /// diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 81b21c7cf5..a0e707ff3b 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -316,9 +316,12 @@ public SentryId CaptureEvent(SentryEvent evt, Action configureScope) } public SentryId CaptureEvent(SentryEvent evt, Scope? scope = null) => - IsEnabled ? ((IHubEx)this).CaptureEventInternal(evt, scope) : SentryId.Empty; + CaptureEvent(evt, null, scope); - SentryId IHubEx.CaptureEventInternal(SentryEvent evt, Scope? scope) + public SentryId CaptureEvent(SentryEvent evt, Hint? hint, Scope? scope = null) => + IsEnabled ? ((IHubEx)this).CaptureEventInternal(evt, hint, scope) : SentryId.Empty; + + SentryId IHubEx.CaptureEventInternal(SentryEvent evt, Hint? hint, Scope? scope) { try { @@ -353,7 +356,7 @@ SentryId IHubEx.CaptureEventInternal(SentryEvent evt, Scope? scope) // Now capture the event with the Sentry client on the current scope. var sentryClient = currentScope.Value; - var id = sentryClient.CaptureEvent(evt, actualScope); + var id = sentryClient.CaptureEvent(evt, hint, actualScope); actualScope.LastEventId = id; actualScope.SessionUpdate = null; diff --git a/src/Sentry/Internal/IHubEx.cs b/src/Sentry/Internal/IHubEx.cs index 8ce427efe4..10c5dd3a22 100644 --- a/src/Sentry/Internal/IHubEx.cs +++ b/src/Sentry/Internal/IHubEx.cs @@ -2,7 +2,7 @@ namespace Sentry.Internal; internal interface IHubEx : IHub { - SentryId CaptureEventInternal(SentryEvent evt, Scope? scope = null); + SentryId CaptureEventInternal(SentryEvent evt, Hint? hint, Scope? scope = null); T? WithScope(Func scopeCallback); Task WithScopeAsync(Func scopeCallback); Task WithScopeAsync(Func> scopeCallback); diff --git a/src/Sentry/Platforms/Android/SentrySdk.cs b/src/Sentry/Platforms/Android/SentrySdk.cs index ce932a7546..b08a2c0e07 100644 --- a/src/Sentry/Platforms/Android/SentrySdk.cs +++ b/src/Sentry/Platforms/Android/SentrySdk.cs @@ -118,7 +118,7 @@ private static void InitSentryAndroidSdk(SentryOptions options) } } - if (options.Android.EnableAndroidSdkBeforeSend && options.BeforeSend is { } beforeSend) + if (options.Android.EnableAndroidSdkBeforeSend && options.BeforeSendInternal is { } beforeSend) { o.BeforeSend = new BeforeSendCallback(beforeSend, options, o); } diff --git a/src/Sentry/Scope.cs b/src/Sentry/Scope.cs index bd4db02382..6b4a0f7075 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -359,7 +359,6 @@ public void ClearBreadcrumbs() #endif } - /// /// Applies the data from this scope to another event-like object. /// diff --git a/src/Sentry/SentryClient.cs b/src/Sentry/SentryClient.cs index 320802a400..93cd277058 100644 --- a/src/Sentry/SentryClient.cs +++ b/src/Sentry/SentryClient.cs @@ -65,6 +65,10 @@ internal SentryClient( /// public SentryId CaptureEvent(SentryEvent? @event, Scope? scope = null) + => CaptureEvent(@event, null, scope); + + /// + public SentryId CaptureEvent(SentryEvent? @event, Hint? hint, Scope? scope = null) { if (@event == null) { @@ -73,7 +77,7 @@ public SentryId CaptureEvent(SentryEvent? @event, Scope? scope = null) try { - return DoSendEvent(@event, scope); + return DoSendEvent(@event, hint, scope); } catch (Exception e) { @@ -197,7 +201,7 @@ public void CaptureSession(SessionUpdate sessionUpdate) public Task FlushAsync(TimeSpan timeout) => Worker.FlushAsync(timeout); // TODO: this method needs to be refactored, it's really hard to analyze nullability - private SentryId DoSendEvent(SentryEvent @event, Scope? scope) + private SentryId DoSendEvent(SentryEvent @event, Hint? hint, Scope? scope) { if (_options.SampleRate != null) { @@ -258,7 +262,7 @@ private SentryId DoSendEvent(SentryEvent @event, Scope? scope) } } - processedEvent = BeforeSend(processedEvent); + processedEvent = BeforeSend(processedEvent, hint); if (processedEvent == null) // Rejected event { _options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.BeforeSend, DataCategory.Error); @@ -319,9 +323,9 @@ private bool CaptureEnvelope(Envelope envelope) return false; } - private SentryEvent? BeforeSend(SentryEvent? @event) + private SentryEvent? BeforeSend(SentryEvent? @event, Hint? hint) { - if (_options.BeforeSend == null) + if (_options.BeforeSendInternal == null) { return @event; } @@ -329,7 +333,8 @@ private bool CaptureEnvelope(Envelope envelope) _options.LogDebug("Calling the BeforeSend callback"); try { - @event = _options.BeforeSend?.Invoke(@event!); + hint ??= new Hint(); + @event = _options.BeforeSendInternal?.Invoke(@event!, hint); } catch (Exception e) { diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index e27763249e..cc8c612f6d 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -304,6 +304,10 @@ public float? SampleRate /// public string? Dsn { get; set; } + private Func? _beforeSend; + + internal Func? BeforeSendInternal { get => _beforeSend; } + /// /// A callback to invoke before sending an event to Sentry /// @@ -312,7 +316,22 @@ public float? SampleRate /// a chance to inspect and/or modify the event before it's sent. If the event /// should not be sent at all, return null from the callback. /// - public Func? BeforeSend { get; set; } + [Obsolete("This property will be removed in a future version. Use SetBeforeSend instead.")] + public Func? BeforeSend + { + get => null; + set => _beforeSend = value is null ? null : (e, _) => value(e); + } + + public void SetBeforeSend(Func beforeSend) + { + _beforeSend = (e, _) => beforeSend(e); + } + + public void SetBeforeSend(Func beforeSend) + { + _beforeSend = beforeSend; + } /// /// A callback to invoke before sending a transaction to Sentry diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index d591590284..0a050b29d7 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -409,10 +409,22 @@ public static SentryId CaptureEvent(SentryEvent evt) public static SentryId CaptureEvent(SentryEvent evt, Scope? scope) => CurrentHub.CaptureEvent(evt, scope); - internal static SentryId CaptureEventInternal(SentryEvent evt, Scope? scope) + /// + /// Captures the event, passing a hint, using the specified scope. + /// + /// The event. + /// a hint for the event. + /// The scope. + /// The Id of the event. + [DebuggerStepThrough] + [EditorBrowsable(EditorBrowsableState.Never)] + public static SentryId CaptureEvent(SentryEvent evt, Hint? hint, Scope? scope) + => CurrentHub.CaptureEvent(evt, hint, scope); + + internal static SentryId CaptureEventInternal(SentryEvent evt, Hint? hint, Scope? scope) => CurrentHub is IHubEx hub - ? hub.CaptureEventInternal(evt, scope) - : CurrentHub.CaptureEvent(evt, scope); + ? hub.CaptureEventInternal(evt, hint, scope) + : CurrentHub.CaptureEvent(evt, hint, scope); /// /// Captures an event with a configurable scope. diff --git a/test/Sentry.EntityFramework.Tests/ErrorProcessorTests.cs b/test/Sentry.EntityFramework.Tests/ErrorProcessorTests.cs index 4f93558703..f31d6dd290 100644 --- a/test/Sentry.EntityFramework.Tests/ErrorProcessorTests.cs +++ b/test/Sentry.EntityFramework.Tests/ErrorProcessorTests.cs @@ -61,23 +61,24 @@ public async Task Integration_DbEntityValidationExceptionProcessorAsync() { Exception assertError = null; // SaveChanges will throw an exception - options.BeforeSend = evt => - { - // We use a try-catch here as we cannot assert directly since SentryClient itself would catch the thrown assertion errors - try + options.SetBeforeSend(evt => { - Assert.True(evt.Extra.TryGetValue("EntityValidationErrors", out var errors)); - var entityValidationErrors = errors as Dictionary>; - Assert.NotNull(entityValidationErrors); - Assert.NotEmpty(entityValidationErrors); - } - catch (Exception ex) - { - assertError = ex; - } + // We use a try-catch here as we cannot assert directly since SentryClient itself would catch the thrown assertion errors + try + { + Assert.True(evt.Extra.TryGetValue("EntityValidationErrors", out var errors)); + var entityValidationErrors = errors as Dictionary>; + Assert.NotNull(entityValidationErrors); + Assert.NotEmpty(entityValidationErrors); + } + catch (Exception ex) + { + assertError = ex; + } - return null; - }; + return null; + } + ); client.CaptureException(e); Assert.Null(assertError); } diff --git a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs index 43e30264d0..4d28523be2 100644 --- a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs +++ b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs @@ -148,14 +148,15 @@ public void UseSentry_SetsMauiSdkNameAndVersion() .UseSentry(options => { options.Dsn = ValidDsn; - options.BeforeSend = e => - { - // capture the event - @event = e; - - // but don't actually send it - return null; - }; + options.SetBeforeSend(e => + { + // capture the event + @event = e; + + // but don't actually send it + return null; + } + ); }); // Act diff --git a/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs b/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs index 7ffa6e27a9..0676d22212 100644 --- a/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs +++ b/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs @@ -11,11 +11,12 @@ protected override void ConfigureBuilder(WebHostBuilder builder) Events = new List(); Configure = options => { - options.BeforeSend = @event => - { - Events.Add(@event); - return @event; - }; + options.SetBeforeSend(@event => + { + Events.Add(@event); + return @event; + } + ); }; ConfigureApp = app => diff --git a/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Core3_1.verified.txt b/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Core3_1.verified.txt new file mode 100644 index 0000000000..4eeeeb9e1c --- /dev/null +++ b/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Core3_1.verified.txt @@ -0,0 +1,181 @@ +[ + { + Header: { + sdk: { + name: sentry.dotnet + } + }, + Items: [ + { + Header: { + type: session + }, + Payload: { + Source: { + DistinctId: Guid_1, + Release: release, + Environment: production, + IsInitial: true + } + } + } + ] + }, + { + Header: { + event_id: Guid_2, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: release, + sample_rate: 1, + trace_id: Guid_3, + transaction: my transaction + } + }, + Items: [ + { + Header: { + type: event + }, + Payload: { + Source: { + Platform: csharp, + SentryExceptions: [ + { + Mechanism: { + Type: generic, + Handled: false, + Synthetic: false + } + } + ], + SentryThreads: [ + { + Crashed: false, + Current: true + } + ], + DebugImages: [ + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: null, + DebugFile: System.Private.CoreLib.pdb, + CodeId: ______________, + CodeFile: .../System.Private.CoreLib.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: .../Sentry.Tests.pdb, + CodeId: _____________, + CodeFile: .../Sentry.Tests.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: xunit.execution.dotnet.pdb, + CodeId: _____________, + CodeFile: .../xunit.execution.dotnet.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: xunit.core.pdb, + CodeId: _____________, + CodeFile: .../xunit.core.dll + } + ], + Level: error, + TransactionName: my transaction, + Request: {}, + Contexts: { + trace: { + Operation: + } + }, + User: {}, + Environment: production + } + } + }, + { + Header: { + type: session + }, + Payload: { + Source: { + DistinctId: Guid_1, + Release: release, + Environment: production, + ErrorCount: 1, + IsInitial: false, + SequenceNumber: 1, + EndStatus: Crashed + } + } + } + ] + }, + { + Header: { + event_id: Guid_4, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: release, + sample_rate: 1, + trace_id: Guid_3, + transaction: my transaction + } + }, + Items: [ + { + Header: { + type: transaction + }, + Payload: { + Source: { + Name: my transaction, + Platform: csharp, + Operation: my operation, + Description: , + Status: Aborted, + IsSampled: true, + SampleRate: 1.0, + Request: {}, + Contexts: { + trace: { + Operation: my operation, + Description: , + Status: Aborted, + IsSampled: true + } + }, + User: {}, + Environment: production, + IsFinished: true + } + } + } + ] + } +] \ No newline at end of file diff --git a/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet6_0.verified.txt b/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet6_0.verified.txt new file mode 100644 index 0000000000..ae69b386e8 --- /dev/null +++ b/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet6_0.verified.txt @@ -0,0 +1,181 @@ +[ + { + Header: { + sdk: { + name: sentry.dotnet + } + }, + Items: [ + { + Header: { + type: session + }, + Payload: { + Source: { + DistinctId: Guid_1, + Release: release, + Environment: production, + IsInitial: true + } + } + } + ] + }, + { + Header: { + event_id: Guid_2, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: release, + sample_rate: 1, + trace_id: Guid_3, + transaction: my transaction + } + }, + Items: [ + { + Header: { + type: event + }, + Payload: { + Source: { + Platform: csharp, + SentryExceptions: [ + { + Mechanism: { + Type: generic, + Handled: false, + Synthetic: false + } + } + ], + SentryThreads: [ + { + Crashed: false, + Current: true + } + ], + DebugImages: [ + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: null, + DebugFile: .../System.Private.CoreLib.pdb, + CodeId: ______________, + CodeFile: .../System.Private.CoreLib.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: .../Sentry.Tests.pdb, + CodeId: _____________, + CodeFile: .../Sentry.Tests.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: xunit.execution.dotnet.pdb, + CodeId: _____________, + CodeFile: .../xunit.execution.dotnet.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: xunit.core.pdb, + CodeId: _____________, + CodeFile: .../xunit.core.dll + } + ], + Level: error, + TransactionName: my transaction, + Request: {}, + Contexts: { + trace: { + Operation: + } + }, + User: {}, + Environment: production + } + } + }, + { + Header: { + type: session + }, + Payload: { + Source: { + DistinctId: Guid_1, + Release: release, + Environment: production, + ErrorCount: 1, + IsInitial: false, + SequenceNumber: 1, + EndStatus: Crashed + } + } + } + ] + }, + { + Header: { + event_id: Guid_4, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: release, + sample_rate: 1, + trace_id: Guid_3, + transaction: my transaction + } + }, + Items: [ + { + Header: { + type: transaction + }, + Payload: { + Source: { + Name: my transaction, + Platform: csharp, + Operation: my operation, + Description: , + Status: Aborted, + IsSampled: true, + SampleRate: 1.0, + Request: {}, + Contexts: { + trace: { + Operation: my operation, + Description: , + Status: Aborted, + IsSampled: true + } + }, + User: {}, + Environment: production, + IsFinished: true + } + } + } + ] + } +] \ No newline at end of file diff --git a/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet7_0.verified.txt b/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet7_0.verified.txt new file mode 100644 index 0000000000..2824ce63df --- /dev/null +++ b/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.DotNet7_0.verified.txt @@ -0,0 +1,181 @@ +[ + { + Header: { + sdk: { + name: sentry.dotnet + } + }, + Items: [ + { + Header: { + type: session + }, + Payload: { + Source: { + DistinctId: Guid_1, + Release: release, + Environment: production, + IsInitial: true + } + } + } + ] + }, + { + Header: { + event_id: Guid_2, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: release, + sample_rate: 1, + trace_id: Guid_3, + transaction: my transaction + } + }, + Items: [ + { + Header: { + type: event + }, + Payload: { + Source: { + Platform: csharp, + SentryExceptions: [ + { + Mechanism: { + Type: generic, + Handled: false, + Synthetic: false + } + } + ], + SentryThreads: [ + { + Crashed: false, + Current: true + } + ], + DebugImages: [ + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: .../System.Private.CoreLib.pdb, + CodeId: ______________, + CodeFile: .../System.Private.CoreLib.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: .../Sentry.Tests.pdb, + CodeId: _____________, + CodeFile: .../Sentry.Tests.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: xunit.execution.dotnet.pdb, + CodeId: _____________, + CodeFile: .../xunit.execution.dotnet.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: xunit.core.pdb, + CodeId: _____________, + CodeFile: .../xunit.core.dll + } + ], + Level: error, + TransactionName: my transaction, + Request: {}, + Contexts: { + trace: { + Operation: + } + }, + User: {}, + Environment: production + } + } + }, + { + Header: { + type: session + }, + Payload: { + Source: { + DistinctId: Guid_1, + Release: release, + Environment: production, + ErrorCount: 1, + IsInitial: false, + SequenceNumber: 1, + EndStatus: Crashed + } + } + } + ] + }, + { + Header: { + event_id: Guid_4, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: release, + sample_rate: 1, + trace_id: Guid_3, + transaction: my transaction + } + }, + Items: [ + { + Header: { + type: transaction + }, + Payload: { + Source: { + Name: my transaction, + Platform: csharp, + Operation: my operation, + Description: , + Status: Aborted, + IsSampled: true, + SampleRate: 1.0, + Request: {}, + Contexts: { + trace: { + Operation: my operation, + Description: , + Status: Aborted, + IsSampled: true + } + }, + User: {}, + Environment: production, + IsFinished: true + } + } + } + ] + } +] \ No newline at end of file diff --git a/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Mono4_0.verified.txt b/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Mono4_0.verified.txt new file mode 100644 index 0000000000..80f9e24e92 --- /dev/null +++ b/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Mono4_0.verified.txt @@ -0,0 +1,181 @@ +[ + { + Header: { + sdk: { + name: sentry.dotnet + } + }, + Items: [ + { + Header: { + type: session + }, + Payload: { + Source: { + DistinctId: Guid_1, + Release: release, + Environment: production, + IsInitial: true + } + } + } + ] + }, + { + Header: { + event_id: Guid_2, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: release, + sample_rate: 1, + trace_id: Guid_3, + transaction: my transaction + } + }, + Items: [ + { + Header: { + type: event + }, + Payload: { + Source: { + Platform: csharp, + SentryExceptions: [ + { + Mechanism: { + Type: generic, + Handled: false, + Synthetic: false + } + } + ], + SentryThreads: [ + { + Crashed: false, + Current: true + } + ], + DebugImages: [ + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: null, + DebugFile: .../mscorlib.pdb, + CodeId: ______________, + CodeFile: .../mscorlib.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: .../Sentry.Tests.pdb, + CodeId: _____________, + CodeFile: .../Sentry.Tests.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: xunit.execution.desktop.pdb, + CodeId: _____________, + CodeFile: .../xunit.execution.desktop.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: xunit.core.pdb, + CodeId: _____________, + CodeFile: .../xunit.core.dll + } + ], + Level: error, + TransactionName: my transaction, + Request: {}, + Contexts: { + trace: { + Operation: + } + }, + User: {}, + Environment: production + } + } + }, + { + Header: { + type: session + }, + Payload: { + Source: { + DistinctId: Guid_1, + Release: release, + Environment: production, + ErrorCount: 1, + IsInitial: false, + SequenceNumber: 1, + EndStatus: Crashed + } + } + } + ] + }, + { + Header: { + event_id: Guid_4, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: release, + sample_rate: 1, + trace_id: Guid_3, + transaction: my transaction + } + }, + Items: [ + { + Header: { + type: transaction + }, + Payload: { + Source: { + Name: my transaction, + Platform: csharp, + Operation: my operation, + Description: , + Status: Aborted, + IsSampled: true, + SampleRate: 1.0, + Request: {}, + Contexts: { + trace: { + Operation: my operation, + Description: , + Status: Aborted, + IsSampled: true + } + }, + User: {}, + Environment: production, + IsFinished: true + } + } + } + ] + } +] \ No newline at end of file diff --git a/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Net4_8.verified.txt b/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Net4_8.verified.txt new file mode 100644 index 0000000000..a1379db4ea --- /dev/null +++ b/test/Sentry.Tests/HintTests.CaptureEvent_ActiveTransaction_UnhandledExceptionTransactionEndedAsCrashed.Net4_8.verified.txt @@ -0,0 +1,181 @@ +[ + { + Header: { + sdk: { + name: sentry.dotnet + } + }, + Items: [ + { + Header: { + type: session + }, + Payload: { + Source: { + DistinctId: Guid_1, + Release: release, + Environment: production, + IsInitial: true + } + } + } + ] + }, + { + Header: { + event_id: Guid_2, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: release, + sample_rate: 1, + trace_id: Guid_3, + transaction: my transaction + } + }, + Items: [ + { + Header: { + type: event + }, + Payload: { + Source: { + Platform: csharp, + SentryExceptions: [ + { + Mechanism: { + Type: generic, + Handled: false, + Synthetic: false + } + } + ], + SentryThreads: [ + { + Crashed: false, + Current: true + } + ], + DebugImages: [ + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-_, + DebugChecksum: null, + DebugFile: mscorlib.pdb, + CodeId: ______________, + CodeFile: .../mscorlib.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: .../Sentry.Tests.pdb, + CodeId: _____________, + CodeFile: .../Sentry.Tests.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: xunit.execution.desktop.pdb, + CodeId: _____________, + CodeFile: .../xunit.execution.desktop.dll + }, + { + Type: pe_dotnet, + ImageAddress: null, + ImageSize: null, + DebugId: ________-____-____-____-____________-________, + DebugChecksum: ______:________________________________________________________________, + DebugFile: xunit.core.pdb, + CodeId: _____________, + CodeFile: .../xunit.core.dll + } + ], + Level: error, + TransactionName: my transaction, + Request: {}, + Contexts: { + trace: { + Operation: + } + }, + User: {}, + Environment: production + } + } + }, + { + Header: { + type: session + }, + Payload: { + Source: { + DistinctId: Guid_1, + Release: release, + Environment: production, + ErrorCount: 1, + IsInitial: false, + SequenceNumber: 1, + EndStatus: Crashed + } + } + } + ] + }, + { + Header: { + event_id: Guid_4, + sdk: { + name: sentry.dotnet + }, + trace: { + environment: production, + public_key: d4d82fc1c2c4032a83f3a29aa3a3aff, + release: release, + sample_rate: 1, + trace_id: Guid_3, + transaction: my transaction + } + }, + Items: [ + { + Header: { + type: transaction + }, + Payload: { + Source: { + Name: my transaction, + Platform: csharp, + Operation: my operation, + Description: , + Status: Aborted, + IsSampled: true, + SampleRate: 1.0, + Request: {}, + Contexts: { + trace: { + Operation: my operation, + Description: , + Status: Aborted, + IsSampled: true + } + }, + User: {}, + Environment: production, + IsFinished: true + } + } + } + ] + } +] \ No newline at end of file diff --git a/test/Sentry.Tests/HintTests.cs b/test/Sentry.Tests/HintTests.cs new file mode 100644 index 0000000000..ae720c15a5 --- /dev/null +++ b/test/Sentry.Tests/HintTests.cs @@ -0,0 +1,297 @@ +namespace Sentry.Tests; + +public class HintTests +{ + private Attachment FakeAttachment(string name = "test.txt") + => new Attachment( + AttachmentType.Default, + new StreamAttachmentContent(new MemoryStream(new byte[] { 1 })), + name, + null); + + [Fact] + public void AddAttachments_WithNullAttachments_DoesNothing() + { + // Arrange + var hint = new Hint(); + + // Act + hint.AddAttachments(null); + + // Assert + Assert.Empty(hint.Attachments); + } + + [Fact] + public void AddAttachments_WithAttachments_AddsToHint() + { + // Arrange + var hint = new Hint(); + var attachment1 = FakeAttachment("attachment1"); + var attachment2 = FakeAttachment("attachment2"); + + // Act + hint.AddAttachments(attachment1, attachment2); + + // Assert + Assert.Equal(2, hint.Attachments.Count); + } + + [Fact] + public void Clear_WithEntries_ClearsHintEntries() + { + // Arrange + var hint = new Hint(); + hint["key"] = "value"; + + // Act + hint.Clear(); + + // Assert + hint.Count.Should().Be(0); + } + + [Fact] + public void ClearAttachments_WithAttachments_ClearsHintAttachments() + { + // Arrange + var hint = new Hint(); + var attachment1 = FakeAttachment("attachment1"); + hint.AddAttachments(attachment1); + + // Act + hint.Attachments.Clear(); + + // Assert + hint.Attachments.Should().BeEmpty(); + } + + [Fact] + public void ContainsKey_ExistingKey_ReturnsTrue() + { + // Arrange + var hint = new Hint(); + hint["key"] = "value"; + + // Act + var containsKey = hint.ContainsKey("key"); + + // Assert + containsKey.Should().BeTrue(); + } + + [Fact] + public void ContainsKey_NonExistingKey_ReturnsFalse() + { + // Arrange + var hint = new Hint(); + hint["key"] = "value"; + + // Act + var containsKey = hint.ContainsKey("nonExistingKey"); + + // Assert + containsKey.Should().BeFalse(); + } + + [Fact] + public void CopyTo_EmptyHint_CopyToArray() + { + // Arrange + var hint = new Hint(); + var array = new object[3]; + + // Act + hint.CopyTo(array, 0); + + // Assert + array.Should().BeEquivalentTo(new object[] { null, null, null }); + } + + [Fact] + public void CopyTo_NonEmptyHint_CopyToArray() + { + // Arrange + var hint = new Hint(); + hint["key1"] = "value1"; + hint["key2"] = "value2"; + hint["key3"] = "value3"; + var array = new object[3]; + + // Act + hint.CopyTo(array, 0); + + // Assert + array.Should().BeEquivalentTo(new [] { + new KeyValuePair("key1", "value1"), + new KeyValuePair("key2", "value2"), + new KeyValuePair("key3", "value3"), + }); + } + + [Fact] + public void CopyTo_ArrayTooSmall_ThrowsException() + { + // Arrange + var hint = new Hint(); + hint["key1"] = "value1"; + hint["key2"] = "value2"; + hint["key3"] = "value3"; + var array = new object[2]; + + // Act and Assert + Assert.Throws(() => hint.CopyTo(array, 0)); + } + + [Fact] + public void Count_ReturnsZero_WhenHintIsEmpty() + { + // Arrange + var hint = new Hint(); + + // Act + var count = hint.Count; + + // Assert + count.Should().Be(0); + } + + [Fact] + public void Count_ReturnsCorrectValue_WhenHintHasItems() + { + // Arrange + var hint = new Hint(); + hint["key1"] = "value1"; + hint["key2"] = "value2"; + + // Act + var count = hint.Count; + + // Assert + count.Should().Be(2); + } + + [Fact] + public void GetAs_WithNonExistingName_ReturnsNull() + { + // Arrange + var hint = new Hint(); + + // Act + var result = hint.GetAs("non-existing"); + + // Assert + Assert.Null(result); + } + + [Fact] + public void GetAs_WithExistingName_ReturnsValue() + { + // Arrange + var hint = new Hint(); + hint["key"] = "value"; + + // Act + var result = hint.GetAs("key"); + + // Assert + Assert.Equal("value", result); + } + + [Fact] + public void GetEnumerator_ReturnsValidEnumreator() + { + // Arrange + var hint = new Hint(); + hint["key1"] = "value1"; + hint["key2"] = "value2"; + + // Act + Assert + var enumerator = hint.GetEnumerator(); + + // Assert + enumerator.Should().BeAssignableTo>>(); + while (enumerator.MoveNext()) + { + enumerator.Current.Key.Should().BeOneOf("key1", "key2"); + enumerator.Current.Value.Should().BeOneOf("value1", "value2"); + } + } + + [Fact] + public void Remove_WithExistingName_RemovesEntry() + { + // Arrange + var hint = new Hint(); + hint["key"] = "value"; + + // Act + hint.Remove("key"); + + // Assert + Assert.Null(hint["key"]); + } + + [Fact] + public void Screenshot_PropertyCanBeSetAndRetrieved() + { + // Arrange + var hint = new Hint(); + var screenshot = FakeAttachment("screenshot.png"); + + // Act + hint.Screenshot = screenshot; + var retrievedScreenshot = hint.Screenshot; + + // Assert + retrievedScreenshot.Should().Be(screenshot); + } + + [Fact] + public void ViewHierarchy_PropertyCanBeSetAndRetrieved() + { + // Arrange + var hint = new Hint(); + var viewHierarchy = FakeAttachment("view_hierarchy.xml"); + + // Act + hint.ViewHierarchy = viewHierarchy; + var retrievedViewHierarchy = hint.ViewHierarchy; + + // Assert + retrievedViewHierarchy.Should().Be(viewHierarchy); + } + + [Fact] + public void WithAttachments_ReturnsHintWithAttachments() + { + // Arrange + var attachment1 = FakeAttachment("attachment1"); + var attachment2 = FakeAttachment("attachment2"); + + // Act + var hint = Hint.WithAttachments(attachment1, attachment2); + + // Assert + hint.Attachments.Count.Should().Be(2); + hint.Attachments.Should().Contain(attachment1); + hint.Attachments.Should().Contain(attachment2); + } + + [Fact] + public void WithAttachments_WithICollection_ReturnsHintWithAttachments() + { + // Arrange + var attachment1 = FakeAttachment("attachment1"); + var attachment2 = FakeAttachment("attachment2"); + var attachments = new List { attachment1, attachment2 }; + + // Act + var hint = Hint.WithAttachments(attachments); + + // Assert + hint.Attachments.Count.Should().Be(2); + hint.Attachments.Should().Contain(attachment1); + hint.Attachments.Should().Contain(attachment2); + } +} diff --git a/test/Sentry.Tests/Sentry.Tests.csproj b/test/Sentry.Tests/Sentry.Tests.csproj index a5f42fc2ce..a0bdebbb3b 100644 --- a/test/Sentry.Tests/Sentry.Tests.csproj +++ b/test/Sentry.Tests/Sentry.Tests.csproj @@ -16,4 +16,27 @@ + + + $([System.String]::Copy('%(FileName)').Split('.')[0]) + HintTests.cs + + + $([System.String]::Copy('%(FileName)').Split('.')[0]) + HintTests.cs + + + $([System.String]::Copy('%(FileName)').Split('.')[0]) + HintTests.cs + + + $([System.String]::Copy('%(FileName)').Split('.')[0]) + HintTests.cs + + + $([System.String]::Copy('%(FileName)').Split('.')[0]) + HintTests.cs + + + diff --git a/test/Sentry.Tests/SentryClientTests.cs b/test/Sentry.Tests/SentryClientTests.cs index dd71edb322..a7bfef91f0 100644 --- a/test/Sentry.Tests/SentryClientTests.cs +++ b/test/Sentry.Tests/SentryClientTests.cs @@ -249,7 +249,7 @@ public void CaptureEvent_EventAndScope_CopyScopeIntoEvent() [Fact] public void CaptureEvent_BeforeEvent_RejectEvent() { - _fixture.SentryOptions.BeforeSend = _ => null; + _fixture.SentryOptions.SetBeforeSend(_ => null); var expectedEvent = new SentryEvent(); var sut = _fixture.GetSut(); @@ -262,7 +262,7 @@ public void CaptureEvent_BeforeEvent_RejectEvent() [Fact] public void CaptureEvent_BeforeEvent_RejectEvent_RecordsDiscard() { - _fixture.SentryOptions.BeforeSend = _ => null; + _fixture.SentryOptions.SetBeforeSend(_ => null); var sut = _fixture.GetSut(); _ = sut.CaptureEvent(new SentryEvent()); @@ -305,7 +305,7 @@ public void CaptureEvent_ExceptionFilter_RecordsDiscard() public void CaptureEvent_BeforeEvent_ModifyEvent() { SentryEvent received = null; - _fixture.SentryOptions.BeforeSend = e => received = e; + _fixture.SentryOptions.SetBeforeSend(e => received = e); var @event = new SentryEvent(); @@ -366,7 +366,7 @@ public void CaptureEvent_SamplingHighest_SendsEvent() // Largest value allowed. Should always send _fixture.SentryOptions.SampleRate = 1; SentryEvent received = null; - _fixture.SentryOptions.BeforeSend = e => received = e; + _fixture.SentryOptions.SetBeforeSend(e => received = e); var @event = new SentryEvent(); @@ -382,7 +382,7 @@ public void CaptureEvent_SamplingNull_DropsEvent() { _fixture.SentryOptions.SampleRate = null; SentryEvent received = null; - _fixture.SentryOptions.BeforeSend = e => received = e; + _fixture.SentryOptions.SetBeforeSend(e => received = e); var @event = new SentryEvent(); diff --git a/test/Sentry.Tests/SentryClientTests.verify.cs b/test/Sentry.Tests/SentryClientTests.verify.cs index 45e70e0ce7..4e8bd19fe2 100644 --- a/test/Sentry.Tests/SentryClientTests.verify.cs +++ b/test/Sentry.Tests/SentryClientTests.verify.cs @@ -7,7 +7,7 @@ public partial class SentryClientTests public Task CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb() { var error = new Exception("Exception message!"); - _fixture.SentryOptions.BeforeSend = _ => throw error; + _fixture.SentryOptions.SetBeforeSend(_ => throw error); var @event = new SentryEvent(); From a7131089cbe1539c7ddb1b91f5705acffb0a0345 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 2 May 2023 20:23:30 +1200 Subject: [PATCH 02/25] Added tests for new CaptureHint overloads taking a Hint parameter --- src/Sentry/Hint.cs | 20 ++++++-------------- test/Sentry.Tests/HubTests.cs | 19 +++++++++++++++++++ test/Sentry.Tests/SentryClientTests.cs | 18 ++++++++++++++++++ 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/Sentry/Hint.cs b/src/Sentry/Hint.cs index 087f147b0d..9a17d8f709 100644 --- a/src/Sentry/Hint.cs +++ b/src/Sentry/Hint.cs @@ -20,7 +20,7 @@ public object? this[string name] set => _internalStorage[name] = value; } - public void AddAttachments(params Attachment[] attachments) + internal void AddAttachmentsInternal(IEnumerable attachments) { if (attachments is not null) { @@ -28,13 +28,9 @@ public void AddAttachments(params Attachment[] attachments) } } - public void AddAttachments(ICollection attachments) - { - if (attachments is not null) - { - _attachments.AddRange(attachments); - } - } + public void AddAttachments(params Attachment[] attachments) => AddAttachmentsInternal(attachments); + + public void AddAttachments(ICollection attachments) => AddAttachmentsInternal(attachments); public ICollection Attachments => _attachments; @@ -51,10 +47,7 @@ public void AddAttachments(ICollection attachments) public IEnumerator> GetEnumerator() => ((IEnumerable>)_internalStorage).GetEnumerator(); - public T? GetAs(string name) where T : class? - => (this[name] is T typedHintValue) - ? typedHintValue - : null; + public T? GetAs(string name) where T : class? => (this[name] is T typedHintValue) ? typedHintValue : null; public bool IsSynchronized => ((ICollection)_internalStorage).IsSynchronized; @@ -71,8 +64,7 @@ public void AddAttachments(ICollection attachments) /// /// /// - public static Hint WithAttachments(params Attachment[] attachment) - => Hint.WithAttachments(attachment.ToList()); + public static Hint WithAttachments(params Attachment[] attachment) => Hint.WithAttachments(attachment.ToList()); /// /// Creates a new Hint with attachments. diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 7ec2da25c5..086a39ebfc 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -403,6 +403,25 @@ public void CaptureEvent_ActiveSession_UnhandledExceptionSessionEndedAsCrashed() )); } + [Fact] + public void CaptureEvent_Client_GetsHint() + { + // Arrange + var @event = new SentryEvent(); + var hint = new Hint(); + var hub = _fixture.GetSut(); + + // Act + hub.CaptureEvent(@event, hint); + + // Assert + _fixture.Client.Received(1).CaptureEvent( + Arg.Any(), + Arg.Is(h => h == hint), + Arg.Any() + ); + } + [Fact] public void AppDomainUnhandledExceptionIntegration_ActiveSession_UnhandledExceptionSessionEndedAsCrashed() { diff --git a/test/Sentry.Tests/SentryClientTests.cs b/test/Sentry.Tests/SentryClientTests.cs index a7bfef91f0..33691293af 100644 --- a/test/Sentry.Tests/SentryClientTests.cs +++ b/test/Sentry.Tests/SentryClientTests.cs @@ -301,6 +301,24 @@ public void CaptureEvent_ExceptionFilter_RecordsDiscard() .RecordDiscardedEvent(DiscardReason.EventProcessor, DataCategory.Error); } + [Fact] + public void CaptureEvent_BeforeSend_GetsHint() + { + Hint received = null; + _fixture.SentryOptions.SetBeforeSend((e, h) => { + received = h; + return e; + }); + + var @event = new SentryEvent(); + var hint = new Hint(); + + var sut = _fixture.GetSut(); + _ = sut.CaptureEvent(@event, hint); + + Assert.Same(hint, received); + } + [Fact] public void CaptureEvent_BeforeEvent_ModifyEvent() { From 36699e8cfa64841c1209753ed19a1cfa04f882e5 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 2 May 2023 21:59:16 +1200 Subject: [PATCH 03/25] Failed requests add a Hint for the HttpResponseMessage --- src/Sentry/Hint.cs | 61 ++++++++++++++++--- src/Sentry/HintTypes.cs | 12 ++++ src/Sentry/SentryFailedRequestHandler.cs | 4 +- src/Sentry/SentryOptions.cs | 2 +- test/Sentry.Tests/HintTests.cs | 15 ++--- .../Sentry.Tests/Protocol/TransactionTests.cs | 14 +++-- .../SentryFailedRequestHandlerTests.cs | 47 +++++++++++++- test/Sentry.Tests/SentrySdkTests.cs | 10 +-- 8 files changed, 132 insertions(+), 33 deletions(-) create mode 100644 src/Sentry/HintTypes.cs diff --git a/src/Sentry/Hint.cs b/src/Sentry/Hint.cs index 9a17d8f709..98f40603cb 100644 --- a/src/Sentry/Hint.cs +++ b/src/Sentry/Hint.cs @@ -6,18 +6,23 @@ namespace Sentry; /// -/// A hint for the to decide whether an event should be sent or cached. It also -/// holds data that should be injected into the event. +/// A hint that can be provided when capturing a or adding a . +/// Hints can be used to filter or modify events or breadcrumbs before they are sent to Sentry. /// public class Hint : ICollection, IEnumerable> { private readonly Dictionary _internalStorage = new(); private readonly List _attachments = new(); - public object? this[string name] + /// + /// Gets or sets additional values to be provided with the hint + /// + /// The key + /// The value with the specified key or null if none exist. + public object? this[string key] { - get => _internalStorage.GetValueOrDefault(name); - set => _internalStorage[name] = value; + get => _internalStorage.GetValueOrDefault(key); + set => _internalStorage[key] = value; } internal void AddAttachmentsInternal(IEnumerable attachments) @@ -28,35 +33,75 @@ internal void AddAttachmentsInternal(IEnumerable attachments) } } + /// + /// Adds one or more attachments to the Hint. + /// + /// public void AddAttachments(params Attachment[] attachments) => AddAttachmentsInternal(attachments); - public void AddAttachments(ICollection attachments) => AddAttachmentsInternal(attachments); + /// + /// Adds multiple attachments to the Hint. + /// + /// + public void AddAttachments(IEnumerable attachments) => AddAttachmentsInternal(attachments); + /// + /// Attachments added to the Hint. + /// public ICollection Attachments => _attachments; + /// + /// Clears any values stored in + /// public void Clear() => _internalStorage.Clear(); + /// + /// Checks if the specified key exists + /// + /// The key + /// True if the key exists. False otherwise. public bool ContainsKey(string key) => _internalStorage.ContainsKey(key); + /// public void CopyTo(Array array, int index) => ((ICollection)_internalStorage).CopyTo(array, index); + /// public int Count => _internalStorage.Count; IEnumerator IEnumerable.GetEnumerator() => _internalStorage.GetEnumerator(); + /// public IEnumerator> GetEnumerator() => ((IEnumerable>)_internalStorage).GetEnumerator(); - public T? GetAs(string name) where T : class? => (this[name] is T typedHintValue) ? typedHintValue : null; + /// + /// Gets the value with the specified key as type + /// + /// They expected value type + /// The key + /// A value of type if one exists with the specified key or null otherwise. + public T? GetValue(string key) where T : class? => (this[key] is T typedHintValue) ? typedHintValue : null; + /// public bool IsSynchronized => ((ICollection)_internalStorage).IsSynchronized; - public void Remove(string name) => _internalStorage.Remove(name); + /// + /// Remves the value with the specified key + /// + /// + public void Remove(string key) => _internalStorage.Remove(key); + /// + /// Gets or sets a Screenshot for the Hint + /// public Attachment? Screenshot { get; set; } + /// public object SyncRoot => ((ICollection)_internalStorage).SyncRoot; + /// + /// Gets or sets a ViewHierarchy for the Hint + /// public Attachment? ViewHierarchy { get; set; } /// diff --git a/src/Sentry/HintTypes.cs b/src/Sentry/HintTypes.cs new file mode 100644 index 0000000000..12439822f2 --- /dev/null +++ b/src/Sentry/HintTypes.cs @@ -0,0 +1,12 @@ +namespace Sentry; + +/// +/// Constants used to name Hints generated by the Sentry SDK +/// +public static class HintTypes +{ + /// + /// Used for HttpResponseMessage hints + /// + public const string HttpResponseMessage = "http-response-message"; +} diff --git a/src/Sentry/SentryFailedRequestHandler.cs b/src/Sentry/SentryFailedRequestHandler.cs index 283bab5387..4022e92f48 100644 --- a/src/Sentry/SentryFailedRequestHandler.cs +++ b/src/Sentry/SentryFailedRequestHandler.cs @@ -75,6 +75,8 @@ public void HandleResponse(HttpResponseMessage response) exception.SetSentryMechanism(MechanismType); var @event = new SentryEvent(exception); + var hint = new Hint(); + hint[HintTypes.HttpResponseMessage] = response; var sentryRequest = new Request { @@ -103,7 +105,7 @@ public void HandleResponse(HttpResponseMessage response) @event.Request = sentryRequest; @event.Contexts[Response.Type] = responseContext; - _hub.CaptureEvent(@event); + _hub.CaptureEvent(@event, hint); } } } diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index cc8c612f6d..da6b45a8b2 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -306,7 +306,7 @@ public float? SampleRate private Func? _beforeSend; - internal Func? BeforeSendInternal { get => _beforeSend; } + internal Func? BeforeSendInternal => _beforeSend; /// /// A callback to invoke before sending an event to Sentry diff --git a/test/Sentry.Tests/HintTests.cs b/test/Sentry.Tests/HintTests.cs index ae720c15a5..a8fc79a2b6 100644 --- a/test/Sentry.Tests/HintTests.cs +++ b/test/Sentry.Tests/HintTests.cs @@ -3,11 +3,12 @@ namespace Sentry.Tests; public class HintTests { private Attachment FakeAttachment(string name = "test.txt") - => new Attachment( + => new( AttachmentType.Default, new StreamAttachmentContent(new MemoryStream(new byte[] { 1 })), name, - null); + null + ); [Fact] public void AddAttachments_WithNullAttachments_DoesNothing() @@ -172,27 +173,27 @@ public void Count_ReturnsCorrectValue_WhenHintHasItems() } [Fact] - public void GetAs_WithNonExistingName_ReturnsNull() + public void GetValue_WithNonExistingKey_ReturnsNull() { // Arrange var hint = new Hint(); // Act - var result = hint.GetAs("non-existing"); + var result = hint.GetValue("non-existing"); // Assert Assert.Null(result); } [Fact] - public void GetAs_WithExistingName_ReturnsValue() + public void GetValue_WithExistingKey_ReturnsValue() { // Arrange var hint = new Hint(); hint["key"] = "value"; // Act - var result = hint.GetAs("key"); + var result = hint.GetValue("key"); // Assert Assert.Equal("value", result); @@ -219,7 +220,7 @@ public void GetEnumerator_ReturnsValidEnumreator() } [Fact] - public void Remove_WithExistingName_RemovesEntry() + public void Remove_WithExistingKey_RemovesEntry() { // Arrange var hint = new Hint(); diff --git a/test/Sentry.Tests/Protocol/TransactionTests.cs b/test/Sentry.Tests/Protocol/TransactionTests.cs index da447c669a..f03e929eef 100644 --- a/test/Sentry.Tests/Protocol/TransactionTests.cs +++ b/test/Sentry.Tests/Protocol/TransactionTests.cs @@ -296,11 +296,15 @@ public void Finish_LinksExceptionToEvent() // Assert transaction.Status.Should().Be(SpanStatus.InternalError); - client.Received(1).CaptureEvent(Arg.Is(e => - e.Contexts.Trace.TraceId == transaction.TraceId && - e.Contexts.Trace.SpanId == transaction.SpanId && - e.Contexts.Trace.ParentSpanId == transaction.ParentSpanId - ), Arg.Any()); + client.Received(1).CaptureEvent( + Arg.Is(e => + e.Contexts.Trace.TraceId == transaction.TraceId && + e.Contexts.Trace.SpanId == transaction.SpanId && + e.Contexts.Trace.ParentSpanId == transaction.ParentSpanId + ), + Arg.Any(), + Arg.Any() + ); } [Fact] diff --git a/test/Sentry.Tests/SentryFailedRequestHandlerTests.cs b/test/Sentry.Tests/SentryFailedRequestHandlerTests.cs index 6582f82e23..06a467267b 100644 --- a/test/Sentry.Tests/SentryFailedRequestHandlerTests.cs +++ b/test/Sentry.Tests/SentryFailedRequestHandlerTests.cs @@ -119,7 +119,11 @@ public void HandleResponse_Capture_FailedRequest() sut.HandleResponse(response); // Assert - _hub.Received(1).CaptureEvent(Arg.Any(), Arg.Any()); + _hub.Received(1).CaptureEvent( + Arg.Any(), + Arg.Any(), + Arg.Any() + ); } [Fact] @@ -145,7 +149,10 @@ public void HandleResponse_Capture_RequestAndResponse() // Act SentryEvent @event = null; - _hub.CaptureEvent(Arg.Do(e => @event = e)); + _hub.CaptureEvent( + Arg.Do(e => @event = e), + Arg.Any() + ); sut.HandleResponse(response); // Assert @@ -192,7 +199,10 @@ public void HandleResponse_Capture_Default_SkipCookiesAndHeaders() // Act SentryEvent @event = null; - _hub.CaptureEvent(Arg.Do(e => @event = e)); + _hub.CaptureEvent( + Arg.Do(e => @event = e), + Arg.Any() + ); sut.HandleResponse(response); // Assert @@ -205,4 +215,35 @@ public void HandleResponse_Capture_Default_SkipCookiesAndHeaders() @event.Contexts.Response.Cookies.Should().BeNullOrEmpty(); } } + + [Fact] + public void HandleResponse_Hint_Response() + { + // Arrange + var options = new SentryOptions + { + CaptureFailedRequests = true + }; + var sut = GetSut(options); + + var response = InternalServerErrorResponse(); // This is in the range + response.RequestMessage = new HttpRequestMessage(HttpMethod.Post, "http://foo/bar"); + + // Act + Hint hint = null; + _hub.CaptureEvent( + Arg.Any(), + Arg.Do(h => hint = h) + ); + sut.HandleResponse(response); + + // Assert + using (new AssertionScope()) + { + hint.Should().NotBeNull(); + + // Response should be captured + hint[HintTypes.HttpResponseMessage].Should().Be(response); + } + } } diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index fa1f5debd7..e878bcfa80 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -114,10 +114,7 @@ public void Init_InvalidDsnEnvironmentVariable_Throws() { // If the variable was set, to non empty string but value is broken, better crash than silently disable var ex = Assert.Throws(() => - SentrySdk.Init(o => - { - o.FakeSettings().EnvironmentVariables[DsnEnvironmentVariable] = InvalidDsn; - })); + SentrySdk.Init(o => o.FakeSettings().EnvironmentVariables[DsnEnvironmentVariable] = InvalidDsn)); Assert.Equal("Invalid DSN: A Project Id is required.", ex.Message); } @@ -125,10 +122,7 @@ public void Init_InvalidDsnEnvironmentVariable_Throws() [Fact] public void Init_DisableDsnEnvironmentVariable_DisablesSdk() { - using var _ = SentrySdk.Init(o => - { - o.FakeSettings().EnvironmentVariables[DsnEnvironmentVariable] = Constants.DisableSdkDsnValue; - }); + using var _ = SentrySdk.Init(o => o.FakeSettings().EnvironmentVariables[DsnEnvironmentVariable] = Constants.DisableSdkDsnValue); Assert.False(SentrySdk.IsEnabled); } From baed55bf8b5171ed2fcf1cc21905b5146a051549 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 4 May 2023 11:02:01 +1200 Subject: [PATCH 04/25] Added BeforeBreadcrumb Hint support (for breadcrumbs on the scope only) --- CHANGELOG.md | 2 ++ src/Sentry/Scope.cs | 13 ++++++++++--- src/Sentry/SentryOptions.cs | 15 +++++++++++++++ test/Sentry.Tests/ScopeTests.cs | 21 +++++++++++++++++++++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9daf68e8ea..f7cffc6165 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Add Hint support ([#2351](https://github.com/getsentry/sentry-dotnet/pull/2351)) + ### Features - Initial work to support profiling in a future release. ([#2206](https://github.com/getsentry/sentry-dotnet/pull/2206)) diff --git a/src/Sentry/Scope.cs b/src/Sentry/Scope.cs index 6b4a0f7075..c99a53a204 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -244,11 +244,18 @@ internal Scope() } /// - public void AddBreadcrumb(Breadcrumb breadcrumb) + public void AddBreadcrumb(Breadcrumb breadcrumb) => AddBreadcrumb(breadcrumb, new Hint()); + + /// + /// Adds a breadcrumb with a hint. + /// + /// The breadcrumb + /// A hint for use in the BeforeBreadcrumb callback + public void AddBreadcrumb(Breadcrumb breadcrumb, Hint hint) { - if (Options.BeforeBreadcrumb is { } beforeBreadcrumb) + if (Options.BeforeBreadcrumbInternal is { } beforeBreadcrumb) { - if (beforeBreadcrumb(breadcrumb) is { } processedBreadcrumb) + if (beforeBreadcrumb(breadcrumb, hint) is { } processedBreadcrumb) { breadcrumb = processedBreadcrumb; } diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index da6b45a8b2..41d5ef78dc 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -343,14 +343,29 @@ public void SetBeforeSend(Func beforeSend) /// public Func? BeforeSendTransaction { get; set; } + private Func? _beforeBreadcrumb; + + internal Func? BeforeBreadcrumbInternal => _beforeBreadcrumb; + /// /// A callback invoked when a breadcrumb is about to be stored. /// /// /// Gives a chance to inspect and modify/reject a breadcrumb. /// + [Obsolete("This property will be removed in a future version. Use SetBeforeBreadcrumb instead.")] public Func? BeforeBreadcrumb { get; set; } + public void SetBeforeBreadcrumb(Func beforeBreadcrumb) + { + _beforeBreadcrumb = (e, _) => beforeBreadcrumb(e); + } + + public void SetBeforeBreadcrumb(Func beforeBreadcrumb) + { + _beforeBreadcrumb = beforeBreadcrumb; + } + private int _maxQueueItems = 30; /// diff --git a/test/Sentry.Tests/ScopeTests.cs b/test/Sentry.Tests/ScopeTests.cs index d7de09a391..0114b0090d 100644 --- a/test/Sentry.Tests/ScopeTests.cs +++ b/test/Sentry.Tests/ScopeTests.cs @@ -309,6 +309,27 @@ public void AddBreadcrumb__AddBreadcrumb_RespectLimits(int initialCount, int max Assert.Equal(expectedCount, scope.Breadcrumbs.Count); } + [Fact] + public void AddBreadcrumb_BeforeAddBreadcrumb_ReceivesHint() + { + // Arrange + var options = new SentryOptions(); + Hint receivedHint = null; + options.SetBeforeBreadcrumb((breadcrumb, hint) => + { + receivedHint = hint; + return breadcrumb; + }); + var scope = new Scope(options); + + // Act + var expectedHint = new Hint(); + scope.AddBreadcrumb(new Breadcrumb(), expectedHint); + + // Assert + receivedHint.Should().Equal(expectedHint); + } + [Theory] [InlineData("123@123.com", null, null, true)] [InlineData("123@123.com", null, null, false)] From 836ac0972e959a6de22e17e234338d204d71607a Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 4 May 2023 12:20:59 +1200 Subject: [PATCH 05/25] - Fixed ScopeExtensionTests - Added missing XML docs on SentryOptions --- src/Sentry/SentryOptions.cs | 37 +++++++++++++----- ...piApprovalTests.Run.DotNet7_0.verified.txt | 39 +++++++++++++++++++ .../Protocol/ScopeExtensionsTests.cs | 6 +-- ...Throws_ErrorToEventBreadcrumb.verified.txt | 3 +- 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index 41d5ef78dc..c32e9556df 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -309,13 +309,9 @@ public float? SampleRate internal Func? BeforeSendInternal => _beforeSend; /// - /// A callback to invoke before sending an event to Sentry + /// Configures a callback to invoke before sending an event to Sentry /// - /// - /// The return of this event will be sent to Sentry. This allows the application - /// a chance to inspect and/or modify the event before it's sent. If the event - /// should not be sent at all, return null from the callback. - /// + /// [Obsolete("This property will be removed in a future version. Use SetBeforeSend instead.")] public Func? BeforeSend { @@ -323,11 +319,23 @@ public float? SampleRate set => _beforeSend = value is null ? null : (e, _) => value(e); } + /// + /// Configures a callback to invoke before sending an event to Sentry + /// + /// public void SetBeforeSend(Func beforeSend) { _beforeSend = (e, _) => beforeSend(e); } + /// + /// Configures a callback function to be invoked before sending an event to Sentry + /// + /// + /// The event returned by this callback will be sent to Sentry. This allows the + /// application a chance to inspect and/or modify the event before it's sent. If the + /// event should not be sent at all, return null from the callback. + /// public void SetBeforeSend(Func beforeSend) { _beforeSend = beforeSend; @@ -348,19 +356,28 @@ public void SetBeforeSend(Func beforeSend) internal Func? BeforeBreadcrumbInternal => _beforeBreadcrumb; /// - /// A callback invoked when a breadcrumb is about to be stored. + /// Sets a callback function to be invoked when a breadcrumb is about to be stored. /// - /// - /// Gives a chance to inspect and modify/reject a breadcrumb. - /// + /// [Obsolete("This property will be removed in a future version. Use SetBeforeBreadcrumb instead.")] public Func? BeforeBreadcrumb { get; set; } + /// + /// Sets a callback function to be invoked when a breadcrumb is about to be stored. + /// + /// public void SetBeforeBreadcrumb(Func beforeBreadcrumb) { _beforeBreadcrumb = (e, _) => beforeBreadcrumb(e); } + /// + /// Sets a callback function to be invoked when a breadcrumb is about to be stored. + /// + /// + /// Gives a chance to inspect and modify the breadcrumb. If null is returned, the + /// breadcrumb will be discarded. Otherwise the result of the callback will be stored. + /// public void SetBeforeBreadcrumb(Func beforeBreadcrumb) { _beforeBreadcrumb = beforeBreadcrumb; diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt index 9816e4fab2..d59221d416 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -138,6 +138,32 @@ namespace Sentry { public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } } + public class Hint : System.Collections.Generic.IEnumerable>, System.Collections.ICollection, System.Collections.IEnumerable + { + public Hint() { } + public System.Collections.Generic.ICollection Attachments { get; } + public int Count { get; } + public bool IsSynchronized { get; } + public object? this[string key] { get; set; } + public Sentry.Attachment? Screenshot { get; set; } + public object SyncRoot { get; } + public Sentry.Attachment? ViewHierarchy { get; set; } + public void AddAttachments(params Sentry.Attachment[] attachments) { } + public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } + public void Clear() { } + public bool ContainsKey(string key) { } + public void CopyTo(System.Array array, int index) { } + public System.Collections.Generic.IEnumerator> GetEnumerator() { } + public T? GetValue(string key) + where T : class? { } + public void Remove(string key) { } + public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } + public static Sentry.Hint WithAttachments(System.Collections.Generic.ICollection attachments) { } + } + public static class HintTypes + { + public const string HttpResponseMessage = "http-response-message"; + } public readonly struct HttpStatusCodeRange : System.IEquatable { public HttpStatusCodeRange(int statusCode) { } @@ -235,6 +261,7 @@ namespace Sentry { bool IsEnabled { get; } Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null); + Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); void CaptureTransaction(Sentry.Transaction transaction); void CaptureUserFeedback(Sentry.UserFeedback userFeedback); @@ -410,6 +437,7 @@ namespace Sentry public Sentry.User User { get; set; } public void AddAttachment(Sentry.Attachment attachment) { } public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { } + public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb, Sentry.Hint hint) { } public void Apply(Sentry.IEventLike other) { } public void Apply(Sentry.Scope other) { } public void Apply(object state) { } @@ -455,6 +483,7 @@ namespace Sentry public SentryClient(Sentry.SentryOptions options) { } public bool IsEnabled { get; } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } @@ -566,7 +595,10 @@ namespace Sentry public bool AutoSessionTracking { get; set; } public System.TimeSpan AutoSessionTrackingInterval { get; set; } public Sentry.Extensibility.IBackgroundWorker? BackgroundWorker { get; set; } + [System.Obsolete("This property will be removed in a future version. Use SetBeforeBreadcrumb instea" + + "d.")] public System.Func? BeforeBreadcrumb { get; set; } + [System.Obsolete("This property will be removed in a future version. Use SetBeforeSend instead.")] public System.Func? BeforeSend { get; set; } public System.Func? BeforeSendTransaction { get; set; } public string? CacheDirectoryPath { get; set; } @@ -620,6 +652,10 @@ namespace Sentry public Sentry.Extensibility.ITransport? Transport { get; set; } public bool UseAsyncFileIO { get; set; } public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSend(System.Func beforeSend) { } } public static class SentryOptionsExtensions { @@ -677,6 +713,7 @@ namespace Sentry public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope) { } public static Sentry.SentryId CaptureException(System.Exception exception) { } public static Sentry.SentryId CaptureException(System.Exception exception, System.Action configureScope) { } public static Sentry.SentryId CaptureMessage(string message, Sentry.SentryLevel level = 1) { } @@ -1157,6 +1194,7 @@ namespace Sentry.Extensibility public void BindException(System.Exception exception, Sentry.ISpan span) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } @@ -1193,6 +1231,7 @@ namespace Sentry.Extensibility public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope) { } public Sentry.SentryId CaptureException(System.Exception exception) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } diff --git a/test/Sentry.Tests/Protocol/ScopeExtensionsTests.cs b/test/Sentry.Tests/Protocol/ScopeExtensionsTests.cs index 36c67314ed..d6fb093701 100644 --- a/test/Sentry.Tests/Protocol/ScopeExtensionsTests.cs +++ b/test/Sentry.Tests/Protocol/ScopeExtensionsTests.cs @@ -311,7 +311,7 @@ public void SetTags_DuplicateTag_LastSet() [Fact] public void AddBreadcrumb_BeforeBreadcrumbDropsCrumb_NoBreadcrumbInEvent() { - _fixture.ScopeOptions.BeforeBreadcrumb = _ => null; + _fixture.ScopeOptions.SetBeforeBreadcrumb((_, _) => null); var sut = _fixture.GetSut(); sut.AddBreadcrumb("no expected"); @@ -323,7 +323,7 @@ public void AddBreadcrumb_BeforeBreadcrumbDropsCrumb_NoBreadcrumbInEvent() public void AddBreadcrumb_BeforeBreadcrumbNewCrumb_NewCrumbUsed() { var expected = new Breadcrumb(); - _fixture.ScopeOptions.BeforeBreadcrumb = _ => expected; + _fixture.ScopeOptions.SetBeforeBreadcrumb((_, _) => expected); var sut = _fixture.GetSut(); sut.AddBreadcrumb("no expected"); @@ -335,7 +335,7 @@ public void AddBreadcrumb_BeforeBreadcrumbNewCrumb_NewCrumbUsed() public void AddBreadcrumb_BeforeBreadcrumbReturns_SameCrumb() { var expected = new Breadcrumb(); - _fixture.ScopeOptions.BeforeBreadcrumb = c => c; + _fixture.ScopeOptions.SetBeforeBreadcrumb((c, _) => c); var sut = _fixture.GetSut(); sut.AddBreadcrumb(expected); diff --git a/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt index 1a3dbe7c04..6dcf0fd76a 100644 --- a/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt +++ b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt @@ -6,9 +6,10 @@ message: Exception message!, stackTrace: at Task Sentry.Tests.SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb() +at void Sentry.SentryOptions.SetBeforeSend(...) at SentryEvent Sentry.SentryClient.BeforeSend(...) }, Category: SentryClient, Level: error } -] +] \ No newline at end of file From 7b4efa19655970b8198b40ec5aaf0242809bb5e3 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 4 May 2023 12:29:00 +1200 Subject: [PATCH 06/25] Added stub of Android platform code to enable builds to complete --- .../Callbacks/BeforeBreadcrumbCallback.cs | 7 +++-- .../Android/Callbacks/BeforeSendCallback.cs | 7 +++-- .../Extensions/AttachmentExtensions.cs | 23 ++++++++++++++ .../Android/Extensions/HintExtensions.cs | 31 +++++++++++++++++++ src/Sentry/Platforms/Android/SentrySdk.cs | 2 +- 5 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 src/Sentry/Platforms/Android/Extensions/AttachmentExtensions.cs create mode 100644 src/Sentry/Platforms/Android/Extensions/HintExtensions.cs diff --git a/src/Sentry/Platforms/Android/Callbacks/BeforeBreadcrumbCallback.cs b/src/Sentry/Platforms/Android/Callbacks/BeforeBreadcrumbCallback.cs index dd0f5ddd64..870292ad62 100644 --- a/src/Sentry/Platforms/Android/Callbacks/BeforeBreadcrumbCallback.cs +++ b/src/Sentry/Platforms/Android/Callbacks/BeforeBreadcrumbCallback.cs @@ -4,9 +4,9 @@ namespace Sentry.Android.Callbacks; internal class BeforeBreadcrumbCallback : JavaObject, JavaSdk.SentryOptions.IBeforeBreadcrumbCallback { - private readonly Func _beforeBreadcrumb; + private readonly Func _beforeBreadcrumb; - public BeforeBreadcrumbCallback(Func beforeBreadcrumb) + public BeforeBreadcrumbCallback(Func beforeBreadcrumb) { _beforeBreadcrumb = beforeBreadcrumb; } @@ -17,7 +17,8 @@ public BeforeBreadcrumbCallback(Func beforeBreadcrumb) // https://github.com/getsentry/sentry-dotnet/issues/1469 var breadcrumb = b.ToBreadcrumb(); - var result = _beforeBreadcrumb.Invoke(breadcrumb); + var hint = h.ToHint(); + var result = _beforeBreadcrumb.Invoke(breadcrumb, hint); if (result == breadcrumb) { diff --git a/src/Sentry/Platforms/Android/Callbacks/BeforeSendCallback.cs b/src/Sentry/Platforms/Android/Callbacks/BeforeSendCallback.cs index 45c1d13ac2..2052925c0c 100644 --- a/src/Sentry/Platforms/Android/Callbacks/BeforeSendCallback.cs +++ b/src/Sentry/Platforms/Android/Callbacks/BeforeSendCallback.cs @@ -4,12 +4,12 @@ namespace Sentry.Android.Callbacks; internal class BeforeSendCallback : JavaObject, JavaSdk.SentryOptions.IBeforeSendCallback { - private readonly Func _beforeSend; + private readonly Func _beforeSend; private readonly SentryOptions _options; private readonly JavaSdk.SentryOptions _javaOptions; public BeforeSendCallback( - Func beforeSend, + Func beforeSend, SentryOptions options, JavaSdk.SentryOptions javaOptions) { @@ -24,7 +24,8 @@ public BeforeSendCallback( // https://github.com/getsentry/sentry-dotnet/issues/1469 var evnt = e.ToSentryEvent(_javaOptions); - var result = _beforeSend.Invoke(evnt); + var hint = h.ToHint(); + var result = _beforeSend?.Invoke(evnt, hint); return result?.ToJavaSentryEvent(_options, _javaOptions); } } diff --git a/src/Sentry/Platforms/Android/Extensions/AttachmentExtensions.cs b/src/Sentry/Platforms/Android/Extensions/AttachmentExtensions.cs new file mode 100644 index 0000000000..43d7b8aa7c --- /dev/null +++ b/src/Sentry/Platforms/Android/Extensions/AttachmentExtensions.cs @@ -0,0 +1,23 @@ +namespace Sentry.Android.Extensions; + +internal static class AttachmentExtensions +{ + public static Attachment ToAttachment(this JavaSdk.Attachment attachment) + { + // TODO: Convert JavaSdk.Attachment to Sentry.Attachment. + // One way to do this might be to serialise the JavaSdk.Attachment as + // JSON and then deserialise it as a Sentry.Attachment. It looks like + // Attachments aren't designed to be serialised directly though (they + // get stuffed into EnvelopeItems instead)... and I'm not sure if we'd + // have access to the JSON serialiser from here or how the data in + // JavaSdk.Attachment.GetBytes() is encoded. + throw new NotImplementedException(); + } + + public static JavaSdk.Attachment ToJavaAttachment(this Attachment attachment) + { + // TODO: Convert Sentry.Attachment to JavaSdk.Attachment. + // Same problem as ToAttachment() above but in reverse. + throw new NotImplementedException(); + } +} diff --git a/src/Sentry/Platforms/Android/Extensions/HintExtensions.cs b/src/Sentry/Platforms/Android/Extensions/HintExtensions.cs new file mode 100644 index 0000000000..ef3c6dee8d --- /dev/null +++ b/src/Sentry/Platforms/Android/Extensions/HintExtensions.cs @@ -0,0 +1,31 @@ +namespace Sentry.Android.Extensions; + +internal static class HintExtensions +{ + public static Hint ToHint(this JavaSdk.Hint javaHint) + { + // Note the JavaSDK doesn't expose the internal hint storage in any way that is iterable, + // so unless you know the key, you can't get the value. This prevents us from converting + // anything in the JavaSdk.Hint except the explicitly named properties: + // Attachments, Screenshot and ViewHierarchy + + var dotnetHint = new Hint(); + // TODO: Implement ToAttachment + //dotnetHint.Screenshot = (javaHint.Screenshot is { } screenshot) ? screenshot.ToAttachment() : null; + //dotnetHint.ViewHierarchy = (javaHint.ViewHierarchy is { } viewhierarchy) ? viewhierarchy.ToAttachment() : null; + //dotnetHint.AddAttachments(javaHint.Attachments.Select(x => x.ToAttachment())); + + return dotnetHint; + } + + public static JavaSdk.Hint ToJavaHint(this Hint dotnetHint) + { + var javaHint = new JavaSdk.Hint(); + // TODO: Implement ToJavaAttachment + //javaHint.Screenshot = (dotnetHint.Screenshot is { } screenshot) ? screenshot.ToJavaAttachment() : null; + //javaHint.ViewHierarchy = (dotnetHint.ViewHierarchy is { } viewhierarchy) ? viewhierarchy.ToJavaAttachment() : null; + //javaHint.AddAttachments(dotnetHint.Attachments.Select(x => x.ToJavaAttachment()).ToList()); + + return javaHint; + } +} diff --git a/src/Sentry/Platforms/Android/SentrySdk.cs b/src/Sentry/Platforms/Android/SentrySdk.cs index b08a2c0e07..9b22385304 100644 --- a/src/Sentry/Platforms/Android/SentrySdk.cs +++ b/src/Sentry/Platforms/Android/SentrySdk.cs @@ -102,7 +102,7 @@ private static void InitSentryAndroidSdk(SentryOptions options) }); } - if (options.BeforeBreadcrumb is { } beforeBreadcrumb) + if (options.BeforeBreadcrumbInternal is { } beforeBreadcrumb) { o.BeforeBreadcrumb = new BeforeBreadcrumbCallback(beforeBreadcrumb); } From 52e5a33019c32fda51eb390da30c818958389565 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 4 May 2023 15:17:26 +1200 Subject: [PATCH 07/25] Sentry.Samples.Console.Customized now demonstrates using hints with breadcrumbs Added hint support to SentrySdk.AddBreadcrumb Fixed StackOverflow exception calling HubExtensions.CaptureEventInternal --- .../Program.cs | 16 ++++++- .../Program.cs | 4 +- src/Sentry/Hint.cs | 10 +++++ src/Sentry/HubExtensions.cs | 43 +++++++++++++++---- .../Callbacks/BeforeBreadcrumbCallback.cs | 2 +- src/Sentry/SentrySdk.cs | 10 +++++ .../ApiApprovalTests.Run.Core3_1.verified.txt | 42 ++++++++++++++++++ ...piApprovalTests.Run.DotNet6_0.verified.txt | 42 ++++++++++++++++++ ...piApprovalTests.Run.DotNet7_0.verified.txt | 3 ++ .../ApiApprovalTests.Run.Net4_8.verified.txt | 42 ++++++++++++++++++ test/Sentry.Tests/HubTests.cs | 6 ++- test/Sentry.Tests/SentrySdkTests.cs | 2 +- 12 files changed, 207 insertions(+), 15 deletions(-) diff --git a/samples/Sentry.Samples.Console.Customized/Program.cs b/samples/Sentry.Samples.Console.Customized/Program.cs index a2e4810e79..811270165e 100644 --- a/samples/Sentry.Samples.Console.Customized/Program.cs +++ b/samples/Sentry.Samples.Console.Customized/Program.cs @@ -55,7 +55,7 @@ await SentrySdk.ConfigureScopeAsync(async scope => ); // Allows inspecting and modifying, returning a new or simply rejecting (returning null) - o.BeforeBreadcrumb = crumb => + o.SetBeforeBreadcrumb((crumb, hint) => { // Don't add breadcrumbs with message containing: if (crumb.Message?.Contains("bad breadcrumb") == true) @@ -63,8 +63,15 @@ await SentrySdk.ConfigureScopeAsync(async scope => return null; } + // Replace breadcrumbs entirely incase of a drastic hint + const string replaceBreadcrumb = "don't trust this breadcrumb"; + if (hint.ContainsKey(replaceBreadcrumb)) + { + return new Breadcrumb(hint.GetValue(replaceBreadcrumb), null, null, null, BreadcrumbLevel.Critical); + } + return crumb; - }; + }); // Ignore exception by its type: o.AddExceptionFilterForType(); @@ -103,6 +110,11 @@ await SentrySdk.ConfigureScopeAsync(async scope => SentrySdk.AddBreadcrumb( "A 'bad breadcrumb' that will be rejected because of 'BeforeBreadcrumb callback above.'"); + SentrySdk.AddBreadcrumb( + new Breadcrumb("A breadcrumb that will be replaced by the 'BeforeBreadcrumb callback because of the hint", null), + new Hint("don't trust this breadcrumb", "trust this instead") + ); + // Data added to the root scope (no PushScope called up to this point) // The modifications done here will affect all events sent and will propagate to child scopes. await SentrySdk.ConfigureScopeAsync(async scope => diff --git a/samples/Sentry.Samples.Console.Profiling/Program.cs b/samples/Sentry.Samples.Console.Profiling/Program.cs index f547ef92b8..1f1980b3ed 100644 --- a/samples/Sentry.Samples.Console.Profiling/Program.cs +++ b/samples/Sentry.Samples.Console.Profiling/Program.cs @@ -56,7 +56,7 @@ await SentrySdk.ConfigureScopeAsync(async scope => ); // Allows inspecting and modifying, returning a new or simply rejecting (returning null) - o.BeforeBreadcrumb = crumb => + o.SetBeforeBreadcrumb(crumb => { // Don't add breadcrumbs with message containing: if (crumb.Message?.Contains("bad breadcrumb") == true) @@ -65,7 +65,7 @@ await SentrySdk.ConfigureScopeAsync(async scope => } return crumb; - }; + }); // Ignore exception by its type: o.AddExceptionFilterForType(); diff --git a/src/Sentry/Hint.cs b/src/Sentry/Hint.cs index 98f40603cb..939da8db6a 100644 --- a/src/Sentry/Hint.cs +++ b/src/Sentry/Hint.cs @@ -14,6 +14,16 @@ public class Hint : ICollection, IEnumerable> private readonly Dictionary _internalStorage = new(); private readonly List _attachments = new(); + public Hint() + { + } + + public Hint(string key, object? value) + : this() + { + _internalStorage[key] = value; + } + /// /// Gets or sets additional values to be provided with the hint /// diff --git a/src/Sentry/HubExtensions.cs b/src/Sentry/HubExtensions.cs index 5a575723e8..0ca57e0b24 100644 --- a/src/Sentry/HubExtensions.cs +++ b/src/Sentry/HubExtensions.cs @@ -111,14 +111,41 @@ public static void AddBreadcrumb( return; } + var breadcrumb = new Breadcrumb( + (clock ?? SystemClock.Clock).GetUtcNow(), + message, + type, + data != null ? new Dictionary(data) : null, + category, + level + ); + + hub.AddBreadcrumb( + breadcrumb + ); + } + + /// + /// Adds a breadcrumb to the current scope. + /// + /// The Hub which holds the scope stack. + /// The breadcrumb to add + /// An hint provided with the breadcrumb in the BeforeBreadcrumb callback + public static void AddBreadcrumb( + this IHub hub, + Breadcrumb breadcrumb, + Hint? hint = null + ) + { + // Not to throw on code that ignores nullability warnings. + if (hub.IsNull()) + { + return; + } + hub.ConfigureScope( - s => s.AddBreadcrumb( - (clock ?? SystemClock.Clock).GetUtcNow(), - message, - category, - type, - data != null ? new Dictionary(data) : null, - level)); + s => s.AddBreadcrumb(breadcrumb, hint ?? new Hint()) + ); } /// @@ -159,7 +186,7 @@ internal static SentryId CaptureExceptionInternal(this IHub hub, Exception ex) = hub.CaptureEventInternal(new SentryEvent(ex)); internal static SentryId CaptureEventInternal(this IHub hub, SentryEvent evt) => - hub is IHubEx hubEx ? hubEx.CaptureEventInternal(evt) : hub.CaptureEvent(evt); + hub is IHubEx hubEx ? hubEx.CaptureEventInternal(evt, null, null) : hub.CaptureEvent(evt); /// /// Captures the exception with a configurable scope callback. diff --git a/src/Sentry/Platforms/Android/Callbacks/BeforeBreadcrumbCallback.cs b/src/Sentry/Platforms/Android/Callbacks/BeforeBreadcrumbCallback.cs index 870292ad62..17472a06ad 100644 --- a/src/Sentry/Platforms/Android/Callbacks/BeforeBreadcrumbCallback.cs +++ b/src/Sentry/Platforms/Android/Callbacks/BeforeBreadcrumbCallback.cs @@ -24,7 +24,7 @@ public BeforeBreadcrumbCallback(Func beforeBreadc { // The result is the same object as was input, and all properties are immutable, // so we can return the original Java object for better performance. - return b; + return b!; } return result?.ToJavaBreadcrumb(); diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index 0a050b29d7..cb02e506b8 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -311,6 +311,16 @@ public static void AddBreadcrumb( BreadcrumbLevel level = default) => CurrentHub.AddBreadcrumb(clock, message, category, type, data, level); + /// + /// Adds a breadcrumb to the current Scope. + /// + /// The breadcrumb to be added + /// A hint providing additional context that can be used in the BeforeBreadcrumb callback + /// + [DebuggerStepThrough] + public static void AddBreadcrumb(Breadcrumb breadcrumb, Hint? hint = null) + => CurrentHub.AddBreadcrumb(breadcrumb, hint); + /// /// Runs the callback within a new scope. /// diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt index bc4762d774..059a4f2471 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt @@ -138,6 +138,33 @@ namespace Sentry { public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } } + public class Hint : System.Collections.Generic.IEnumerable>, System.Collections.ICollection, System.Collections.IEnumerable + { + public Hint() { } + public Hint(string key, object? value) { } + public System.Collections.Generic.ICollection Attachments { get; } + public int Count { get; } + public bool IsSynchronized { get; } + public object? this[string key] { get; set; } + public Sentry.Attachment? Screenshot { get; set; } + public object SyncRoot { get; } + public Sentry.Attachment? ViewHierarchy { get; set; } + public void AddAttachments(params Sentry.Attachment[] attachments) { } + public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } + public void Clear() { } + public bool ContainsKey(string key) { } + public void CopyTo(System.Array array, int index) { } + public System.Collections.Generic.IEnumerator> GetEnumerator() { } + public T? GetValue(string key) + where T : class? { } + public void Remove(string key) { } + public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } + public static Sentry.Hint WithAttachments(System.Collections.Generic.ICollection attachments) { } + } + public static class HintTypes + { + public const string HttpResponseMessage = "http-response-message"; + } public readonly struct HttpStatusCodeRange : System.IEquatable { public HttpStatusCodeRange(int statusCode) { } @@ -157,6 +184,7 @@ namespace Sentry } public static class HubExtensions { + public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.Hint? hint = null) { } public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action configureScope) { } @@ -235,6 +263,7 @@ namespace Sentry { bool IsEnabled { get; } Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null); + Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); void CaptureTransaction(Sentry.Transaction transaction); void CaptureUserFeedback(Sentry.UserFeedback userFeedback); @@ -410,6 +439,7 @@ namespace Sentry public Sentry.User User { get; set; } public void AddAttachment(Sentry.Attachment attachment) { } public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { } + public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb, Sentry.Hint hint) { } public void Apply(Sentry.IEventLike other) { } public void Apply(Sentry.Scope other) { } public void Apply(object state) { } @@ -455,6 +485,7 @@ namespace Sentry public SentryClient(Sentry.SentryOptions options) { } public bool IsEnabled { get; } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } @@ -565,7 +596,10 @@ namespace Sentry public bool AutoSessionTracking { get; set; } public System.TimeSpan AutoSessionTrackingInterval { get; set; } public Sentry.Extensibility.IBackgroundWorker? BackgroundWorker { get; set; } + [System.Obsolete("This property will be removed in a future version. Use SetBeforeBreadcrumb instea" + + "d.")] public System.Func? BeforeBreadcrumb { get; set; } + [System.Obsolete("This property will be removed in a future version. Use SetBeforeSend instead.")] public System.Func? BeforeSend { get; set; } public System.Func? BeforeSendTransaction { get; set; } public string? CacheDirectoryPath { get; set; } @@ -619,6 +653,10 @@ namespace Sentry public Sentry.Extensibility.ITransport? Transport { get; set; } public bool UseAsyncFileIO { get; set; } public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSend(System.Func beforeSend) { } } public static class SentryOptionsExtensions { @@ -669,6 +707,7 @@ namespace Sentry { public static bool IsEnabled { get; } public static Sentry.SentryId LastEventId { get; } + public static void AddBreadcrumb(Sentry.Breadcrumb breadcrumb, Sentry.Hint? hint = null) { } public static void AddBreadcrumb(string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void BindClient(Sentry.ISentryClient client) { } @@ -676,6 +715,7 @@ namespace Sentry public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope) { } public static Sentry.SentryId CaptureException(System.Exception exception) { } public static Sentry.SentryId CaptureException(System.Exception exception, System.Action configureScope) { } public static Sentry.SentryId CaptureMessage(string message, Sentry.SentryLevel level = 1) { } @@ -1156,6 +1196,7 @@ namespace Sentry.Extensibility public void BindException(System.Exception exception, Sentry.ISpan span) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } @@ -1192,6 +1233,7 @@ namespace Sentry.Extensibility public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope) { } public Sentry.SentryId CaptureException(System.Exception exception) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt index 9816e4fab2..19a60f992f 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -138,6 +138,33 @@ namespace Sentry { public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } } + public class Hint : System.Collections.Generic.IEnumerable>, System.Collections.ICollection, System.Collections.IEnumerable + { + public Hint() { } + public Hint(string key, object? value) { } + public System.Collections.Generic.ICollection Attachments { get; } + public int Count { get; } + public bool IsSynchronized { get; } + public object? this[string key] { get; set; } + public Sentry.Attachment? Screenshot { get; set; } + public object SyncRoot { get; } + public Sentry.Attachment? ViewHierarchy { get; set; } + public void AddAttachments(params Sentry.Attachment[] attachments) { } + public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } + public void Clear() { } + public bool ContainsKey(string key) { } + public void CopyTo(System.Array array, int index) { } + public System.Collections.Generic.IEnumerator> GetEnumerator() { } + public T? GetValue(string key) + where T : class? { } + public void Remove(string key) { } + public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } + public static Sentry.Hint WithAttachments(System.Collections.Generic.ICollection attachments) { } + } + public static class HintTypes + { + public const string HttpResponseMessage = "http-response-message"; + } public readonly struct HttpStatusCodeRange : System.IEquatable { public HttpStatusCodeRange(int statusCode) { } @@ -157,6 +184,7 @@ namespace Sentry } public static class HubExtensions { + public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.Hint? hint = null) { } public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action configureScope) { } @@ -235,6 +263,7 @@ namespace Sentry { bool IsEnabled { get; } Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null); + Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); void CaptureTransaction(Sentry.Transaction transaction); void CaptureUserFeedback(Sentry.UserFeedback userFeedback); @@ -410,6 +439,7 @@ namespace Sentry public Sentry.User User { get; set; } public void AddAttachment(Sentry.Attachment attachment) { } public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { } + public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb, Sentry.Hint hint) { } public void Apply(Sentry.IEventLike other) { } public void Apply(Sentry.Scope other) { } public void Apply(object state) { } @@ -455,6 +485,7 @@ namespace Sentry public SentryClient(Sentry.SentryOptions options) { } public bool IsEnabled { get; } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } @@ -566,7 +597,10 @@ namespace Sentry public bool AutoSessionTracking { get; set; } public System.TimeSpan AutoSessionTrackingInterval { get; set; } public Sentry.Extensibility.IBackgroundWorker? BackgroundWorker { get; set; } + [System.Obsolete("This property will be removed in a future version. Use SetBeforeBreadcrumb instea" + + "d.")] public System.Func? BeforeBreadcrumb { get; set; } + [System.Obsolete("This property will be removed in a future version. Use SetBeforeSend instead.")] public System.Func? BeforeSend { get; set; } public System.Func? BeforeSendTransaction { get; set; } public string? CacheDirectoryPath { get; set; } @@ -620,6 +654,10 @@ namespace Sentry public Sentry.Extensibility.ITransport? Transport { get; set; } public bool UseAsyncFileIO { get; set; } public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSend(System.Func beforeSend) { } } public static class SentryOptionsExtensions { @@ -670,6 +708,7 @@ namespace Sentry { public static bool IsEnabled { get; } public static Sentry.SentryId LastEventId { get; } + public static void AddBreadcrumb(Sentry.Breadcrumb breadcrumb, Sentry.Hint? hint = null) { } public static void AddBreadcrumb(string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void BindClient(Sentry.ISentryClient client) { } @@ -677,6 +716,7 @@ namespace Sentry public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope) { } public static Sentry.SentryId CaptureException(System.Exception exception) { } public static Sentry.SentryId CaptureException(System.Exception exception, System.Action configureScope) { } public static Sentry.SentryId CaptureMessage(string message, Sentry.SentryLevel level = 1) { } @@ -1157,6 +1197,7 @@ namespace Sentry.Extensibility public void BindException(System.Exception exception, Sentry.ISpan span) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } @@ -1193,6 +1234,7 @@ namespace Sentry.Extensibility public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope) { } public Sentry.SentryId CaptureException(System.Exception exception) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt index d59221d416..19a60f992f 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -141,6 +141,7 @@ namespace Sentry public class Hint : System.Collections.Generic.IEnumerable>, System.Collections.ICollection, System.Collections.IEnumerable { public Hint() { } + public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } public int Count { get; } public bool IsSynchronized { get; } @@ -183,6 +184,7 @@ namespace Sentry } public static class HubExtensions { + public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.Hint? hint = null) { } public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action configureScope) { } @@ -706,6 +708,7 @@ namespace Sentry { public static bool IsEnabled { get; } public static Sentry.SentryId LastEventId { get; } + public static void AddBreadcrumb(Sentry.Breadcrumb breadcrumb, Sentry.Hint? hint = null) { } public static void AddBreadcrumb(string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void BindClient(Sentry.ISentryClient client) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index fa1b223ac0..bf2805d2cd 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -137,6 +137,33 @@ namespace Sentry { public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } } + public class Hint : System.Collections.Generic.IEnumerable>, System.Collections.ICollection, System.Collections.IEnumerable + { + public Hint() { } + public Hint(string key, object? value) { } + public System.Collections.Generic.ICollection Attachments { get; } + public int Count { get; } + public bool IsSynchronized { get; } + public object? this[string key] { get; set; } + public Sentry.Attachment? Screenshot { get; set; } + public object SyncRoot { get; } + public Sentry.Attachment? ViewHierarchy { get; set; } + public void AddAttachments(params Sentry.Attachment[] attachments) { } + public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } + public void Clear() { } + public bool ContainsKey(string key) { } + public void CopyTo(System.Array array, int index) { } + public System.Collections.Generic.IEnumerator> GetEnumerator() { } + public T? GetValue(string key) + where T : class? { } + public void Remove(string key) { } + public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } + public static Sentry.Hint WithAttachments(System.Collections.Generic.ICollection attachments) { } + } + public static class HintTypes + { + public const string HttpResponseMessage = "http-response-message"; + } public readonly struct HttpStatusCodeRange : System.IEquatable { public HttpStatusCodeRange(int statusCode) { } @@ -156,6 +183,7 @@ namespace Sentry } public static class HubExtensions { + public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Breadcrumb breadcrumb, Sentry.Hint? hint = null) { } public static void AddBreadcrumb(this Sentry.IHub hub, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void AddBreadcrumb(this Sentry.IHub hub, Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static Sentry.SentryId CaptureException(this Sentry.IHub hub, System.Exception ex, System.Action configureScope) { } @@ -234,6 +262,7 @@ namespace Sentry { bool IsEnabled { get; } Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null); + Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); void CaptureTransaction(Sentry.Transaction transaction); void CaptureUserFeedback(Sentry.UserFeedback userFeedback); @@ -409,6 +438,7 @@ namespace Sentry public Sentry.User User { get; set; } public void AddAttachment(Sentry.Attachment attachment) { } public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb) { } + public void AddBreadcrumb(Sentry.Breadcrumb breadcrumb, Sentry.Hint hint) { } public void Apply(Sentry.IEventLike other) { } public void Apply(Sentry.Scope other) { } public void Apply(object state) { } @@ -454,6 +484,7 @@ namespace Sentry public SentryClient(Sentry.SentryOptions options) { } public bool IsEnabled { get; } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Scope? scope = null) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } @@ -564,7 +595,10 @@ namespace Sentry public bool AutoSessionTracking { get; set; } public System.TimeSpan AutoSessionTrackingInterval { get; set; } public Sentry.Extensibility.IBackgroundWorker? BackgroundWorker { get; set; } + [System.Obsolete("This property will be removed in a future version. Use SetBeforeBreadcrumb instea" + + "d.")] public System.Func? BeforeBreadcrumb { get; set; } + [System.Obsolete("This property will be removed in a future version. Use SetBeforeSend instead.")] public System.Func? BeforeSend { get; set; } public System.Func? BeforeSendTransaction { get; set; } public string? CacheDirectoryPath { get; set; } @@ -618,6 +652,10 @@ namespace Sentry public Sentry.Extensibility.ITransport? Transport { get; set; } public bool UseAsyncFileIO { get; set; } public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSend(System.Func beforeSend) { } } public static class SentryOptionsExtensions { @@ -668,6 +706,7 @@ namespace Sentry { public static bool IsEnabled { get; } public static Sentry.SentryId LastEventId { get; } + public static void AddBreadcrumb(Sentry.Breadcrumb breadcrumb, Sentry.Hint? hint = null) { } public static void AddBreadcrumb(string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void AddBreadcrumb(Sentry.Infrastructure.ISystemClock? clock, string message, string? category = null, string? type = null, System.Collections.Generic.IDictionary? data = null, Sentry.BreadcrumbLevel level = 0) { } public static void BindClient(Sentry.ISentryClient client) { } @@ -675,6 +714,7 @@ namespace Sentry public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public static Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope) { } public static Sentry.SentryId CaptureException(System.Exception exception) { } public static Sentry.SentryId CaptureException(System.Exception exception, System.Action configureScope) { } public static Sentry.SentryId CaptureMessage(string message, Sentry.SentryLevel level = 1) { } @@ -1155,6 +1195,7 @@ namespace Sentry.Extensibility public void BindException(System.Exception exception, Sentry.ISpan span) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope = null) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } @@ -1191,6 +1232,7 @@ namespace Sentry.Extensibility public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Scope? scope) { } public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, System.Action configureScope) { } + public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope) { } public Sentry.SentryId CaptureException(System.Exception exception) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 086a39ebfc..31de51945e 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -127,6 +127,7 @@ public void CaptureException_FinishedSpanBoundToSameExceptionExists_EventIsLinke Arg.Is(evt => evt.Contexts.Trace.TraceId == transaction.TraceId && evt.Contexts.Trace.SpanId == transaction.SpanId), + Arg.Any(), Arg.Any()); } @@ -149,6 +150,7 @@ public void CaptureException_ActiveSpanExistsOnScope_EventIsLinkedToSpan() Arg.Is(evt => evt.Contexts.Trace.TraceId == transaction.TraceId && evt.Contexts.Trace.SpanId == transaction.SpanId), + Arg.Any(), Arg.Any()); } @@ -171,6 +173,7 @@ public void CaptureException_ActiveSpanExistsOnScopeButIsSampledOut_EventIsNotLi Arg.Is(evt => evt.Contexts.Trace.TraceId == default && evt.Contexts.Trace.SpanId == default), + Arg.Any(), Arg.Any()); } @@ -189,6 +192,7 @@ public void CaptureException_NoActiveSpanAndNoSpanBoundToSameException_EventIsNo Arg.Is(evt => evt.Contexts.Trace.TraceId == default && evt.Contexts.Trace.SpanId == default), + Arg.Any(), Arg.Any()); } @@ -1061,7 +1065,7 @@ public void CaptureEvent_HubEnabled(bool enabled) hub.CaptureEvent(evt); // Assert - _fixture.Client.Received(enabled ? 1 : 0).CaptureEvent(Arg.Any(), Arg.Any()); + _fixture.Client.Received(enabled ? 1 : 0).CaptureEvent(Arg.Any(), Arg.Any(), Arg.Any()); } [Theory] diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index e878bcfa80..022ca45d32 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -428,7 +428,7 @@ public void PushScope_MultiCallState_SameDisposableInstance() public void PushScope_MultiCallParameterless_SameDisposableInstance() => Assert.Same(SentrySdk.PushScope(), SentrySdk.PushScope()); [Fact] - public void AddBreadcrumb_NoClock_NoOp() => SentrySdk.AddBreadcrumb(null!); + public void AddBreadcrumb_NoClock_NoOp() => SentrySdk.AddBreadcrumb(message: null!); [Fact] public void AddBreadcrumb_WithClock_NoOp() => SentrySdk.AddBreadcrumb(clock: null, null!); From baee2ca15ca061a7535b793898b6dc27879c33dc Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 4 May 2023 15:43:24 +1200 Subject: [PATCH 08/25] Added missing XML docs on Hint constructors --- src/Sentry/Hint.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Sentry/Hint.cs b/src/Sentry/Hint.cs index 939da8db6a..63f7f13cb0 100644 --- a/src/Sentry/Hint.cs +++ b/src/Sentry/Hint.cs @@ -14,10 +14,18 @@ public class Hint : ICollection, IEnumerable> private readonly Dictionary _internalStorage = new(); private readonly List _attachments = new(); + /// + /// Creates a new instance of . + /// public Hint() { } + /// + /// Creates a new hint with a single key/value pair. + /// + /// + /// public Hint(string key, object? value) : this() { From d3dae05b638fb685fb159cee292154145ddbe6d4 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 4 May 2023 16:28:53 +1200 Subject: [PATCH 09/25] Updated MiddlewareLoggerIntegration tests to account for modified implementation --- test/Sentry.AspNetCore.Tests/MiddlewareLoggerIntegration.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/Sentry.AspNetCore.Tests/MiddlewareLoggerIntegration.cs b/test/Sentry.AspNetCore.Tests/MiddlewareLoggerIntegration.cs index 5a6009065e..17094275f2 100644 --- a/test/Sentry.AspNetCore.Tests/MiddlewareLoggerIntegration.cs +++ b/test/Sentry.AspNetCore.Tests/MiddlewareLoggerIntegration.cs @@ -41,7 +41,7 @@ public Fixture() }; loggingOptions.InitializeSdk = false; - Client.When(client => client.CaptureEvent(Arg.Any(), Arg.Any())) + Client.When(client => client.CaptureEvent(Arg.Any(), Arg.Any(), Arg.Any())) .Do(callback => callback.Arg().Evaluate()); var hub = new Hub(new SentryOptions { Dsn = ValidDsn }); @@ -83,6 +83,7 @@ public async Task InvokeAsync_LoggerMessage_AsBreadcrumb() _ = _fixture.Client.Received(1).CaptureEvent( Arg.Any(), + Arg.Any(), Arg.Is(e => e.Breadcrumbs.Any(b => b.Message == expectedCrumb))); } @@ -104,6 +105,7 @@ public async Task InvokeAsync_LoggerPushesScope_LoggerMessage_AsBreadcrumb() _ = _fixture.Client.Received(1).CaptureEvent( Arg.Any(), + Arg.Any(), Arg.Is(e => e.Breadcrumbs.Any(b => b.Message == expectedCrumb))); } @@ -119,6 +121,7 @@ public async Task InvokeAsync_OptionsConfigureScope_AffectsAllRequests() _ = _fixture.Client.Received(1).CaptureEvent( Arg.Any(), + Arg.Any(), Arg.Is(e => e.Level == expected)); } From fb6e9cf67c9a5dc90bf4a4ccd0c5359208590462 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 4 May 2023 18:45:16 +1200 Subject: [PATCH 10/25] Updated verified tests for CaptureTransaction_BeforeSendTransactionThrows_ErrorToEventBreadcrumb --- ...ws_ErrorToEventBreadcrumb.Core3_1.verified.txt | 15 +++++++++++++++ ..._ErrorToEventBreadcrumb.DotNet6_0.verified.txt | 15 +++++++++++++++ ..._ErrorToEventBreadcrumb.DotNet7_0.verified.txt | 15 +++++++++++++++ ...ows_ErrorToEventBreadcrumb.Net4_8.verified.txt | 14 ++++++++++++++ ...ventThrows_ErrorToEventBreadcrumb.verified.txt | 2 +- 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.Core3_1.verified.txt create mode 100644 test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.DotNet6_0.verified.txt create mode 100644 test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.DotNet7_0.verified.txt create mode 100644 test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.Net4_8.verified.txt diff --git a/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.Core3_1.verified.txt b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.Core3_1.verified.txt new file mode 100644 index 0000000000..6dcf0fd76a --- /dev/null +++ b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.Core3_1.verified.txt @@ -0,0 +1,15 @@ +[ + { + Timestamp: DateTimeOffset_1, + Message: BeforeSend callback failed., + Data: { + message: Exception message!, + stackTrace: +at Task Sentry.Tests.SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb() +at void Sentry.SentryOptions.SetBeforeSend(...) +at SentryEvent Sentry.SentryClient.BeforeSend(...) + }, + Category: SentryClient, + Level: error + } +] \ No newline at end of file diff --git a/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.DotNet6_0.verified.txt b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.DotNet6_0.verified.txt new file mode 100644 index 0000000000..6dcf0fd76a --- /dev/null +++ b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.DotNet6_0.verified.txt @@ -0,0 +1,15 @@ +[ + { + Timestamp: DateTimeOffset_1, + Message: BeforeSend callback failed., + Data: { + message: Exception message!, + stackTrace: +at Task Sentry.Tests.SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb() +at void Sentry.SentryOptions.SetBeforeSend(...) +at SentryEvent Sentry.SentryClient.BeforeSend(...) + }, + Category: SentryClient, + Level: error + } +] \ No newline at end of file diff --git a/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.DotNet7_0.verified.txt b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.DotNet7_0.verified.txt new file mode 100644 index 0000000000..6dcf0fd76a --- /dev/null +++ b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.DotNet7_0.verified.txt @@ -0,0 +1,15 @@ +[ + { + Timestamp: DateTimeOffset_1, + Message: BeforeSend callback failed., + Data: { + message: Exception message!, + stackTrace: +at Task Sentry.Tests.SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb() +at void Sentry.SentryOptions.SetBeforeSend(...) +at SentryEvent Sentry.SentryClient.BeforeSend(...) + }, + Category: SentryClient, + Level: error + } +] \ No newline at end of file diff --git a/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.Net4_8.verified.txt b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.Net4_8.verified.txt new file mode 100644 index 0000000000..6ce33e1e01 --- /dev/null +++ b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.Net4_8.verified.txt @@ -0,0 +1,14 @@ +[ + { + Timestamp: DateTimeOffset_1, + Message: BeforeSend callback failed., + Data: { + message: Exception message!, + stackTrace: +at Task Sentry.Tests.SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb() +at SentryEvent Sentry.SentryClient.BeforeSend(...) + }, + Category: SentryClient, + Level: error + } +] \ No newline at end of file diff --git a/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt index 6dcf0fd76a..f630c73b9e 100644 --- a/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt +++ b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt @@ -12,4 +12,4 @@ at SentryEvent Sentry.SentryClient.BeforeSend(...) Category: SentryClient, Level: error } -] \ No newline at end of file +] From d2115480b6b86fe3f6c4b10fb22a5025085430b9 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 4 May 2023 20:40:55 +1200 Subject: [PATCH 11/25] Tail chasing Verify test errors Not able to reproduce CI Build errors on my local machine... trying one more thing before potentially recommending we remove this test. This code change should remove an extra line from the call stack. --- ...Event_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt | 3 +-- test/Sentry.Tests/SentryClientTests.verify.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt index f630c73b9e..6ce33e1e01 100644 --- a/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt +++ b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt @@ -6,10 +6,9 @@ message: Exception message!, stackTrace: at Task Sentry.Tests.SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb() -at void Sentry.SentryOptions.SetBeforeSend(...) at SentryEvent Sentry.SentryClient.BeforeSend(...) }, Category: SentryClient, Level: error } -] +] \ No newline at end of file diff --git a/test/Sentry.Tests/SentryClientTests.verify.cs b/test/Sentry.Tests/SentryClientTests.verify.cs index 4e8bd19fe2..f408cf30e0 100644 --- a/test/Sentry.Tests/SentryClientTests.verify.cs +++ b/test/Sentry.Tests/SentryClientTests.verify.cs @@ -7,7 +7,7 @@ public partial class SentryClientTests public Task CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb() { var error = new Exception("Exception message!"); - _fixture.SentryOptions.SetBeforeSend(_ => throw error); + _fixture.SentryOptions.SetBeforeSend((_,_) => throw error); var @event = new SentryEvent(); From c1c27772f97f66de01b3a6a50cdfa65bdcd5858e Mon Sep 17 00:00:00 2001 From: Matt Johnson-Pint Date: Sat, 6 May 2023 13:26:58 -0700 Subject: [PATCH 12/25] Update CHANGELOG.md --- CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a86dc23d2d..cd6e5a75ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ # Changelog -## 3.31.0 +## Unreleased + +### Features -- Add Hint support ([#2351](https://github.com/getsentry/sentry-dotnet/pull/2351)) +- Add `Hint` support ([#2351](https://github.com/getsentry/sentry-dotnet/pull/2351)) + +## 3.31.0 ### Features From eded3b6b70f461d05a87a73303f1d926605a47ec Mon Sep 17 00:00:00 2001 From: Matt Johnson-Pint Date: Sat, 6 May 2023 13:39:38 -0700 Subject: [PATCH 13/25] Fix iOS compilation issue --- src/Sentry/Platforms/iOS/SentrySdk.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Sentry/Platforms/iOS/SentrySdk.cs b/src/Sentry/Platforms/iOS/SentrySdk.cs index 79eef5cfa3..bdeb077ff3 100644 --- a/src/Sentry/Platforms/iOS/SentrySdk.cs +++ b/src/Sentry/Platforms/iOS/SentrySdk.cs @@ -52,12 +52,15 @@ private static void InitSentryCocoaSdk(SentryOptions options) // NOTE: Tags in options.DefaultTags should not be passed down, because we already call SetTag on each // one when sending events, which is relayed through the scope observer. - if (options.BeforeBreadcrumb is { } beforeBreadcrumb) + if (options.BeforeBreadcrumbInternal is { } beforeBreadcrumb) { cocoaOptions.BeforeBreadcrumb = b => { + // Note: The Cocoa SDK doesn't yet support hints. + // See https://github.com/getsentry/sentry-cocoa/issues/2325 + var hint = new Hint(); var breadcrumb = b.ToBreadcrumb(options.DiagnosticLogger); - var result = beforeBreadcrumb(breadcrumb)?.ToCocoaBreadcrumb(); + var result = beforeBreadcrumb(breadcrumb, hint)?.ToCocoaBreadcrumb(); // Note: Nullable result is allowed but delegate is generated incorrectly // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 From 6d78761294a6d68583fb60d935665c1b7a082f4a Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 8 May 2023 10:36:55 +1200 Subject: [PATCH 14/25] Moved hint data from base Hint class to Items property, for clarity --- src/Sentry/Hint.cs | 66 +-------- src/Sentry/SentryFailedRequestHandler.cs | 3 +- .../ApiApprovalTests.Run.Core3_1.verified.txt | 14 +- ...piApprovalTests.Run.DotNet6_0.verified.txt | 14 +- ...piApprovalTests.Run.DotNet7_0.verified.txt | 14 +- .../ApiApprovalTests.Run.Net4_8.verified.txt | 14 +- test/Sentry.Tests/HintTests.cs | 128 ++---------------- test/Sentry.Tests/ScopeTests.cs | 2 +- .../SentryFailedRequestHandlerTests.cs | 2 +- 9 files changed, 29 insertions(+), 228 deletions(-) diff --git a/src/Sentry/Hint.cs b/src/Sentry/Hint.cs index 63f7f13cb0..6e28b14865 100644 --- a/src/Sentry/Hint.cs +++ b/src/Sentry/Hint.cs @@ -1,18 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Net.Mail; -using Sentry.Internal.Extensions; - namespace Sentry; /// /// A hint that can be provided when capturing a or adding a . /// Hints can be used to filter or modify events or breadcrumbs before they are sent to Sentry. /// -public class Hint : ICollection, IEnumerable> +public class Hint { - private readonly Dictionary _internalStorage = new(); private readonly List _attachments = new(); + private readonly Dictionary _items = new(); /// /// Creates a new instance of . @@ -29,18 +24,7 @@ public Hint() public Hint(string key, object? value) : this() { - _internalStorage[key] = value; - } - - /// - /// Gets or sets additional values to be provided with the hint - /// - /// The key - /// The value with the specified key or null if none exist. - public object? this[string key] - { - get => _internalStorage.GetValueOrDefault(key); - set => _internalStorage[key] = value; + _items[key] = value; } internal void AddAttachmentsInternal(IEnumerable attachments) @@ -68,55 +52,13 @@ internal void AddAttachmentsInternal(IEnumerable attachments) /// public ICollection Attachments => _attachments; - /// - /// Clears any values stored in - /// - public void Clear() => _internalStorage.Clear(); - - /// - /// Checks if the specified key exists - /// - /// The key - /// True if the key exists. False otherwise. - public bool ContainsKey(string key) => _internalStorage.ContainsKey(key); - - /// - public void CopyTo(Array array, int index) => ((ICollection)_internalStorage).CopyTo(array, index); - - /// - public int Count => _internalStorage.Count; - - IEnumerator IEnumerable.GetEnumerator() => _internalStorage.GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - => ((IEnumerable>)_internalStorage).GetEnumerator(); - - /// - /// Gets the value with the specified key as type - /// - /// They expected value type - /// The key - /// A value of type if one exists with the specified key or null otherwise. - public T? GetValue(string key) where T : class? => (this[key] is T typedHintValue) ? typedHintValue : null; - - /// - public bool IsSynchronized => ((ICollection)_internalStorage).IsSynchronized; - - /// - /// Remves the value with the specified key - /// - /// - public void Remove(string key) => _internalStorage.Remove(key); + public IDictionary Items => _items; /// /// Gets or sets a Screenshot for the Hint /// public Attachment? Screenshot { get; set; } - /// - public object SyncRoot => ((ICollection)_internalStorage).SyncRoot; - /// /// Gets or sets a ViewHierarchy for the Hint /// diff --git a/src/Sentry/SentryFailedRequestHandler.cs b/src/Sentry/SentryFailedRequestHandler.cs index 4022e92f48..10ec349693 100644 --- a/src/Sentry/SentryFailedRequestHandler.cs +++ b/src/Sentry/SentryFailedRequestHandler.cs @@ -75,8 +75,7 @@ public void HandleResponse(HttpResponseMessage response) exception.SetSentryMechanism(MechanismType); var @event = new SentryEvent(exception); - var hint = new Hint(); - hint[HintTypes.HttpResponseMessage] = response; + var hint = new Hint(HintTypes.HttpResponseMessage, response); var sentryRequest = new Request { diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt index 059a4f2471..b8ab4ebf24 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt @@ -138,26 +138,16 @@ namespace Sentry { public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } } - public class Hint : System.Collections.Generic.IEnumerable>, System.Collections.ICollection, System.Collections.IEnumerable + public class Hint { public Hint() { } public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } - public int Count { get; } - public bool IsSynchronized { get; } - public object? this[string key] { get; set; } + public System.Collections.Generic.IDictionary Items { get; } public Sentry.Attachment? Screenshot { get; set; } - public object SyncRoot { get; } public Sentry.Attachment? ViewHierarchy { get; set; } public void AddAttachments(params Sentry.Attachment[] attachments) { } public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } - public void Clear() { } - public bool ContainsKey(string key) { } - public void CopyTo(System.Array array, int index) { } - public System.Collections.Generic.IEnumerator> GetEnumerator() { } - public T? GetValue(string key) - where T : class? { } - public void Remove(string key) { } public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } public static Sentry.Hint WithAttachments(System.Collections.Generic.ICollection attachments) { } } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt index 19a60f992f..75f7bb702a 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -138,26 +138,16 @@ namespace Sentry { public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } } - public class Hint : System.Collections.Generic.IEnumerable>, System.Collections.ICollection, System.Collections.IEnumerable + public class Hint { public Hint() { } public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } - public int Count { get; } - public bool IsSynchronized { get; } - public object? this[string key] { get; set; } + public System.Collections.Generic.IDictionary Items { get; } public Sentry.Attachment? Screenshot { get; set; } - public object SyncRoot { get; } public Sentry.Attachment? ViewHierarchy { get; set; } public void AddAttachments(params Sentry.Attachment[] attachments) { } public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } - public void Clear() { } - public bool ContainsKey(string key) { } - public void CopyTo(System.Array array, int index) { } - public System.Collections.Generic.IEnumerator> GetEnumerator() { } - public T? GetValue(string key) - where T : class? { } - public void Remove(string key) { } public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } public static Sentry.Hint WithAttachments(System.Collections.Generic.ICollection attachments) { } } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt index 19a60f992f..75f7bb702a 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -138,26 +138,16 @@ namespace Sentry { public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } } - public class Hint : System.Collections.Generic.IEnumerable>, System.Collections.ICollection, System.Collections.IEnumerable + public class Hint { public Hint() { } public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } - public int Count { get; } - public bool IsSynchronized { get; } - public object? this[string key] { get; set; } + public System.Collections.Generic.IDictionary Items { get; } public Sentry.Attachment? Screenshot { get; set; } - public object SyncRoot { get; } public Sentry.Attachment? ViewHierarchy { get; set; } public void AddAttachments(params Sentry.Attachment[] attachments) { } public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } - public void Clear() { } - public bool ContainsKey(string key) { } - public void CopyTo(System.Array array, int index) { } - public System.Collections.Generic.IEnumerator> GetEnumerator() { } - public T? GetValue(string key) - where T : class? { } - public void Remove(string key) { } public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } public static Sentry.Hint WithAttachments(System.Collections.Generic.ICollection attachments) { } } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index bf2805d2cd..9057b53e6f 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -137,26 +137,16 @@ namespace Sentry { public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } } - public class Hint : System.Collections.Generic.IEnumerable>, System.Collections.ICollection, System.Collections.IEnumerable + public class Hint { public Hint() { } public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } - public int Count { get; } - public bool IsSynchronized { get; } - public object? this[string key] { get; set; } + public System.Collections.Generic.IDictionary Items { get; } public Sentry.Attachment? Screenshot { get; set; } - public object SyncRoot { get; } public Sentry.Attachment? ViewHierarchy { get; set; } public void AddAttachments(params Sentry.Attachment[] attachments) { } public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } - public void Clear() { } - public bool ContainsKey(string key) { } - public void CopyTo(System.Array array, int index) { } - public System.Collections.Generic.IEnumerator> GetEnumerator() { } - public T? GetValue(string key) - where T : class? { } - public void Remove(string key) { } public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } public static Sentry.Hint WithAttachments(System.Collections.Generic.ICollection attachments) { } } diff --git a/test/Sentry.Tests/HintTests.cs b/test/Sentry.Tests/HintTests.cs index a8fc79a2b6..baab200b06 100644 --- a/test/Sentry.Tests/HintTests.cs +++ b/test/Sentry.Tests/HintTests.cs @@ -42,14 +42,13 @@ public void AddAttachments_WithAttachments_AddsToHint() public void Clear_WithEntries_ClearsHintEntries() { // Arrange - var hint = new Hint(); - hint["key"] = "value"; + var hint = new Hint("key", "value"); // Act - hint.Clear(); + hint.Items.Clear(); // Assert - hint.Count.Should().Be(0); + hint.Items.Count.Should().Be(0); } [Fact] @@ -71,11 +70,10 @@ public void ClearAttachments_WithAttachments_ClearsHintAttachments() public void ContainsKey_ExistingKey_ReturnsTrue() { // Arrange - var hint = new Hint(); - hint["key"] = "value"; + var hint = new Hint("key", "value"); // Act - var containsKey = hint.ContainsKey("key"); + var containsKey = hint.Items.ContainsKey("key"); // Assert containsKey.Should().BeTrue(); @@ -85,65 +83,15 @@ public void ContainsKey_ExistingKey_ReturnsTrue() public void ContainsKey_NonExistingKey_ReturnsFalse() { // Arrange - var hint = new Hint(); - hint["key"] = "value"; + var hint = new Hint("key", "value"); // Act - var containsKey = hint.ContainsKey("nonExistingKey"); + var containsKey = hint.Items.ContainsKey("nonExistingKey"); // Assert containsKey.Should().BeFalse(); } - [Fact] - public void CopyTo_EmptyHint_CopyToArray() - { - // Arrange - var hint = new Hint(); - var array = new object[3]; - - // Act - hint.CopyTo(array, 0); - - // Assert - array.Should().BeEquivalentTo(new object[] { null, null, null }); - } - - [Fact] - public void CopyTo_NonEmptyHint_CopyToArray() - { - // Arrange - var hint = new Hint(); - hint["key1"] = "value1"; - hint["key2"] = "value2"; - hint["key3"] = "value3"; - var array = new object[3]; - - // Act - hint.CopyTo(array, 0); - - // Assert - array.Should().BeEquivalentTo(new [] { - new KeyValuePair("key1", "value1"), - new KeyValuePair("key2", "value2"), - new KeyValuePair("key3", "value3"), - }); - } - - [Fact] - public void CopyTo_ArrayTooSmall_ThrowsException() - { - // Arrange - var hint = new Hint(); - hint["key1"] = "value1"; - hint["key2"] = "value2"; - hint["key3"] = "value3"; - var array = new object[2]; - - // Act and Assert - Assert.Throws(() => hint.CopyTo(array, 0)); - } - [Fact] public void Count_ReturnsZero_WhenHintIsEmpty() { @@ -151,7 +99,7 @@ public void Count_ReturnsZero_WhenHintIsEmpty() var hint = new Hint(); // Act - var count = hint.Count; + var count = hint.Items.Count; // Assert count.Should().Be(0); @@ -162,75 +110,27 @@ public void Count_ReturnsCorrectValue_WhenHintHasItems() { // Arrange var hint = new Hint(); - hint["key1"] = "value1"; - hint["key2"] = "value2"; + hint.Items["key1"] = "value1"; + hint.Items["key2"] = "value2"; // Act - var count = hint.Count; + var count = hint.Items.Count; // Assert count.Should().Be(2); } - [Fact] - public void GetValue_WithNonExistingKey_ReturnsNull() - { - // Arrange - var hint = new Hint(); - - // Act - var result = hint.GetValue("non-existing"); - - // Assert - Assert.Null(result); - } - - [Fact] - public void GetValue_WithExistingKey_ReturnsValue() - { - // Arrange - var hint = new Hint(); - hint["key"] = "value"; - - // Act - var result = hint.GetValue("key"); - - // Assert - Assert.Equal("value", result); - } - - [Fact] - public void GetEnumerator_ReturnsValidEnumreator() - { - // Arrange - var hint = new Hint(); - hint["key1"] = "value1"; - hint["key2"] = "value2"; - - // Act + Assert - var enumerator = hint.GetEnumerator(); - - // Assert - enumerator.Should().BeAssignableTo>>(); - while (enumerator.MoveNext()) - { - enumerator.Current.Key.Should().BeOneOf("key1", "key2"); - enumerator.Current.Value.Should().BeOneOf("value1", "value2"); - } - } - [Fact] public void Remove_WithExistingKey_RemovesEntry() { // Arrange - var hint = new Hint(); - hint["key"] = "value"; + var hint = new Hint("key", "value"); // Act - hint.Remove("key"); + hint.Items.Remove("key"); // Assert - Assert.Null(hint["key"]); + hint.Items.ContainsKey("key").Should().BeFalse(); } [Fact] diff --git a/test/Sentry.Tests/ScopeTests.cs b/test/Sentry.Tests/ScopeTests.cs index 0114b0090d..6e7b707225 100644 --- a/test/Sentry.Tests/ScopeTests.cs +++ b/test/Sentry.Tests/ScopeTests.cs @@ -327,7 +327,7 @@ public void AddBreadcrumb_BeforeAddBreadcrumb_ReceivesHint() scope.AddBreadcrumb(new Breadcrumb(), expectedHint); // Assert - receivedHint.Should().Equal(expectedHint); + receivedHint.Should().BeSameAs(expectedHint); } [Theory] diff --git a/test/Sentry.Tests/SentryFailedRequestHandlerTests.cs b/test/Sentry.Tests/SentryFailedRequestHandlerTests.cs index 06a467267b..fd8515f248 100644 --- a/test/Sentry.Tests/SentryFailedRequestHandlerTests.cs +++ b/test/Sentry.Tests/SentryFailedRequestHandlerTests.cs @@ -243,7 +243,7 @@ public void HandleResponse_Hint_Response() hint.Should().NotBeNull(); // Response should be captured - hint[HintTypes.HttpResponseMessage].Should().Be(response); + hint.Items[HintTypes.HttpResponseMessage].Should().Be(response); } } } From 9142abf71d451d3941893387235279c246c86cb7 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 8 May 2023 10:45:03 +1200 Subject: [PATCH 15/25] Added XML docs for Hint.Items property --- src/Sentry/Hint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Sentry/Hint.cs b/src/Sentry/Hint.cs index 6e28b14865..905fdd287c 100644 --- a/src/Sentry/Hint.cs +++ b/src/Sentry/Hint.cs @@ -52,6 +52,9 @@ internal void AddAttachmentsInternal(IEnumerable attachments) /// public ICollection Attachments => _attachments; + /// + /// Data provided with the Hint. + /// public IDictionary Items => _items; /// From 8868e479fed15d5316fe2540b1865c20e7d2e11c Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 8 May 2023 11:36:49 +1200 Subject: [PATCH 16/25] Updated Customized console sample to use new Hint --- samples/Sentry.Samples.Console.Customized/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/Sentry.Samples.Console.Customized/Program.cs b/samples/Sentry.Samples.Console.Customized/Program.cs index 811270165e..d77c79f028 100644 --- a/samples/Sentry.Samples.Console.Customized/Program.cs +++ b/samples/Sentry.Samples.Console.Customized/Program.cs @@ -65,9 +65,9 @@ await SentrySdk.ConfigureScopeAsync(async scope => // Replace breadcrumbs entirely incase of a drastic hint const string replaceBreadcrumb = "don't trust this breadcrumb"; - if (hint.ContainsKey(replaceBreadcrumb)) + if (hint.Items.TryGetValue(replaceBreadcrumb, out var replacementMessage)) { - return new Breadcrumb(hint.GetValue(replaceBreadcrumb), null, null, null, BreadcrumbLevel.Critical); + return new Breadcrumb((string)replacementMessage, null, null, null, BreadcrumbLevel.Critical); } return crumb; From 0f701eb0dabae0f5b2606f5c2fdecb143b3ec8fc Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Mon, 8 May 2023 19:52:07 +1200 Subject: [PATCH 17/25] - Added Hints to BeforeSendTransaction --- src/Sentry/Extensibility/DisabledHub.cs | 7 ++ src/Sentry/Extensibility/HubAdapter.cs | 8 ++ src/Sentry/ISentryClient.cs | 15 ++++ src/Sentry/Internal/Hub.cs | 6 +- src/Sentry/SentryClient.cs | 13 +-- src/Sentry/SentryOptions.cs | 28 +++++- src/Sentry/SentrySdk.cs | 12 +++ .../SentryTracingMiddlewareTests.cs | 12 ++- .../ApiApprovalTests.Run.Core3_1.verified.txt | 9 ++ ...piApprovalTests.Run.DotNet6_0.verified.txt | 9 ++ ...piApprovalTests.Run.DotNet7_0.verified.txt | 9 ++ .../ApiApprovalTests.Run.Net4_8.verified.txt | 9 ++ test/Sentry.Tests/HubTests.cs | 16 +++- .../Sentry.Tests/Protocol/MeasurementTests.cs | 88 ++++++++++++------- .../Sentry.Tests/Protocol/TransactionTests.cs | 2 +- test/Sentry.Tests/SentryClientTests.cs | 35 ++++++-- test/Sentry.Tests/SentryClientTests.verify.cs | 2 +- 17 files changed, 227 insertions(+), 53 deletions(-) diff --git a/src/Sentry/Extensibility/DisabledHub.cs b/src/Sentry/Extensibility/DisabledHub.cs index f5564d023f..109e5ef248 100644 --- a/src/Sentry/Extensibility/DisabledHub.cs +++ b/src/Sentry/Extensibility/DisabledHub.cs @@ -131,6 +131,13 @@ public void CaptureTransaction(Transaction transaction) { } + /// + /// No-Op. + /// + public void CaptureTransaction(Transaction transaction, Hint? hint) + { + } + /// /// No-Op. /// diff --git a/src/Sentry/Extensibility/HubAdapter.cs b/src/Sentry/Extensibility/HubAdapter.cs index b5b5f7caf4..607b39aed6 100644 --- a/src/Sentry/Extensibility/HubAdapter.cs +++ b/src/Sentry/Extensibility/HubAdapter.cs @@ -215,6 +215,14 @@ public SentryId CaptureException(Exception exception) public void CaptureTransaction(Transaction transaction) => SentrySdk.CaptureTransaction(transaction); + /// + /// Forwards the call to . + /// + [DebuggerStepThrough] + [EditorBrowsable(EditorBrowsableState.Never)] + public void CaptureTransaction(Transaction transaction, Hint? hint) + => SentrySdk.CaptureTransaction(transaction, hint); + /// /// Forwards the call to . /// diff --git a/src/Sentry/ISentryClient.cs b/src/Sentry/ISentryClient.cs index 98c7c806f3..01971d9790 100644 --- a/src/Sentry/ISentryClient.cs +++ b/src/Sentry/ISentryClient.cs @@ -44,6 +44,21 @@ public interface ISentryClient [EditorBrowsable(EditorBrowsableState.Never)] void CaptureTransaction(Transaction transaction); + /// + /// Captures a transaction. + /// + /// + /// Note: this method is NOT meant to be called from user code! + /// Instead, call on the transaction. + /// + /// The transaction. + /// + /// A hint providing extra context. + /// This will be available in callbacks prior to processing the transaction. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + void CaptureTransaction(Transaction transaction, Hint? hint); + /// /// Captures a session update. /// diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index a0e707ff3b..4b67c7296f 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -394,7 +394,9 @@ public void CaptureUserFeedback(UserFeedback userFeedback) } } - public void CaptureTransaction(Transaction transaction) + public void CaptureTransaction(Transaction transaction) => CaptureTransaction(transaction, null); + + public void CaptureTransaction(Transaction transaction, Hint? hint) { // Note: The hub should capture transactions even if it is disabled. // This allows transactions to be reported as failed when they encountered an unhandled exception, @@ -431,7 +433,7 @@ public void CaptureTransaction(Transaction transaction) } } - currentScope.Value.CaptureTransaction(processedTransaction); + currentScope.Value.CaptureTransaction(processedTransaction, hint); } catch (Exception e) { diff --git a/src/Sentry/SentryClient.cs b/src/Sentry/SentryClient.cs index 93cd277058..6d02636893 100644 --- a/src/Sentry/SentryClient.cs +++ b/src/Sentry/SentryClient.cs @@ -100,7 +100,10 @@ public void CaptureUserFeedback(UserFeedback userFeedback) } /// - public void CaptureTransaction(Transaction transaction) + public void CaptureTransaction(Transaction transaction) => CaptureTransaction(transaction, null); + + /// + public void CaptureTransaction(Transaction transaction, Hint? hint) { if (transaction.SpanId.Equals(SpanId.Empty)) { @@ -136,7 +139,7 @@ public void CaptureTransaction(Transaction transaction) return; } - var processedTransaction = BeforeSendTransaction(transaction); + var processedTransaction = BeforeSendTransaction(transaction, hint ?? new Hint()); if (processedTransaction is null) // Rejected transaction { _options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.BeforeSend, DataCategory.Transaction); @@ -147,9 +150,9 @@ public void CaptureTransaction(Transaction transaction) CaptureEnvelope(Envelope.FromTransaction(processedTransaction)); } - private Transaction? BeforeSendTransaction(Transaction transaction) + private Transaction? BeforeSendTransaction(Transaction transaction, Hint hint) { - if (_options.BeforeSendTransaction is null) + if (_options.BeforeSendTransactionInternal is null) { return transaction; } @@ -158,7 +161,7 @@ public void CaptureTransaction(Transaction transaction) try { - return _options.BeforeSendTransaction?.Invoke(transaction); + return _options.BeforeSendTransactionInternal?.Invoke(transaction, hint); } catch (Exception e) { diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index c32e9556df..0ea7b9c619 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -341,6 +341,10 @@ public void SetBeforeSend(Func beforeSend) _beforeSend = beforeSend; } + private Func? _beforeSendTransaction; + + internal Func? BeforeSendTransactionInternal => _beforeSendTransaction; + /// /// A callback to invoke before sending a transaction to Sentry /// @@ -349,7 +353,29 @@ public void SetBeforeSend(Func beforeSend) /// a chance to inspect and/or modify the transaction before it's sent. If the transaction /// should not be sent at all, return null from the callback. /// - public Func? BeforeSendTransaction { get; set; } + [Obsolete("This property will be removed in a future version. Use SetBeforeSendTransaction instead.")] + public Func? BeforeSendTransaction { + get => null; + set => _beforeSendTransaction = value is null ? null : (e, _) => value(e); + } + + /// + /// Configures a callback to invoke before sending a transaction to Sentry + /// + /// The callback + public void SetBeforeSendTransaction(Func beforeSendTransaction) + { + _beforeSendTransaction = (e, _) => beforeSendTransaction(e); + } + + /// + /// Configures a callback to invoke before sending a transaction to Sentry + /// + /// The callback + public void SetBeforeSendTransaction(Func beforeSendTransaction) + { + _beforeSendTransaction = beforeSendTransaction; + } private Func? _beforeBreadcrumb; diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index cb02e506b8..2215f33ccf 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -527,6 +527,18 @@ public static void CaptureUserFeedback(SentryId eventId, string email, string co public static void CaptureTransaction(Transaction transaction) => CurrentHub.CaptureTransaction(transaction); + /// + /// Captures a transaction. + /// + /// + /// Note: this method is NOT meant to be called from user code! + /// Instead, call on the transaction. + /// + [DebuggerStepThrough] + [EditorBrowsable(EditorBrowsableState.Never)] + public static void CaptureTransaction(Transaction transaction, Hint? hint) + => CurrentHub.CaptureTransaction(transaction, hint); + /// /// Captures a session update. /// diff --git a/test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs b/test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs index 047b844293..b9ce887f77 100644 --- a/test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryTracingMiddlewareTests.cs @@ -55,7 +55,9 @@ public async Task Transactions_are_grouped_by_route() sentryClient.Received(2).CaptureTransaction( Arg.Is(transaction => transaction.Name == "GET /person/{id}" && - transaction.NameSource == TransactionNameSource.Route)); + transaction.NameSource == TransactionNameSource.Route), + Arg.Any() + ); } [Fact] @@ -150,7 +152,9 @@ public async Task Transaction_is_started_automatically_from_incoming_trace_heade t.TraceId == SentryId.Parse("75302ac48a024bde9a3b3734a82e36c8") && t.ParentSpanId == SpanId.Parse("1000000000000000") && t.IsSampled == false - )); + ), + Arg.Any() + ); } [Theory] @@ -554,7 +558,7 @@ public async Task Transaction_TransactionNameProviderSetSet_TransactionNameSet() var expectedName = "My custom name"; var sentryClient = Substitute.For(); - sentryClient.When(x => x.CaptureTransaction(Arg.Any())) + sentryClient.When(x => x.CaptureTransaction(Arg.Any(), Arg.Any())) .Do(callback => transaction = callback.Arg()); var options = new SentryAspNetCoreOptions { @@ -596,7 +600,7 @@ public async Task Transaction_TransactionNameProviderSetUnset_TransactionNameSet Transaction transaction = null; var sentryClient = Substitute.For(); - sentryClient.When(x => x.CaptureTransaction(Arg.Any())) + sentryClient.When(x => x.CaptureTransaction(Arg.Any(), Arg.Any())) .Do(callback => transaction = callback.Arg()); var options = new SentryAspNetCoreOptions { diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt index b8ab4ebf24..f53308bb0e 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt @@ -256,6 +256,7 @@ namespace Sentry Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); void CaptureTransaction(Sentry.Transaction transaction); + void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint); void CaptureUserFeedback(Sentry.UserFeedback userFeedback); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } @@ -478,6 +479,7 @@ namespace Sentry public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } + public void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } [System.Obsolete("Sentry client should not be explicitly disposed of. This method will be removed i" + "n version 4.")] @@ -591,6 +593,8 @@ namespace Sentry public System.Func? BeforeBreadcrumb { get; set; } [System.Obsolete("This property will be removed in a future version. Use SetBeforeSend instead.")] public System.Func? BeforeSend { get; set; } + [System.Obsolete("This property will be removed in a future version. Use SetBeforeSendTransaction i" + + "nstead.")] public System.Func? BeforeSendTransaction { get; set; } public string? CacheDirectoryPath { get; set; } public bool CaptureFailedRequests { get; set; } @@ -647,6 +651,8 @@ namespace Sentry public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } public void SetBeforeSend(System.Func beforeSend) { } public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } } public static class SentryOptionsExtensions { @@ -712,6 +718,7 @@ namespace Sentry public static Sentry.SentryId CaptureMessage(string message, System.Action configureScope, Sentry.SentryLevel level = 1) { } public static void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public static void CaptureTransaction(Sentry.Transaction transaction) { } + public static void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public static void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } public static void CaptureUserFeedback(Sentry.SentryId eventId, string email, string comments, string? name = null) { } [System.Obsolete("WARNING: This method deliberately causes a crash, and should not be used in a rea" + @@ -1189,6 +1196,7 @@ namespace Sentry.Extensibility public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } + public void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } public void ConfigureScope(System.Action configureScope) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } @@ -1227,6 +1235,7 @@ namespace Sentry.Extensibility public Sentry.SentryId CaptureException(System.Exception exception) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } + public void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public void CaptureUserFeedback(Sentry.UserFeedback sentryUserFeedback) { } public void ConfigureScope(System.Action configureScope) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt index 75f7bb702a..4b0f571319 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -256,6 +256,7 @@ namespace Sentry Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); void CaptureTransaction(Sentry.Transaction transaction); + void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint); void CaptureUserFeedback(Sentry.UserFeedback userFeedback); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } @@ -478,6 +479,7 @@ namespace Sentry public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } + public void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } [System.Obsolete("Sentry client should not be explicitly disposed of. This method will be removed i" + "n version 4.")] @@ -592,6 +594,8 @@ namespace Sentry public System.Func? BeforeBreadcrumb { get; set; } [System.Obsolete("This property will be removed in a future version. Use SetBeforeSend instead.")] public System.Func? BeforeSend { get; set; } + [System.Obsolete("This property will be removed in a future version. Use SetBeforeSendTransaction i" + + "nstead.")] public System.Func? BeforeSendTransaction { get; set; } public string? CacheDirectoryPath { get; set; } public bool CaptureFailedRequests { get; set; } @@ -648,6 +652,8 @@ namespace Sentry public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } public void SetBeforeSend(System.Func beforeSend) { } public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } } public static class SentryOptionsExtensions { @@ -713,6 +719,7 @@ namespace Sentry public static Sentry.SentryId CaptureMessage(string message, System.Action configureScope, Sentry.SentryLevel level = 1) { } public static void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public static void CaptureTransaction(Sentry.Transaction transaction) { } + public static void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public static void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } public static void CaptureUserFeedback(Sentry.SentryId eventId, string email, string comments, string? name = null) { } [System.Obsolete("WARNING: This method deliberately causes a crash, and should not be used in a rea" + @@ -1190,6 +1197,7 @@ namespace Sentry.Extensibility public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } + public void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } public void ConfigureScope(System.Action configureScope) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } @@ -1228,6 +1236,7 @@ namespace Sentry.Extensibility public Sentry.SentryId CaptureException(System.Exception exception) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } + public void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public void CaptureUserFeedback(Sentry.UserFeedback sentryUserFeedback) { } public void ConfigureScope(System.Action configureScope) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt index 75f7bb702a..4b0f571319 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -256,6 +256,7 @@ namespace Sentry Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); void CaptureTransaction(Sentry.Transaction transaction); + void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint); void CaptureUserFeedback(Sentry.UserFeedback userFeedback); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } @@ -478,6 +479,7 @@ namespace Sentry public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } + public void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } [System.Obsolete("Sentry client should not be explicitly disposed of. This method will be removed i" + "n version 4.")] @@ -592,6 +594,8 @@ namespace Sentry public System.Func? BeforeBreadcrumb { get; set; } [System.Obsolete("This property will be removed in a future version. Use SetBeforeSend instead.")] public System.Func? BeforeSend { get; set; } + [System.Obsolete("This property will be removed in a future version. Use SetBeforeSendTransaction i" + + "nstead.")] public System.Func? BeforeSendTransaction { get; set; } public string? CacheDirectoryPath { get; set; } public bool CaptureFailedRequests { get; set; } @@ -648,6 +652,8 @@ namespace Sentry public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } public void SetBeforeSend(System.Func beforeSend) { } public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } } public static class SentryOptionsExtensions { @@ -713,6 +719,7 @@ namespace Sentry public static Sentry.SentryId CaptureMessage(string message, System.Action configureScope, Sentry.SentryLevel level = 1) { } public static void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public static void CaptureTransaction(Sentry.Transaction transaction) { } + public static void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public static void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } public static void CaptureUserFeedback(Sentry.SentryId eventId, string email, string comments, string? name = null) { } [System.Obsolete("WARNING: This method deliberately causes a crash, and should not be used in a rea" + @@ -1190,6 +1197,7 @@ namespace Sentry.Extensibility public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } + public void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } public void ConfigureScope(System.Action configureScope) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } @@ -1228,6 +1236,7 @@ namespace Sentry.Extensibility public Sentry.SentryId CaptureException(System.Exception exception) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } + public void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public void CaptureUserFeedback(Sentry.UserFeedback sentryUserFeedback) { } public void ConfigureScope(System.Action configureScope) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 9057b53e6f..38bbcbc072 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -255,6 +255,7 @@ namespace Sentry Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null); void CaptureSession(Sentry.SessionUpdate sessionUpdate); void CaptureTransaction(Sentry.Transaction transaction); + void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint); void CaptureUserFeedback(Sentry.UserFeedback userFeedback); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } @@ -477,6 +478,7 @@ namespace Sentry public Sentry.SentryId CaptureEvent(Sentry.SentryEvent? @event, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } + public void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } [System.Obsolete("Sentry client should not be explicitly disposed of. This method will be removed i" + "n version 4.")] @@ -590,6 +592,8 @@ namespace Sentry public System.Func? BeforeBreadcrumb { get; set; } [System.Obsolete("This property will be removed in a future version. Use SetBeforeSend instead.")] public System.Func? BeforeSend { get; set; } + [System.Obsolete("This property will be removed in a future version. Use SetBeforeSendTransaction i" + + "nstead.")] public System.Func? BeforeSendTransaction { get; set; } public string? CacheDirectoryPath { get; set; } public bool CaptureFailedRequests { get; set; } @@ -646,6 +650,8 @@ namespace Sentry public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } public void SetBeforeSend(System.Func beforeSend) { } public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } } public static class SentryOptionsExtensions { @@ -711,6 +717,7 @@ namespace Sentry public static Sentry.SentryId CaptureMessage(string message, System.Action configureScope, Sentry.SentryLevel level = 1) { } public static void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public static void CaptureTransaction(Sentry.Transaction transaction) { } + public static void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public static void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } public static void CaptureUserFeedback(Sentry.SentryId eventId, string email, string comments, string? name = null) { } [System.Obsolete("WARNING: This method deliberately causes a crash, and should not be used in a rea" + @@ -1188,6 +1195,7 @@ namespace Sentry.Extensibility public Sentry.SentryId CaptureEvent(Sentry.SentryEvent evt, Sentry.Hint? hint, Sentry.Scope? scope = null) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } + public void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } public void ConfigureScope(System.Action configureScope) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } @@ -1226,6 +1234,7 @@ namespace Sentry.Extensibility public Sentry.SentryId CaptureException(System.Exception exception) { } public void CaptureSession(Sentry.SessionUpdate sessionUpdate) { } public void CaptureTransaction(Sentry.Transaction transaction) { } + public void CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint) { } public void CaptureUserFeedback(Sentry.UserFeedback sentryUserFeedback) { } public void ConfigureScope(System.Action configureScope) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 31de51945e..cbabb6ebfa 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1126,7 +1126,21 @@ public void CaptureTransaction_HubEnabled(bool enabled) transaction.Finish(); // Assert - _fixture.Client.Received().CaptureTransaction(Arg.Is(t => t.IsSampled == enabled)); + _fixture.Client.Received().CaptureTransaction(Arg.Is(t => t.IsSampled == enabled), Arg.Any()); + } + + [Fact] + public void CaptureTransaction_Provides_Hint_To_Client() + { + // Arrange + var hub = _fixture.GetSut(); + + // Act + var transaction = hub.StartTransaction("test", "test"); + transaction.Finish(); + + // Assert + _fixture.Client.Received().CaptureTransaction(Arg.Any(), Arg.Any()); } #if ANDROID && CI_BUILD diff --git a/test/Sentry.Tests/Protocol/MeasurementTests.cs b/test/Sentry.Tests/Protocol/MeasurementTests.cs index c4fe8c1c9f..762a74a9bb 100644 --- a/test/Sentry.Tests/Protocol/MeasurementTests.cs +++ b/test/Sentry.Tests/Protocol/MeasurementTests.cs @@ -182,10 +182,13 @@ public void Transaction_SetMeasurement_IntValue() transaction.Finish(); // Assert - client.Received(1).CaptureTransaction(Arg.Is(t => - t.Measurements.Count == 1 && - t.Measurements["foo"].Value.As() == int.MaxValue && - t.Measurements["foo"].Unit == EmptyUnit)); + client.Received(1).CaptureTransaction( + Arg.Is(t => + t.Measurements.Count == 1 && + t.Measurements["foo"].Value.As() == int.MaxValue && + t.Measurements["foo"].Unit == EmptyUnit), + Arg.Any() + ); } [Fact] @@ -206,10 +209,13 @@ public void Transaction_SetMeasurement_LongValue() transaction.Finish(); // Assert - client.Received(1).CaptureTransaction(Arg.Is(t => - t.Measurements.Count == 1 && - t.Measurements["foo"].Value.As() == long.MaxValue && - t.Measurements["foo"].Unit == EmptyUnit)); + client.Received(1).CaptureTransaction( + Arg.Is(t => + t.Measurements.Count == 1 && + t.Measurements["foo"].Value.As() == long.MaxValue && + t.Measurements["foo"].Unit == EmptyUnit), + Arg.Any() + ); } [Fact] @@ -230,10 +236,13 @@ public void Transaction_SetMeasurement_ULongValue() transaction.Finish(); // Assert - client.Received(1).CaptureTransaction(Arg.Is(t => - t.Measurements.Count == 1 && - t.Measurements["foo"].Value.As() == ulong.MaxValue && - t.Measurements["foo"].Unit == EmptyUnit)); + client.Received(1).CaptureTransaction( + Arg.Is(t => + t.Measurements.Count == 1 && + t.Measurements["foo"].Value.As() == ulong.MaxValue && + t.Measurements["foo"].Unit == EmptyUnit), + Arg.Any() + ); } [Fact] @@ -254,10 +263,13 @@ public void Transaction_SetMeasurement_DoubleValue() transaction.Finish(); // Assert - client.Received(1).CaptureTransaction(Arg.Is(t => - t.Measurements.Count == 1 && - Math.Abs(t.Measurements["foo"].Value.As() - double.MaxValue) < double.Epsilon && - t.Measurements["foo"].Unit == EmptyUnit)); + client.Received(1).CaptureTransaction( + Arg.Is(t => + t.Measurements.Count == 1 && + Math.Abs(t.Measurements["foo"].Value.As() - double.MaxValue) < double.Epsilon && + t.Measurements["foo"].Unit == EmptyUnit), + Arg.Any() + ); } [Fact] @@ -278,10 +290,13 @@ public void Transaction_SetMeasurement_IntValue_WithUnit() transaction.Finish(); // Assert - client.Received(1).CaptureTransaction(Arg.Is(t => - t.Measurements.Count == 1 && - t.Measurements["foo"].Value.As() == int.MaxValue && - t.Measurements["foo"].Unit == MeasurementUnit.Duration.Nanosecond)); + client.Received(1).CaptureTransaction( + Arg.Is(t => + t.Measurements.Count == 1 && + t.Measurements["foo"].Value.As() == int.MaxValue && + t.Measurements["foo"].Unit == MeasurementUnit.Duration.Nanosecond), + Arg.Any() + ); } [Fact] @@ -302,10 +317,13 @@ public void Transaction_SetMeasurement_LongValue_WithUnit() transaction.Finish(); // Assert - client.Received(1).CaptureTransaction(Arg.Is(t => - t.Measurements.Count == 1 && - t.Measurements["foo"].Value.As() == long.MaxValue && - t.Measurements["foo"].Unit == MeasurementUnit.Duration.Nanosecond)); + client.Received(1).CaptureTransaction( + Arg.Is(t => + t.Measurements.Count == 1 && + t.Measurements["foo"].Value.As() == long.MaxValue && + t.Measurements["foo"].Unit == MeasurementUnit.Duration.Nanosecond), + Arg.Any() + ); } [Fact] @@ -326,10 +344,13 @@ public void Transaction_SetMeasurement_ULongValue_WithUnit() transaction.Finish(); // Assert - client.Received(1).CaptureTransaction(Arg.Is(t => - t.Measurements.Count == 1 && - t.Measurements["foo"].Value.As() == ulong.MaxValue && - t.Measurements["foo"].Unit == MeasurementUnit.Duration.Nanosecond)); + client.Received(1).CaptureTransaction( + Arg.Is(t => + t.Measurements.Count == 1 && + t.Measurements["foo"].Value.As() == ulong.MaxValue && + t.Measurements["foo"].Unit == MeasurementUnit.Duration.Nanosecond), + Arg.Any() + ); } [Fact] @@ -350,9 +371,12 @@ public void Transaction_SetMeasurement_DoubleValue_WithUnit() transaction.Finish(); // Assert - client.Received(1).CaptureTransaction(Arg.Is(t => - t.Measurements.Count == 1 && - Math.Abs(t.Measurements["foo"].Value.As() - double.MaxValue) < double.Epsilon && - t.Measurements["foo"].Unit == MeasurementUnit.Duration.Nanosecond)); + client.Received(1).CaptureTransaction( + Arg.Is(t => + t.Measurements.Count == 1 && + Math.Abs(t.Measurements["foo"].Value.As() - double.MaxValue) < double.Epsilon && + t.Measurements["foo"].Unit == MeasurementUnit.Duration.Nanosecond), + Arg.Any() + ); } } diff --git a/test/Sentry.Tests/Protocol/TransactionTests.cs b/test/Sentry.Tests/Protocol/TransactionTests.cs index f03e929eef..7d5979b738 100644 --- a/test/Sentry.Tests/Protocol/TransactionTests.cs +++ b/test/Sentry.Tests/Protocol/TransactionTests.cs @@ -273,7 +273,7 @@ public void Finish_CapturesTransaction() transaction.Finish(); // Assert - client.Received(1).CaptureTransaction(Arg.Any()); + client.Received(1).CaptureTransaction(Arg.Any(), Arg.Any()); } [Fact] diff --git a/test/Sentry.Tests/SentryClientTests.cs b/test/Sentry.Tests/SentryClientTests.cs index 33691293af..2751a31390 100644 --- a/test/Sentry.Tests/SentryClientTests.cs +++ b/test/Sentry.Tests/SentryClientTests.cs @@ -688,7 +688,7 @@ public void CaptureTransaction_DisposedClient_DoesNotThrow() [Fact] public void CaptureTransaction_BeforeSendTransaction_RejectEvent() { - _fixture.SentryOptions.BeforeSendTransaction = _ => null; + _fixture.SentryOptions.SetBeforeSendTransaction(_ => null); var sut = _fixture.GetSut(); sut.CaptureTransaction( @@ -701,11 +701,34 @@ public void CaptureTransaction_BeforeSendTransaction_RejectEvent() _ = _fixture.BackgroundWorker.DidNotReceive().EnqueueEnvelope(Arg.Any()); } + [Fact] + public void CaptureTransaction_BeforeSendTransaction_GetsHint() + { + Hint received = null; + _fixture.SentryOptions.SetBeforeSendTransaction((tx, h) => + { + received = h; + return tx; + }); + + var transaction = new Transaction("test name", "test operation") + { + IsSampled = true, + EndTimestamp = DateTimeOffset.Now // finished + }; + + var sut = _fixture.GetSut(); + var hint = new Hint(); + sut.CaptureTransaction(transaction, hint); + + Assert.Same(hint, received); + } + [Fact] public void CaptureTransaction_BeforeSendTransaction_ModifyEvent() { Transaction received = null; - _fixture.SentryOptions.BeforeSendTransaction = tx => received = tx; + _fixture.SentryOptions.SetBeforeSendTransaction(tx => received = tx); var transaction = new Transaction("test name", "test operation") { @@ -723,7 +746,7 @@ public void CaptureTransaction_BeforeSendTransaction_ModifyEvent() public void CaptureTransaction_BeforeSendTransaction_replaced_transaction_captured() { Transaction received = null; - _fixture.SentryOptions.BeforeSendTransaction = tx => + _fixture.SentryOptions.SetBeforeSendTransaction(tx => { received = new Transaction("name2", "operation2") { @@ -733,7 +756,7 @@ public void CaptureTransaction_BeforeSendTransaction_replaced_transaction_captur }; return received; - }; + }); var transaction = new Transaction("name", "operation") { @@ -759,7 +782,7 @@ public void CaptureTransaction_BeforeSendTransaction_SamplingNull_DropsEvent() _fixture.SentryOptions.SampleRate = null; Transaction received = null; - _fixture.SentryOptions.BeforeSendTransaction = e => received = e; + _fixture.SentryOptions.SetBeforeSendTransaction(e => received = e); var transaction = new Transaction("test name", "test operation") { @@ -777,7 +800,7 @@ public void CaptureTransaction_BeforeSendTransaction_SamplingNull_DropsEvent() [Fact] public void CaptureTransaction_BeforeSendTransaction_RejectEvent_RecordsDiscard() { - _fixture.SentryOptions.BeforeSendTransaction = _ => null; + _fixture.SentryOptions.SetBeforeSendTransaction(_ => null); var sut = _fixture.GetSut(); sut.CaptureTransaction( new Transaction("test name", "test operation") diff --git a/test/Sentry.Tests/SentryClientTests.verify.cs b/test/Sentry.Tests/SentryClientTests.verify.cs index f408cf30e0..47c3d0a2bb 100644 --- a/test/Sentry.Tests/SentryClientTests.verify.cs +++ b/test/Sentry.Tests/SentryClientTests.verify.cs @@ -21,7 +21,7 @@ public Task CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb() public Task CaptureTransaction_BeforeSendTransactionThrows_ErrorToEventBreadcrumb() { var error = new Exception("Exception message!"); - _fixture.SentryOptions.BeforeSendTransaction = _ => throw error; + _fixture.SentryOptions.SetBeforeSendTransaction((_, _) => throw error); var transaction = new Transaction("name", "operation") { From 25b61f46994ab29f1d1475e8c5aeb2e555a8e8c1 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 9 May 2023 20:35:34 +1200 Subject: [PATCH 18/25] Attachments from the Scope get included in Hints before adding Bookmarks, capturing Events or sending Transactions --- .../Program.cs | 2 +- .../Program.cs | 4 +- src/Sentry/Hint.cs | 17 +++--- src/Sentry/Internal/Hub.cs | 11 ++-- src/Sentry/Scope.cs | 8 ++- src/Sentry/SentryClient.cs | 5 +- src/Sentry/SentryOptions.cs | 30 ++--------- .../ErrorProcessorTests.cs | 2 +- .../SentryMauiAppBuilderExtensionsTests.cs | 2 +- .../SerilogAspNetSentrySdkTestFixture.cs | 7 +-- .../ApiApprovalTests.Run.Core3_1.verified.txt | 13 +++-- ...piApprovalTests.Run.DotNet6_0.verified.txt | 13 +++-- ...piApprovalTests.Run.DotNet7_0.verified.txt | 13 +++-- .../ApiApprovalTests.Run.Net4_8.verified.txt | 13 +++-- test/Sentry.Tests/AttachmentHelper.cs | 13 +++++ test/Sentry.Tests/HintTests.cs | 52 +++---------------- test/Sentry.Tests/HubTests.cs | 28 ++++++++++ test/Sentry.Tests/ScopeTests.cs | 23 ++++++++ test/Sentry.Tests/SentryClientTests.cs | 43 +++++++++++---- 19 files changed, 169 insertions(+), 130 deletions(-) create mode 100644 test/Sentry.Tests/AttachmentHelper.cs diff --git a/samples/Sentry.Samples.Console.Customized/Program.cs b/samples/Sentry.Samples.Console.Customized/Program.cs index d77c79f028..ae347ef6f4 100644 --- a/samples/Sentry.Samples.Console.Customized/Program.cs +++ b/samples/Sentry.Samples.Console.Customized/Program.cs @@ -42,7 +42,7 @@ await SentrySdk.ConfigureScopeAsync(async scope => // o.SampleRate = 0.5f; // Randomly drop (don't send to Sentry) half of events // Modifications to event before it goes out. Could replace the event altogether - o.SetBeforeSend(@event => + o.SetBeforeSend((@event, _) => { // Drop an event altogether: if (@event.Tags.ContainsKey("SomeTag")) diff --git a/samples/Sentry.Samples.Console.Profiling/Program.cs b/samples/Sentry.Samples.Console.Profiling/Program.cs index 1f1980b3ed..bf3fdf1dc1 100644 --- a/samples/Sentry.Samples.Console.Profiling/Program.cs +++ b/samples/Sentry.Samples.Console.Profiling/Program.cs @@ -43,7 +43,7 @@ await SentrySdk.ConfigureScopeAsync(async scope => // o.SampleRate = 0.5f; // Randomly drop (don't send to Sentry) half of events // Modifications to event before it goes out. Could replace the event altogether - o.SetBeforeSend(@event => + o.SetBeforeSend((@event, _) => { // Drop an event altogether: if (@event.Tags.ContainsKey("SomeTag")) @@ -56,7 +56,7 @@ await SentrySdk.ConfigureScopeAsync(async scope => ); // Allows inspecting and modifying, returning a new or simply rejecting (returning null) - o.SetBeforeBreadcrumb(crumb => + o.SetBeforeBreadcrumb((crumb, _) => { // Don't add breadcrumbs with message containing: if (crumb.Message?.Contains("bad breadcrumb") == true) diff --git a/src/Sentry/Hint.cs b/src/Sentry/Hint.cs index 905fdd287c..0278d8e8cb 100644 --- a/src/Sentry/Hint.cs +++ b/src/Sentry/Hint.cs @@ -47,6 +47,13 @@ internal void AddAttachmentsInternal(IEnumerable attachments) /// public void AddAttachments(IEnumerable attachments) => AddAttachmentsInternal(attachments); + /// + /// The Java SDK has some logic so that certain Hint types do not copy attachments from the Scope. This + /// allows us to do the same in the .NET SDK in the future. + /// + /// The that the attachments should be copied from + internal void AddScopeAttachments(Scope scope) => AddAttachmentsInternal(scope.Attachments); + /// /// Attachments added to the Hint. /// @@ -57,16 +64,6 @@ internal void AddAttachmentsInternal(IEnumerable attachments) /// public IDictionary Items => _items; - /// - /// Gets or sets a Screenshot for the Hint - /// - public Attachment? Screenshot { get; set; } - - /// - /// Gets or sets a ViewHierarchy for the Hint - /// - public Attachment? ViewHierarchy { get; set; } - /// /// Creates a new Hint with one or more attachments. /// diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 4b67c7296f..0729015541 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -410,14 +410,18 @@ public void CaptureTransaction(Transaction transaction, Hint? hint) try { // Apply scope data - var currentScope = ScopeManager.GetCurrent(); - var scope = currentScope.Key; + var currentScopeAndClient = ScopeManager.GetCurrent(); + var scope = currentScopeAndClient.Key; scope.Evaluate(); scope.Apply(transaction); // Apply enricher _enricher.Apply(transaction); + // Add attachments to the hint for processors and callbacks + hint ??= new Hint(); + hint.AddScopeAttachments(scope); + var processedTransaction = transaction; if (transaction.IsSampled != false) { @@ -433,7 +437,8 @@ public void CaptureTransaction(Transaction transaction, Hint? hint) } } - currentScope.Value.CaptureTransaction(processedTransaction, hint); + var client = currentScopeAndClient.Value; + client.CaptureTransaction(processedTransaction, hint); } catch (Exception e) { diff --git a/src/Sentry/Scope.cs b/src/Sentry/Scope.cs index c99a53a204..17f891c710 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -255,6 +255,8 @@ public void AddBreadcrumb(Breadcrumb breadcrumb, Hint hint) { if (Options.BeforeBreadcrumbInternal is { } beforeBreadcrumb) { + hint.AddScopeAttachments(this); + if (beforeBreadcrumb(breadcrumb, hint) is { } processedBreadcrumb) { breadcrumb = processedBreadcrumb; @@ -469,8 +471,10 @@ public void Apply(Scope other) /// public Scope Clone() { - var clone = new Scope(Options); - clone.OnEvaluating = OnEvaluating; + var clone = new Scope(Options) + { + OnEvaluating = OnEvaluating + }; Apply(clone); diff --git a/src/Sentry/SentryClient.cs b/src/Sentry/SentryClient.cs index 6d02636893..eb6d101054 100644 --- a/src/Sentry/SentryClient.cs +++ b/src/Sentry/SentryClient.cs @@ -226,6 +226,8 @@ private SentryId DoSendEvent(SentryEvent @event, Hint? hint, Scope? scope) } scope ??= new Scope(_options); + hint ??= new Hint(); + hint.AddScopeAttachments(scope); _options.LogInfo("Capturing event."); @@ -326,7 +328,7 @@ private bool CaptureEnvelope(Envelope envelope) return false; } - private SentryEvent? BeforeSend(SentryEvent? @event, Hint? hint) + private SentryEvent? BeforeSend(SentryEvent? @event, Hint hint) { if (_options.BeforeSendInternal == null) { @@ -336,7 +338,6 @@ private bool CaptureEnvelope(Envelope envelope) _options.LogDebug("Calling the BeforeSend callback"); try { - hint ??= new Hint(); @event = _options.BeforeSendInternal?.Invoke(@event!, hint); } catch (Exception e) diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index 0ea7b9c619..5321d30459 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -319,15 +319,6 @@ public float? SampleRate set => _beforeSend = value is null ? null : (e, _) => value(e); } - /// - /// Configures a callback to invoke before sending an event to Sentry - /// - /// - public void SetBeforeSend(Func beforeSend) - { - _beforeSend = (e, _) => beforeSend(e); - } - /// /// Configures a callback function to be invoked before sending an event to Sentry /// @@ -359,15 +350,6 @@ public void SetBeforeSend(Func beforeSend) set => _beforeSendTransaction = value is null ? null : (e, _) => value(e); } - /// - /// Configures a callback to invoke before sending a transaction to Sentry - /// - /// The callback - public void SetBeforeSendTransaction(Func beforeSendTransaction) - { - _beforeSendTransaction = (e, _) => beforeSendTransaction(e); - } - /// /// Configures a callback to invoke before sending a transaction to Sentry /// @@ -386,15 +368,9 @@ public void SetBeforeSendTransaction(Func befor /// /// [Obsolete("This property will be removed in a future version. Use SetBeforeBreadcrumb instead.")] - public Func? BeforeBreadcrumb { get; set; } - - /// - /// Sets a callback function to be invoked when a breadcrumb is about to be stored. - /// - /// - public void SetBeforeBreadcrumb(Func beforeBreadcrumb) - { - _beforeBreadcrumb = (e, _) => beforeBreadcrumb(e); + public Func? BeforeBreadcrumb { + get => null; + set => _beforeBreadcrumb = value is null ? null : (e, _) => value(e); } /// diff --git a/test/Sentry.EntityFramework.Tests/ErrorProcessorTests.cs b/test/Sentry.EntityFramework.Tests/ErrorProcessorTests.cs index f31d6dd290..08533cb40b 100644 --- a/test/Sentry.EntityFramework.Tests/ErrorProcessorTests.cs +++ b/test/Sentry.EntityFramework.Tests/ErrorProcessorTests.cs @@ -61,7 +61,7 @@ public async Task Integration_DbEntityValidationExceptionProcessorAsync() { Exception assertError = null; // SaveChanges will throw an exception - options.SetBeforeSend(evt => + options.SetBeforeSend((evt, _) => { // We use a try-catch here as we cannot assert directly since SentryClient itself would catch the thrown assertion errors try diff --git a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs index 4d28523be2..cb301eacf5 100644 --- a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs +++ b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs @@ -148,7 +148,7 @@ public void UseSentry_SetsMauiSdkNameAndVersion() .UseSentry(options => { options.Dsn = ValidDsn; - options.SetBeforeSend(e => + options.SetBeforeSend((e, _) => { // capture the event @event = e; diff --git a/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs b/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs index 0676d22212..8f3c49f4d3 100644 --- a/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs +++ b/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs @@ -11,12 +11,7 @@ protected override void ConfigureBuilder(WebHostBuilder builder) Events = new List(); Configure = options => { - options.SetBeforeSend(@event => - { - Events.Add(@event); - return @event; - } - ); + options.SetBeforeSend((@event, _) => { Events.Add(@event); return @event; }); }; ConfigureApp = app => diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt index f53308bb0e..b8bb88f0d8 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt @@ -144,8 +144,6 @@ namespace Sentry public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } public System.Collections.Generic.IDictionary Items { get; } - public Sentry.Attachment? Screenshot { get; set; } - public Sentry.Attachment? ViewHierarchy { get; set; } public void AddAttachments(params Sentry.Attachment[] attachments) { } public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } @@ -647,11 +645,8 @@ namespace Sentry public Sentry.Extensibility.ITransport? Transport { get; set; } public bool UseAsyncFileIO { get; set; } public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } - public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } - public void SetBeforeSend(System.Func beforeSend) { } public void SetBeforeSend(System.Func beforeSend) { } - public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } } public static class SentryOptionsExtensions @@ -1260,6 +1255,14 @@ namespace Sentry.Extensibility bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } + public interface IContextualSentryEventProcessor + { + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint? hint); + } + public interface IContextualSentryTransactionProcessor + { + Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint? hint); + } public interface IDiagnosticLogger { bool IsEnabled(Sentry.SentryLevel level); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt index 4b0f571319..2edeeb46a4 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -144,8 +144,6 @@ namespace Sentry public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } public System.Collections.Generic.IDictionary Items { get; } - public Sentry.Attachment? Screenshot { get; set; } - public Sentry.Attachment? ViewHierarchy { get; set; } public void AddAttachments(params Sentry.Attachment[] attachments) { } public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } @@ -648,11 +646,8 @@ namespace Sentry public Sentry.Extensibility.ITransport? Transport { get; set; } public bool UseAsyncFileIO { get; set; } public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } - public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } - public void SetBeforeSend(System.Func beforeSend) { } public void SetBeforeSend(System.Func beforeSend) { } - public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } } public static class SentryOptionsExtensions @@ -1261,6 +1256,14 @@ namespace Sentry.Extensibility bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } + public interface IContextualSentryEventProcessor + { + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint? hint); + } + public interface IContextualSentryTransactionProcessor + { + Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint? hint); + } public interface IDiagnosticLogger { bool IsEnabled(Sentry.SentryLevel level); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt index 4b0f571319..2edeeb46a4 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -144,8 +144,6 @@ namespace Sentry public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } public System.Collections.Generic.IDictionary Items { get; } - public Sentry.Attachment? Screenshot { get; set; } - public Sentry.Attachment? ViewHierarchy { get; set; } public void AddAttachments(params Sentry.Attachment[] attachments) { } public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } @@ -648,11 +646,8 @@ namespace Sentry public Sentry.Extensibility.ITransport? Transport { get; set; } public bool UseAsyncFileIO { get; set; } public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } - public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } - public void SetBeforeSend(System.Func beforeSend) { } public void SetBeforeSend(System.Func beforeSend) { } - public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } } public static class SentryOptionsExtensions @@ -1261,6 +1256,14 @@ namespace Sentry.Extensibility bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } + public interface IContextualSentryEventProcessor + { + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint? hint); + } + public interface IContextualSentryTransactionProcessor + { + Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint? hint); + } public interface IDiagnosticLogger { bool IsEnabled(Sentry.SentryLevel level); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 38bbcbc072..bafe95c91b 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -143,8 +143,6 @@ namespace Sentry public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } public System.Collections.Generic.IDictionary Items { get; } - public Sentry.Attachment? Screenshot { get; set; } - public Sentry.Attachment? ViewHierarchy { get; set; } public void AddAttachments(params Sentry.Attachment[] attachments) { } public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } @@ -646,11 +644,8 @@ namespace Sentry public Sentry.Extensibility.ITransport? Transport { get; set; } public bool UseAsyncFileIO { get; set; } public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } - public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } - public void SetBeforeSend(System.Func beforeSend) { } public void SetBeforeSend(System.Func beforeSend) { } - public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } } public static class SentryOptionsExtensions @@ -1259,6 +1254,14 @@ namespace Sentry.Extensibility bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } + public interface IContextualSentryEventProcessor + { + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint? hint); + } + public interface IContextualSentryTransactionProcessor + { + Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint? hint); + } public interface IDiagnosticLogger { bool IsEnabled(Sentry.SentryLevel level); diff --git a/test/Sentry.Tests/AttachmentHelper.cs b/test/Sentry.Tests/AttachmentHelper.cs new file mode 100644 index 0000000000..53c27b7cf2 --- /dev/null +++ b/test/Sentry.Tests/AttachmentHelper.cs @@ -0,0 +1,13 @@ +namespace Sentry.Tests +{ + internal static class AttachmentHelper + { + internal static Attachment FakeAttachment(string name = "test.txt") + => new( + AttachmentType.Default, + new StreamAttachmentContent(new MemoryStream(new byte[] { 1 })), + name, + null + ); + } +} diff --git a/test/Sentry.Tests/HintTests.cs b/test/Sentry.Tests/HintTests.cs index baab200b06..717c170e01 100644 --- a/test/Sentry.Tests/HintTests.cs +++ b/test/Sentry.Tests/HintTests.cs @@ -2,14 +2,6 @@ namespace Sentry.Tests; public class HintTests { - private Attachment FakeAttachment(string name = "test.txt") - => new( - AttachmentType.Default, - new StreamAttachmentContent(new MemoryStream(new byte[] { 1 })), - name, - null - ); - [Fact] public void AddAttachments_WithNullAttachments_DoesNothing() { @@ -28,8 +20,8 @@ public void AddAttachments_WithAttachments_AddsToHint() { // Arrange var hint = new Hint(); - var attachment1 = FakeAttachment("attachment1"); - var attachment2 = FakeAttachment("attachment2"); + var attachment1 = AttachmentHelper.FakeAttachment("attachment1"); + var attachment2 = AttachmentHelper.FakeAttachment("attachment2"); // Act hint.AddAttachments(attachment1, attachment2); @@ -56,7 +48,7 @@ public void ClearAttachments_WithAttachments_ClearsHintAttachments() { // Arrange var hint = new Hint(); - var attachment1 = FakeAttachment("attachment1"); + var attachment1 = AttachmentHelper.FakeAttachment("attachment1"); hint.AddAttachments(attachment1); // Act @@ -133,42 +125,12 @@ public void Remove_WithExistingKey_RemovesEntry() hint.Items.ContainsKey("key").Should().BeFalse(); } - [Fact] - public void Screenshot_PropertyCanBeSetAndRetrieved() - { - // Arrange - var hint = new Hint(); - var screenshot = FakeAttachment("screenshot.png"); - - // Act - hint.Screenshot = screenshot; - var retrievedScreenshot = hint.Screenshot; - - // Assert - retrievedScreenshot.Should().Be(screenshot); - } - - [Fact] - public void ViewHierarchy_PropertyCanBeSetAndRetrieved() - { - // Arrange - var hint = new Hint(); - var viewHierarchy = FakeAttachment("view_hierarchy.xml"); - - // Act - hint.ViewHierarchy = viewHierarchy; - var retrievedViewHierarchy = hint.ViewHierarchy; - - // Assert - retrievedViewHierarchy.Should().Be(viewHierarchy); - } - [Fact] public void WithAttachments_ReturnsHintWithAttachments() { // Arrange - var attachment1 = FakeAttachment("attachment1"); - var attachment2 = FakeAttachment("attachment2"); + var attachment1 = AttachmentHelper.FakeAttachment("attachment1"); + var attachment2 = AttachmentHelper.FakeAttachment("attachment2"); // Act var hint = Hint.WithAttachments(attachment1, attachment2); @@ -183,8 +145,8 @@ public void WithAttachments_ReturnsHintWithAttachments() public void WithAttachments_WithICollection_ReturnsHintWithAttachments() { // Arrange - var attachment1 = FakeAttachment("attachment1"); - var attachment2 = FakeAttachment("attachment2"); + var attachment1 = AttachmentHelper.FakeAttachment("attachment1"); + var attachment2 = AttachmentHelper.FakeAttachment("attachment2"); var attachments = new List { attachment1, attachment2 }; // Act diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index cbabb6ebfa..57905c6131 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1143,6 +1143,34 @@ public void CaptureTransaction_Provides_Hint_To_Client() _fixture.Client.Received().CaptureTransaction(Arg.Any(), Arg.Any()); } + [Fact] + public void CaptureTransaction_Hint_Gets_Attachments() + { + // Arrange + var hub = _fixture.GetSut(); + List attachments = new List { + AttachmentHelper.FakeAttachment("foo"), + AttachmentHelper.FakeAttachment("bar") + }; + hub.ConfigureScope(s => { + s.AddAttachment(attachments[0]); + s.AddAttachment(attachments[1]); + }); + + // Act + Hint hint = null; + _fixture.Client.CaptureTransaction( + Arg.Any(), + Arg.Do(h => hint = h) + ); + var transaction = hub.StartTransaction("test", "test"); + transaction.Finish(); + + // Assert + hint.Should().NotBeNull(); + hint.Attachments.Should().Contain(attachments); + } + #if ANDROID && CI_BUILD // TODO: Test is flaky in CI [SkippableTheory(typeof(NSubstitute.Exceptions.ReceivedCallsException))] diff --git a/test/Sentry.Tests/ScopeTests.cs b/test/Sentry.Tests/ScopeTests.cs index 6e7b707225..a0affcd1dd 100644 --- a/test/Sentry.Tests/ScopeTests.cs +++ b/test/Sentry.Tests/ScopeTests.cs @@ -330,6 +330,29 @@ public void AddBreadcrumb_BeforeAddBreadcrumb_ReceivesHint() receivedHint.Should().BeSameAs(expectedHint); } + [Fact] + public void AddBreadcrumb_ScopeAttachments_Copied_To_Hint() + { + // Arrange + var options = new SentryOptions(); + Hint hint = null; + options.SetBeforeBreadcrumb((b, h) => + { + hint = h; + return b; + }); + var scope = new Scope(options); + scope.AddAttachment(AttachmentHelper.FakeAttachment("foo.txt")); + scope.AddAttachment(AttachmentHelper.FakeAttachment("bar.txt")); + + // Act + scope.AddBreadcrumb(new Breadcrumb()); + + // Assert + hint.Should().NotBeNull(); + hint.Attachments.Should().Contain(scope.Attachments); + } + [Theory] [InlineData("123@123.com", null, null, true)] [InlineData("123@123.com", null, null, false)] diff --git a/test/Sentry.Tests/SentryClientTests.cs b/test/Sentry.Tests/SentryClientTests.cs index 2751a31390..d221ccf145 100644 --- a/test/Sentry.Tests/SentryClientTests.cs +++ b/test/Sentry.Tests/SentryClientTests.cs @@ -249,7 +249,7 @@ public void CaptureEvent_EventAndScope_CopyScopeIntoEvent() [Fact] public void CaptureEvent_BeforeEvent_RejectEvent() { - _fixture.SentryOptions.SetBeforeSend(_ => null); + _fixture.SentryOptions.SetBeforeSend((_, _) => null); var expectedEvent = new SentryEvent(); var sut = _fixture.GetSut(); @@ -262,7 +262,7 @@ public void CaptureEvent_BeforeEvent_RejectEvent() [Fact] public void CaptureEvent_BeforeEvent_RejectEvent_RecordsDiscard() { - _fixture.SentryOptions.SetBeforeSend(_ => null); + _fixture.SentryOptions.SetBeforeSend((_, _) => null); var sut = _fixture.GetSut(); _ = sut.CaptureEvent(new SentryEvent()); @@ -319,11 +319,34 @@ public void CaptureEvent_BeforeSend_GetsHint() Assert.Same(hint, received); } + [Fact] + public void CaptureEvent_Add_ScopeAttachments_To_Hint() + { + // Arrange + Hint hint = null; + _fixture.SentryOptions.SetBeforeSend((e, h) => { + hint = h; + return e; + }); + var scope = new Scope(_fixture.SentryOptions); + scope.AddAttachment(AttachmentHelper.FakeAttachment("foo.txt")); + scope.AddAttachment(AttachmentHelper.FakeAttachment("bar.txt")); + + var sut = _fixture.GetSut(); + + // Act + _ = sut.CaptureEvent(new SentryEvent(), scope); + + // Assert + hint.Should().NotBeNull(); + hint.Attachments.Should().Contain(scope.Attachments); + } + [Fact] public void CaptureEvent_BeforeEvent_ModifyEvent() { SentryEvent received = null; - _fixture.SentryOptions.SetBeforeSend(e => received = e); + _fixture.SentryOptions.SetBeforeSend((e, _) => received = e); var @event = new SentryEvent(); @@ -384,7 +407,7 @@ public void CaptureEvent_SamplingHighest_SendsEvent() // Largest value allowed. Should always send _fixture.SentryOptions.SampleRate = 1; SentryEvent received = null; - _fixture.SentryOptions.SetBeforeSend(e => received = e); + _fixture.SentryOptions.SetBeforeSend((e, _) => received = e); var @event = new SentryEvent(); @@ -400,7 +423,7 @@ public void CaptureEvent_SamplingNull_DropsEvent() { _fixture.SentryOptions.SampleRate = null; SentryEvent received = null; - _fixture.SentryOptions.SetBeforeSend(e => received = e); + _fixture.SentryOptions.SetBeforeSend((e, _) => received = e); var @event = new SentryEvent(); @@ -688,7 +711,7 @@ public void CaptureTransaction_DisposedClient_DoesNotThrow() [Fact] public void CaptureTransaction_BeforeSendTransaction_RejectEvent() { - _fixture.SentryOptions.SetBeforeSendTransaction(_ => null); + _fixture.SentryOptions.SetBeforeSendTransaction((_, _) => null); var sut = _fixture.GetSut(); sut.CaptureTransaction( @@ -728,7 +751,7 @@ public void CaptureTransaction_BeforeSendTransaction_GetsHint() public void CaptureTransaction_BeforeSendTransaction_ModifyEvent() { Transaction received = null; - _fixture.SentryOptions.SetBeforeSendTransaction(tx => received = tx); + _fixture.SentryOptions.SetBeforeSendTransaction((tx, _) => received = tx); var transaction = new Transaction("test name", "test operation") { @@ -746,7 +769,7 @@ public void CaptureTransaction_BeforeSendTransaction_ModifyEvent() public void CaptureTransaction_BeforeSendTransaction_replaced_transaction_captured() { Transaction received = null; - _fixture.SentryOptions.SetBeforeSendTransaction(tx => + _fixture.SentryOptions.SetBeforeSendTransaction((_, _) => { received = new Transaction("name2", "operation2") { @@ -782,7 +805,7 @@ public void CaptureTransaction_BeforeSendTransaction_SamplingNull_DropsEvent() _fixture.SentryOptions.SampleRate = null; Transaction received = null; - _fixture.SentryOptions.SetBeforeSendTransaction(e => received = e); + _fixture.SentryOptions.SetBeforeSendTransaction((e, _) => received = e); var transaction = new Transaction("test name", "test operation") { @@ -800,7 +823,7 @@ public void CaptureTransaction_BeforeSendTransaction_SamplingNull_DropsEvent() [Fact] public void CaptureTransaction_BeforeSendTransaction_RejectEvent_RecordsDiscard() { - _fixture.SentryOptions.SetBeforeSendTransaction(_ => null); + _fixture.SentryOptions.SetBeforeSendTransaction((_, _) => null); var sut = _fixture.GetSut(); sut.CaptureTransaction( new Transaction("test name", "test operation") From aa6d91e9828813d072a2a85dd57cf56f825bd51d Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 9 May 2023 22:18:45 +1200 Subject: [PATCH 19/25] Added hint support for Transaction/Event processors --- .../IContextualSentryEventProcessor.cs | 20 +++++++++ .../IContextualSentryTransactionProcessor.cs | 19 ++++++++ .../Extensibility/ISentryEventProcessor.cs | 12 +++++- .../ISentryTransactionProcessor.cs | 12 +++++- src/Sentry/Internal/Hub.cs | 2 +- src/Sentry/SentryClient.cs | 3 +- src/Sentry/SentryOptionsExtensions.cs | 1 - .../ApplicationBuilderExtensionsTests.cs | 1 - .../ApiApprovalTests.Run.Core3_1.verified.txt | 8 ++-- ...piApprovalTests.Run.DotNet6_0.verified.txt | 8 ++-- ...piApprovalTests.Run.DotNet7_0.verified.txt | 8 ++-- .../ApiApprovalTests.Run.Net4_8.verified.txt | 8 ++-- test/Sentry.Tests/HubTests.cs | 43 ++++++++++++++++++- test/Sentry.Tests/SentryClientTests.cs | 42 +++++++++++++++++- 14 files changed, 161 insertions(+), 26 deletions(-) create mode 100644 src/Sentry/Extensibility/IContextualSentryEventProcessor.cs create mode 100644 src/Sentry/Extensibility/IContextualSentryTransactionProcessor.cs diff --git a/src/Sentry/Extensibility/IContextualSentryEventProcessor.cs b/src/Sentry/Extensibility/IContextualSentryEventProcessor.cs new file mode 100644 index 0000000000..6fa0ed7a45 --- /dev/null +++ b/src/Sentry/Extensibility/IContextualSentryEventProcessor.cs @@ -0,0 +1,20 @@ +namespace Sentry.Extensibility; + +/// +/// Process a SentryEvent during the prepare phase. +/// +public interface IContextualSentryEventProcessor: ISentryEventProcessor +{ + /// + /// Process the + /// + /// The event to process + /// A with context that may be useful prior to sending the event + /// The processed event or null if the event was dropped. + /// + /// The event returned can be the same instance received or a new one. + /// Returning null will stop the processing pipeline so that the event will neither be processed by + /// additional event processors or sent to Sentry. + /// + SentryEvent? Process(SentryEvent @event, Hint hint); +} diff --git a/src/Sentry/Extensibility/IContextualSentryTransactionProcessor.cs b/src/Sentry/Extensibility/IContextualSentryTransactionProcessor.cs new file mode 100644 index 0000000000..62cdae5531 --- /dev/null +++ b/src/Sentry/Extensibility/IContextualSentryTransactionProcessor.cs @@ -0,0 +1,19 @@ +namespace Sentry.Extensibility; + +/// +/// Process a during the prepare phase. +/// +public interface IContextualSentryTransactionProcessor: ISentryTransactionProcessor +{ + /// + /// Process the + /// + /// The Transaction to process + /// A with context that may be useful prior to sending the transaction + /// + /// The transaction returned can be the same instance received or a new one. + /// Returning null will stop the processing pipeline. + /// Meaning the transaction should no longer be processed nor send. + /// + Transaction? Process(Transaction transaction, Hint hint); +} diff --git a/src/Sentry/Extensibility/ISentryEventProcessor.cs b/src/Sentry/Extensibility/ISentryEventProcessor.cs index 447515d277..da9d34f74f 100644 --- a/src/Sentry/Extensibility/ISentryEventProcessor.cs +++ b/src/Sentry/Extensibility/ISentryEventProcessor.cs @@ -16,4 +16,14 @@ public interface ISentryEventProcessor /// Meaning the event should no longer be processed nor send. /// SentryEvent? Process(SentryEvent @event); -} \ No newline at end of file +} + +internal static class ISentryEventProcessorExtensions +{ + internal static SentryEvent? DoProcessEvent(this ISentryEventProcessor processor, SentryEvent @event, Hint hint) + { + return (processor is IContextualSentryEventProcessor contextualProcessor) + ? contextualProcessor.Process(@event, hint) + : processor.Process(@event); + } +} diff --git a/src/Sentry/Extensibility/ISentryTransactionProcessor.cs b/src/Sentry/Extensibility/ISentryTransactionProcessor.cs index 50a8757dbe..e9b8202ff5 100644 --- a/src/Sentry/Extensibility/ISentryTransactionProcessor.cs +++ b/src/Sentry/Extensibility/ISentryTransactionProcessor.cs @@ -1,4 +1,4 @@ -namespace Sentry.Extensibility; +namespace Sentry.Extensibility; /// /// Process a during the prepare phase. @@ -16,3 +16,13 @@ public interface ISentryTransactionProcessor /// Transaction? Process(Transaction transaction); } + +internal static class ISentryTransactionProcessorExtensions +{ + internal static Transaction? DoProcessTransaction(this ISentryTransactionProcessor processor, Transaction transaction, Hint hint) + { + return (processor is IContextualSentryTransactionProcessor contextualProcessor) + ? contextualProcessor.Process(transaction, hint) + : processor.Process(transaction); + } +} diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 0729015541..4b842d8044 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -427,7 +427,7 @@ public void CaptureTransaction(Transaction transaction, Hint? hint) { foreach (var processor in scope.GetAllTransactionProcessors()) { - processedTransaction = processor.Process(transaction); + processedTransaction = processor.DoProcessTransaction(transaction, hint); if (processedTransaction == null) { _options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.EventProcessor, DataCategory.Transaction); diff --git a/src/Sentry/SentryClient.cs b/src/Sentry/SentryClient.cs index eb6d101054..391bec42fb 100644 --- a/src/Sentry/SentryClient.cs +++ b/src/Sentry/SentryClient.cs @@ -258,7 +258,8 @@ private SentryId DoSendEvent(SentryEvent @event, Hint? hint, Scope? scope) foreach (var processor in scope.GetAllEventProcessors()) { - processedEvent = processor.Process(processedEvent); + processedEvent = processor.DoProcessEvent(processedEvent, hint); + if (processedEvent == null) { _options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.EventProcessor, DataCategory.Error); diff --git a/src/Sentry/SentryOptionsExtensions.cs b/src/Sentry/SentryOptionsExtensions.cs index 0876d285d6..39e8a61e5e 100644 --- a/src/Sentry/SentryOptionsExtensions.cs +++ b/src/Sentry/SentryOptionsExtensions.cs @@ -446,4 +446,3 @@ internal static void SetupLogging(this SentryOptions options) return options.TryGetDsnSpecificCacheDirectoryPath(); } } - diff --git a/test/Sentry.AspNetCore.Tests/ApplicationBuilderExtensionsTests.cs b/test/Sentry.AspNetCore.Tests/ApplicationBuilderExtensionsTests.cs index 9bb4a032f6..a6d2f52bea 100644 --- a/test/Sentry.AspNetCore.Tests/ApplicationBuilderExtensionsTests.cs +++ b/test/Sentry.AspNetCore.Tests/ApplicationBuilderExtensionsTests.cs @@ -78,7 +78,6 @@ public void UseSentry_OriginalEventExceptionProcessor_StillAvailable() var sut = _fixture.GetSut(); - _ = sut.UseSentry(); var missing = originalProviders.Except(_fixture.SentryAspNetCoreOptions.ExceptionProcessorsProviders); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt index b8bb88f0d8..e91a72ef71 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt @@ -1255,13 +1255,13 @@ namespace Sentry.Extensibility bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } - public interface IContextualSentryEventProcessor + public interface IContextualSentryEventProcessor : Sentry.Extensibility.ISentryEventProcessor { - Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint? hint); + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint hint); } - public interface IContextualSentryTransactionProcessor + public interface IContextualSentryTransactionProcessor : Sentry.Extensibility.ISentryTransactionProcessor { - Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint? hint); + Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint hint); } public interface IDiagnosticLogger { diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt index 2edeeb46a4..05d0a39f1e 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -1256,13 +1256,13 @@ namespace Sentry.Extensibility bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } - public interface IContextualSentryEventProcessor + public interface IContextualSentryEventProcessor : Sentry.Extensibility.ISentryEventProcessor { - Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint? hint); + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint hint); } - public interface IContextualSentryTransactionProcessor + public interface IContextualSentryTransactionProcessor : Sentry.Extensibility.ISentryTransactionProcessor { - Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint? hint); + Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint hint); } public interface IDiagnosticLogger { diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt index 2edeeb46a4..05d0a39f1e 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -1256,13 +1256,13 @@ namespace Sentry.Extensibility bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } - public interface IContextualSentryEventProcessor + public interface IContextualSentryEventProcessor : Sentry.Extensibility.ISentryEventProcessor { - Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint? hint); + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint hint); } - public interface IContextualSentryTransactionProcessor + public interface IContextualSentryTransactionProcessor : Sentry.Extensibility.ISentryTransactionProcessor { - Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint? hint); + Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint hint); } public interface IDiagnosticLogger { diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index bafe95c91b..c59a9e6f3b 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -1254,13 +1254,13 @@ namespace Sentry.Extensibility bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } - public interface IContextualSentryEventProcessor + public interface IContextualSentryEventProcessor : Sentry.Extensibility.ISentryEventProcessor { - Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint? hint); + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint hint); } - public interface IContextualSentryTransactionProcessor + public interface IContextualSentryTransactionProcessor : Sentry.Extensibility.ISentryTransactionProcessor { - Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint? hint); + Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint hint); } public interface IDiagnosticLogger { diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 57905c6131..e80b633819 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1130,7 +1130,7 @@ public void CaptureTransaction_HubEnabled(bool enabled) } [Fact] - public void CaptureTransaction_Provides_Hint_To_Client() + public void CaptureTransaction_Client_Gets_Hint() { // Arrange var hub = _fixture.GetSut(); @@ -1144,7 +1144,7 @@ public void CaptureTransaction_Provides_Hint_To_Client() } [Fact] - public void CaptureTransaction_Hint_Gets_Attachments() + public void CaptureTransaction_Client_Gets_ScopeAttachments() { // Arrange var hub = _fixture.GetSut(); @@ -1171,6 +1171,45 @@ public void CaptureTransaction_Hint_Gets_Attachments() hint.Attachments.Should().Contain(attachments); } + [Fact] + public void CaptureTransaction_EventProcessor_Gets_Hint() + { + // Arrange + var processor = Substitute.For(); + processor.Process(Arg.Any(), Arg.Any()).Returns(new Transaction("name", "operation")); + _fixture.Options.AddTransactionProcessor(processor); + + // Act + var hub = _fixture.GetSut(); + var transaction = hub.StartTransaction("test", "test"); + transaction.Finish(); + + // Assert + processor.Received(1).Process(Arg.Any(), Arg.Any()); + } + + [Fact] + public void CaptureTransaction_EventProcessor_Gets_ScopeAttachments() + { + // Arrange + var processor = Substitute.For(); + Hint hint = null; + processor.Process(Arg.Any(), Arg.Do(h => hint = h)).Returns(new Transaction("name", "operation")); + _fixture.Options.AddTransactionProcessor(processor); + + List attachments = new List { AttachmentHelper.FakeAttachment("foo.txt") }; + var hub = _fixture.GetSut(); + hub.ConfigureScope(s => s.AddAttachment(attachments[0])); + + // Act + var transaction = hub.StartTransaction("test", "test"); + transaction.Finish(); + + // Assert + hint.Should().NotBeNull(); + hint.Attachments.Should().Contain(attachments); + } + #if ANDROID && CI_BUILD // TODO: Test is flaky in CI [SkippableTheory(typeof(NSubstitute.Exceptions.ReceivedCallsException))] diff --git a/test/Sentry.Tests/SentryClientTests.cs b/test/Sentry.Tests/SentryClientTests.cs index d221ccf145..c5ee0216eb 100644 --- a/test/Sentry.Tests/SentryClientTests.cs +++ b/test/Sentry.Tests/SentryClientTests.cs @@ -1,3 +1,4 @@ +using FluentAssertions.Execution; using Sentry.Internal.Http; using BackgroundWorker = Sentry.Internal.BackgroundWorker; @@ -320,7 +321,7 @@ public void CaptureEvent_BeforeSend_GetsHint() } [Fact] - public void CaptureEvent_Add_ScopeAttachments_To_Hint() + public void CaptureEvent_BeforeSend_Gets_ScopeAttachments() { // Arrange Hint hint = null; @@ -343,7 +344,44 @@ public void CaptureEvent_Add_ScopeAttachments_To_Hint() } [Fact] - public void CaptureEvent_BeforeEvent_ModifyEvent() + public void CaptureEvent_EventProcessor_Gets_Hint() + { + // Arrange + var processor = Substitute.For(); + processor.Process(Arg.Any(), Arg.Any()).Returns(new SentryEvent()); + _fixture.SentryOptions.AddEventProcessor(processor); + + // Act + var sut = _fixture.GetSut(); + _ = sut.CaptureEvent(new SentryEvent()); + + // Assert + processor.Received(1).Process(Arg.Any(), Arg.Any()); + } + + [Fact] + public void CaptureEvent_EventProcessor_Gets_ScopeAttachments() + { + // Arrange + var processor = Substitute.For(); + Hint hint = null; + processor.Process(Arg.Any(), Arg.Do(h => hint = h)).Returns(new SentryEvent()); + _fixture.SentryOptions.AddEventProcessor(processor); + + Scope scope = new Scope(_fixture.SentryOptions); + scope.AddAttachment(AttachmentHelper.FakeAttachment("foo.txt")); + + // Act + var sut = _fixture.GetSut(); + _ = sut.CaptureEvent(new SentryEvent(), scope); + + // Assert + hint.Should().NotBeNull(); + hint.Attachments.Should().Contain(scope.Attachments); + } + + [Fact] + public void CaptureEvent_BeforeSend_ModifyEvent() { SentryEvent received = null; _fixture.SentryOptions.SetBeforeSend((e, _) => received = e); From 860af915dc072209aa3aa9327513f029572eb2f5 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Wed, 10 May 2023 10:36:35 +1200 Subject: [PATCH 20/25] - Renamed Contextual processors to ProcessorWithHint (more specific) - Added overload to CaptureEvent to allow passing both Hint and ConfigureScope callback --- .../Extensibility/ISentryEventProcessor.cs | 2 +- ...essor.cs => ISentryEventProcessorWithHint.cs} | 3 ++- .../Extensibility/ISentryTransactionProcessor.cs | 2 +- ...cs => ISentryTransactionProcessorWithHint.cs} | 2 +- src/Sentry/Internal/Hub.cs | 7 +++++-- .../ApiApprovalTests.Run.Core3_1.verified.txt | 16 ++++++++-------- .../ApiApprovalTests.Run.DotNet6_0.verified.txt | 16 ++++++++-------- .../ApiApprovalTests.Run.DotNet7_0.verified.txt | 16 ++++++++-------- .../ApiApprovalTests.Run.Net4_8.verified.txt | 16 ++++++++-------- test/Sentry.Tests/HubTests.cs | 4 ++-- test/Sentry.Tests/SentryClientTests.cs | 4 ++-- 11 files changed, 46 insertions(+), 42 deletions(-) rename src/Sentry/Extensibility/{IContextualSentryEventProcessor.cs => ISentryEventProcessorWithHint.cs} (91%) rename src/Sentry/Extensibility/{IContextualSentryTransactionProcessor.cs => ISentryTransactionProcessorWithHint.cs} (89%) diff --git a/src/Sentry/Extensibility/ISentryEventProcessor.cs b/src/Sentry/Extensibility/ISentryEventProcessor.cs index da9d34f74f..229aa3339a 100644 --- a/src/Sentry/Extensibility/ISentryEventProcessor.cs +++ b/src/Sentry/Extensibility/ISentryEventProcessor.cs @@ -22,7 +22,7 @@ internal static class ISentryEventProcessorExtensions { internal static SentryEvent? DoProcessEvent(this ISentryEventProcessor processor, SentryEvent @event, Hint hint) { - return (processor is IContextualSentryEventProcessor contextualProcessor) + return (processor is ISentryEventProcessorWithHint contextualProcessor) ? contextualProcessor.Process(@event, hint) : processor.Process(@event); } diff --git a/src/Sentry/Extensibility/IContextualSentryEventProcessor.cs b/src/Sentry/Extensibility/ISentryEventProcessorWithHint.cs similarity index 91% rename from src/Sentry/Extensibility/IContextualSentryEventProcessor.cs rename to src/Sentry/Extensibility/ISentryEventProcessorWithHint.cs index 6fa0ed7a45..61b4fcf96d 100644 --- a/src/Sentry/Extensibility/IContextualSentryEventProcessor.cs +++ b/src/Sentry/Extensibility/ISentryEventProcessorWithHint.cs @@ -3,7 +3,7 @@ namespace Sentry.Extensibility; /// /// Process a SentryEvent during the prepare phase. /// -public interface IContextualSentryEventProcessor: ISentryEventProcessor +public interface ISentryEventProcessorWithHint: ISentryEventProcessor { /// /// Process the @@ -18,3 +18,4 @@ public interface IContextualSentryEventProcessor: ISentryEventProcessor /// SentryEvent? Process(SentryEvent @event, Hint hint); } + diff --git a/src/Sentry/Extensibility/ISentryTransactionProcessor.cs b/src/Sentry/Extensibility/ISentryTransactionProcessor.cs index e9b8202ff5..62607ac1c6 100644 --- a/src/Sentry/Extensibility/ISentryTransactionProcessor.cs +++ b/src/Sentry/Extensibility/ISentryTransactionProcessor.cs @@ -21,7 +21,7 @@ internal static class ISentryTransactionProcessorExtensions { internal static Transaction? DoProcessTransaction(this ISentryTransactionProcessor processor, Transaction transaction, Hint hint) { - return (processor is IContextualSentryTransactionProcessor contextualProcessor) + return (processor is ISentryTransactionProcessorWithHint contextualProcessor) ? contextualProcessor.Process(transaction, hint) : processor.Process(transaction); } diff --git a/src/Sentry/Extensibility/IContextualSentryTransactionProcessor.cs b/src/Sentry/Extensibility/ISentryTransactionProcessorWithHint.cs similarity index 89% rename from src/Sentry/Extensibility/IContextualSentryTransactionProcessor.cs rename to src/Sentry/Extensibility/ISentryTransactionProcessorWithHint.cs index 62cdae5531..91b867e116 100644 --- a/src/Sentry/Extensibility/IContextualSentryTransactionProcessor.cs +++ b/src/Sentry/Extensibility/ISentryTransactionProcessorWithHint.cs @@ -3,7 +3,7 @@ namespace Sentry.Extensibility; /// /// Process a during the prepare phase. /// -public interface IContextualSentryTransactionProcessor: ISentryTransactionProcessor +public interface ISentryTransactionProcessorWithHint: ISentryTransactionProcessor { /// /// Process the diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 4b842d8044..86a112711a 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -294,7 +294,10 @@ public void EndSession(SessionEndStatus status = SessionEndStatus.Exited) => return null; } - public SentryId CaptureEvent(SentryEvent evt, Action configureScope) + public SentryId CaptureEvent(SentryEvent evt, Action configureScope) => + CaptureEvent(evt, null, configureScope); + + public SentryId CaptureEvent(SentryEvent evt, Hint? hint, Action configureScope) { if (!IsEnabled) { @@ -306,7 +309,7 @@ public SentryId CaptureEvent(SentryEvent evt, Action configureScope) var clonedScope = ScopeManager.GetCurrent().Key.Clone(); configureScope(clonedScope); - return CaptureEvent(evt, clonedScope); + return CaptureEvent(evt, hint, clonedScope); } catch (Exception e) { diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt index e91a72ef71..4fcbfe1e17 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt @@ -1255,14 +1255,6 @@ namespace Sentry.Extensibility bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } - public interface IContextualSentryEventProcessor : Sentry.Extensibility.ISentryEventProcessor - { - Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint hint); - } - public interface IContextualSentryTransactionProcessor : Sentry.Extensibility.ISentryTransactionProcessor - { - Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint hint); - } public interface IDiagnosticLogger { bool IsEnabled(Sentry.SentryLevel level); @@ -1296,6 +1288,10 @@ namespace Sentry.Extensibility { Sentry.SentryEvent? Process(Sentry.SentryEvent @event); } + public interface ISentryEventProcessorWithHint : Sentry.Extensibility.ISentryEventProcessor + { + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint hint); + } public interface ISentryStackTraceFactory { Sentry.SentryStackTrace? Create(System.Exception? exception = null); @@ -1304,6 +1300,10 @@ namespace Sentry.Extensibility { Sentry.Transaction? Process(Sentry.Transaction transaction); } + public interface ISentryTransactionProcessorWithHint : Sentry.Extensibility.ISentryTransactionProcessor + { + Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint hint); + } public interface ITransport { System.Threading.Tasks.Task SendEnvelopeAsync(Sentry.Protocol.Envelopes.Envelope envelope, System.Threading.CancellationToken cancellationToken = default); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt index 05d0a39f1e..6ddf2b1d70 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -1256,14 +1256,6 @@ namespace Sentry.Extensibility bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } - public interface IContextualSentryEventProcessor : Sentry.Extensibility.ISentryEventProcessor - { - Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint hint); - } - public interface IContextualSentryTransactionProcessor : Sentry.Extensibility.ISentryTransactionProcessor - { - Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint hint); - } public interface IDiagnosticLogger { bool IsEnabled(Sentry.SentryLevel level); @@ -1297,6 +1289,10 @@ namespace Sentry.Extensibility { Sentry.SentryEvent? Process(Sentry.SentryEvent @event); } + public interface ISentryEventProcessorWithHint : Sentry.Extensibility.ISentryEventProcessor + { + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint hint); + } public interface ISentryStackTraceFactory { Sentry.SentryStackTrace? Create(System.Exception? exception = null); @@ -1305,6 +1301,10 @@ namespace Sentry.Extensibility { Sentry.Transaction? Process(Sentry.Transaction transaction); } + public interface ISentryTransactionProcessorWithHint : Sentry.Extensibility.ISentryTransactionProcessor + { + Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint hint); + } public interface ITransport { System.Threading.Tasks.Task SendEnvelopeAsync(Sentry.Protocol.Envelopes.Envelope envelope, System.Threading.CancellationToken cancellationToken = default); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt index 05d0a39f1e..6ddf2b1d70 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -1256,14 +1256,6 @@ namespace Sentry.Extensibility bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } - public interface IContextualSentryEventProcessor : Sentry.Extensibility.ISentryEventProcessor - { - Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint hint); - } - public interface IContextualSentryTransactionProcessor : Sentry.Extensibility.ISentryTransactionProcessor - { - Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint hint); - } public interface IDiagnosticLogger { bool IsEnabled(Sentry.SentryLevel level); @@ -1297,6 +1289,10 @@ namespace Sentry.Extensibility { Sentry.SentryEvent? Process(Sentry.SentryEvent @event); } + public interface ISentryEventProcessorWithHint : Sentry.Extensibility.ISentryEventProcessor + { + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint hint); + } public interface ISentryStackTraceFactory { Sentry.SentryStackTrace? Create(System.Exception? exception = null); @@ -1305,6 +1301,10 @@ namespace Sentry.Extensibility { Sentry.Transaction? Process(Sentry.Transaction transaction); } + public interface ISentryTransactionProcessorWithHint : Sentry.Extensibility.ISentryTransactionProcessor + { + Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint hint); + } public interface ITransport { System.Threading.Tasks.Task SendEnvelopeAsync(Sentry.Protocol.Envelopes.Envelope envelope, System.Threading.CancellationToken cancellationToken = default); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index c59a9e6f3b..0d64ed02a5 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -1254,14 +1254,6 @@ namespace Sentry.Extensibility bool EnqueueEnvelope(Sentry.Protocol.Envelopes.Envelope envelope); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } - public interface IContextualSentryEventProcessor : Sentry.Extensibility.ISentryEventProcessor - { - Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint hint); - } - public interface IContextualSentryTransactionProcessor : Sentry.Extensibility.ISentryTransactionProcessor - { - Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint hint); - } public interface IDiagnosticLogger { bool IsEnabled(Sentry.SentryLevel level); @@ -1295,6 +1287,10 @@ namespace Sentry.Extensibility { Sentry.SentryEvent? Process(Sentry.SentryEvent @event); } + public interface ISentryEventProcessorWithHint : Sentry.Extensibility.ISentryEventProcessor + { + Sentry.SentryEvent? Process(Sentry.SentryEvent @event, Sentry.Hint hint); + } public interface ISentryStackTraceFactory { Sentry.SentryStackTrace? Create(System.Exception? exception = null); @@ -1303,6 +1299,10 @@ namespace Sentry.Extensibility { Sentry.Transaction? Process(Sentry.Transaction transaction); } + public interface ISentryTransactionProcessorWithHint : Sentry.Extensibility.ISentryTransactionProcessor + { + Sentry.Transaction? Process(Sentry.Transaction transaction, Sentry.Hint hint); + } public interface ITransport { System.Threading.Tasks.Task SendEnvelopeAsync(Sentry.Protocol.Envelopes.Envelope envelope, System.Threading.CancellationToken cancellationToken = default); diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index e80b633819..4546c0bf37 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1175,7 +1175,7 @@ public void CaptureTransaction_Client_Gets_ScopeAttachments() public void CaptureTransaction_EventProcessor_Gets_Hint() { // Arrange - var processor = Substitute.For(); + var processor = Substitute.For(); processor.Process(Arg.Any(), Arg.Any()).Returns(new Transaction("name", "operation")); _fixture.Options.AddTransactionProcessor(processor); @@ -1192,7 +1192,7 @@ public void CaptureTransaction_EventProcessor_Gets_Hint() public void CaptureTransaction_EventProcessor_Gets_ScopeAttachments() { // Arrange - var processor = Substitute.For(); + var processor = Substitute.For(); Hint hint = null; processor.Process(Arg.Any(), Arg.Do(h => hint = h)).Returns(new Transaction("name", "operation")); _fixture.Options.AddTransactionProcessor(processor); diff --git a/test/Sentry.Tests/SentryClientTests.cs b/test/Sentry.Tests/SentryClientTests.cs index c5ee0216eb..78638a79ad 100644 --- a/test/Sentry.Tests/SentryClientTests.cs +++ b/test/Sentry.Tests/SentryClientTests.cs @@ -347,7 +347,7 @@ public void CaptureEvent_BeforeSend_Gets_ScopeAttachments() public void CaptureEvent_EventProcessor_Gets_Hint() { // Arrange - var processor = Substitute.For(); + var processor = Substitute.For(); processor.Process(Arg.Any(), Arg.Any()).Returns(new SentryEvent()); _fixture.SentryOptions.AddEventProcessor(processor); @@ -363,7 +363,7 @@ public void CaptureEvent_EventProcessor_Gets_Hint() public void CaptureEvent_EventProcessor_Gets_ScopeAttachments() { // Arrange - var processor = Substitute.For(); + var processor = Substitute.For(); Hint hint = null; processor.Process(Arg.Any(), Arg.Do(h => hint = h)).Returns(new SentryEvent()); _fixture.SentryOptions.AddEventProcessor(processor); From d02f33aaf0e6a5304e90e885aa40d32019856ec5 Mon Sep 17 00:00:00 2001 From: Matt Johnson-Pint Date: Mon, 15 May 2023 12:27:59 -0700 Subject: [PATCH 21/25] Add overloads without hints --- src/Sentry/SentryOptions.cs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index 9fe890234a..559e51a8d5 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -337,6 +337,19 @@ public void SetBeforeSend(Func beforeSend) _beforeSend = beforeSend; } + /// + /// Configures a callback function to be invoked before sending an event to Sentry + /// + /// + /// The event returned by this callback will be sent to Sentry. This allows the + /// application a chance to inspect and/or modify the event before it's sent. If the + /// event should not be sent at all, return null from the callback. + /// + public void SetBeforeSend(Func beforeSend) + { + _beforeSend = (@event, _) => beforeSend(@event); + } + private Func? _beforeSendTransaction; internal Func? BeforeSendTransactionInternal => _beforeSendTransaction; @@ -364,6 +377,15 @@ public void SetBeforeSendTransaction(Func befor _beforeSendTransaction = beforeSendTransaction; } + /// + /// Configures a callback to invoke before sending a transaction to Sentry + /// + /// The callback + public void SetBeforeSendTransaction(Func beforeSendTransaction) + { + _beforeSendTransaction = (transaction, _) => beforeSendTransaction(transaction); + } + private Func? _beforeBreadcrumb; internal Func? BeforeBreadcrumbInternal => _beforeBreadcrumb; @@ -390,6 +412,18 @@ public void SetBeforeBreadcrumb(Func beforeBreadc _beforeBreadcrumb = beforeBreadcrumb; } + /// + /// Sets a callback function to be invoked when a breadcrumb is about to be stored. + /// + /// + /// Gives a chance to inspect and modify the breadcrumb. If null is returned, the + /// breadcrumb will be discarded. Otherwise the result of the callback will be stored. + /// + public void SetBeforeBreadcrumb(Func beforeBreadcrumb) + { + _beforeBreadcrumb = (breadcrumb, _) => beforeBreadcrumb(breadcrumb); + } + private int _maxQueueItems = 30; /// From e382114a65d4512ad03bbc388b10c0092e1e3571 Mon Sep 17 00:00:00 2001 From: Matt Johnson-Pint Date: Mon, 15 May 2023 13:00:59 -0700 Subject: [PATCH 22/25] Cleanup Hint. Just expose Attachments, not AddAttachments. --- src/Sentry/Hint.cs | 60 ++++++++++++++-------------------- src/Sentry/Internal/Hub.cs | 2 +- src/Sentry/Scope.cs | 2 +- src/Sentry/SentryClient.cs | 2 +- test/Sentry.Tests/HintTests.cs | 18 ++-------- 5 files changed, 30 insertions(+), 54 deletions(-) diff --git a/src/Sentry/Hint.cs b/src/Sentry/Hint.cs index 0278d8e8cb..44481fbd66 100644 --- a/src/Sentry/Hint.cs +++ b/src/Sentry/Hint.cs @@ -1,8 +1,8 @@ namespace Sentry; /// -/// A hint that can be provided when capturing a or adding a . -/// Hints can be used to filter or modify events or breadcrumbs before they are sent to Sentry. +/// A hint that can be provided when capturing a or when adding a . +/// Hints can be used to filter or modify events, transactions, or breadcrumbs before they are sent to Sentry. /// public class Hint { @@ -17,69 +17,57 @@ public Hint() } /// - /// Creates a new hint with a single key/value pair. + /// Creates a new hint containing a single item. /// - /// - /// + /// The key of the hint item. + /// The value of the hint item. public Hint(string key, object? value) : this() { _items[key] = value; } - internal void AddAttachmentsInternal(IEnumerable attachments) - { - if (attachments is not null) - { - _attachments.AddRange(attachments); - } - } - - /// - /// Adds one or more attachments to the Hint. - /// - /// - public void AddAttachments(params Attachment[] attachments) => AddAttachmentsInternal(attachments); - - /// - /// Adds multiple attachments to the Hint. - /// - /// - public void AddAttachments(IEnumerable attachments) => AddAttachmentsInternal(attachments); - /// - /// The Java SDK has some logic so that certain Hint types do not copy attachments from the Scope. This - /// allows us to do the same in the .NET SDK in the future. + /// The Java SDK has some logic so that certain Hint types do not copy attachments from the Scope. + /// This provides a location that allows us to do the same in the .NET SDK in the future. /// /// The that the attachments should be copied from - internal void AddScopeAttachments(Scope scope) => AddAttachmentsInternal(scope.Attachments); + internal void AddAttachmentsFromScope(Scope scope) => _attachments.AddRange(scope.Attachments); /// /// Attachments added to the Hint. /// + /// + /// This collection represents all of the attachments that will be sent to Sentry with the corresponding event. + /// You can add or remove attachments from this collection as needed. + /// public ICollection Attachments => _attachments; /// - /// Data provided with the Hint. + /// A dictionary of arbitrary items provided with the Hint. /// + /// + /// These are not sent to Sentry, but rather they are available during processing, such as when using + /// BeforeSend and others. + /// public IDictionary Items => _items; /// /// Creates a new Hint with one or more attachments. /// - /// - /// - public static Hint WithAttachments(params Attachment[] attachment) => Hint.WithAttachments(attachment.ToList()); + /// The attachment(s) to add. + /// A Hint having the attachment(s). + public static Hint WithAttachments(params Attachment[] attachments) => WithAttachments(attachments.AsEnumerable()); /// /// Creates a new Hint with attachments. /// - /// - /// - public static Hint WithAttachments(ICollection attachments) + /// The attachments to add. + /// A Hint having the attachments. + public static Hint WithAttachments(IEnumerable attachments) { var hint = new Hint(); - hint.AddAttachments(attachments); + hint._attachments.AddRange(attachments); return hint; } } diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 2832179027..30f4b80409 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -419,7 +419,7 @@ public void CaptureTransaction(Transaction transaction, Hint? hint) // Add attachments to the hint for processors and callbacks hint ??= new Hint(); - hint.AddScopeAttachments(scope); + hint.AddAttachmentsFromScope(scope); var processedTransaction = transaction; if (transaction.IsSampled != false) diff --git a/src/Sentry/Scope.cs b/src/Sentry/Scope.cs index 283350fac6..e950d5eff8 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -255,7 +255,7 @@ public void AddBreadcrumb(Breadcrumb breadcrumb, Hint hint) { if (Options.BeforeBreadcrumbInternal is { } beforeBreadcrumb) { - hint.AddScopeAttachments(this); + hint.AddAttachmentsFromScope(this); if (beforeBreadcrumb(breadcrumb, hint) is { } processedBreadcrumb) { diff --git a/src/Sentry/SentryClient.cs b/src/Sentry/SentryClient.cs index 391bec42fb..529df8d087 100644 --- a/src/Sentry/SentryClient.cs +++ b/src/Sentry/SentryClient.cs @@ -227,7 +227,7 @@ private SentryId DoSendEvent(SentryEvent @event, Hint? hint, Scope? scope) scope ??= new Scope(_options); hint ??= new Hint(); - hint.AddScopeAttachments(scope); + hint.AddAttachmentsFromScope(scope); _options.LogInfo("Capturing event."); diff --git a/test/Sentry.Tests/HintTests.cs b/test/Sentry.Tests/HintTests.cs index 717c170e01..39823fdac5 100644 --- a/test/Sentry.Tests/HintTests.cs +++ b/test/Sentry.Tests/HintTests.cs @@ -2,19 +2,6 @@ namespace Sentry.Tests; public class HintTests { - [Fact] - public void AddAttachments_WithNullAttachments_DoesNothing() - { - // Arrange - var hint = new Hint(); - - // Act - hint.AddAttachments(null); - - // Assert - Assert.Empty(hint.Attachments); - } - [Fact] public void AddAttachments_WithAttachments_AddsToHint() { @@ -24,7 +11,8 @@ public void AddAttachments_WithAttachments_AddsToHint() var attachment2 = AttachmentHelper.FakeAttachment("attachment2"); // Act - hint.AddAttachments(attachment1, attachment2); + hint.Attachments.Add(attachment1); + hint.Attachments.Add(attachment2); // Assert Assert.Equal(2, hint.Attachments.Count); @@ -49,7 +37,7 @@ public void ClearAttachments_WithAttachments_ClearsHintAttachments() // Arrange var hint = new Hint(); var attachment1 = AttachmentHelper.FakeAttachment("attachment1"); - hint.AddAttachments(attachment1); + hint.Attachments.Add(attachment1); // Act hint.Attachments.Clear(); From c3fdad474df74306099f74b7ede9f5face918c08 Mon Sep 17 00:00:00 2001 From: Matt Johnson-Pint Date: Mon, 15 May 2023 13:03:13 -0700 Subject: [PATCH 23/25] Update API snapshots --- .../ApiApprovalTests.Run.Core3_1.verified.txt | 9 +++++---- .../ApiApprovalTests.Run.DotNet6_0.verified.txt | 9 +++++---- .../ApiApprovalTests.Run.DotNet7_0.verified.txt | 9 +++++---- .../ApiApprovalTests.Run.Net4_8.verified.txt | 9 +++++---- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt index f01b6d86ad..22230b0b0d 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt @@ -144,10 +144,8 @@ namespace Sentry public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } public System.Collections.Generic.IDictionary Items { get; } - public void AddAttachments(params Sentry.Attachment[] attachments) { } - public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } - public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } - public static Sentry.Hint WithAttachments(System.Collections.Generic.ICollection attachments) { } + public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachments) { } + public static Sentry.Hint WithAttachments(System.Collections.Generic.IEnumerable attachments) { } } public static class HintTypes { @@ -648,8 +646,11 @@ namespace Sentry public Sentry.Extensibility.ITransport? Transport { get; set; } public bool UseAsyncFileIO { get; set; } public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeSend(System.Func beforeSend) { } public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } } public static class SentryOptionsExtensions diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt index b70b10023b..41046b99a2 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -144,10 +144,8 @@ namespace Sentry public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } public System.Collections.Generic.IDictionary Items { get; } - public void AddAttachments(params Sentry.Attachment[] attachments) { } - public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } - public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } - public static Sentry.Hint WithAttachments(System.Collections.Generic.ICollection attachments) { } + public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachments) { } + public static Sentry.Hint WithAttachments(System.Collections.Generic.IEnumerable attachments) { } } public static class HintTypes { @@ -649,8 +647,11 @@ namespace Sentry public Sentry.Extensibility.ITransport? Transport { get; set; } public bool UseAsyncFileIO { get; set; } public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeSend(System.Func beforeSend) { } public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } } public static class SentryOptionsExtensions diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt index b70b10023b..41046b99a2 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -144,10 +144,8 @@ namespace Sentry public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } public System.Collections.Generic.IDictionary Items { get; } - public void AddAttachments(params Sentry.Attachment[] attachments) { } - public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } - public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } - public static Sentry.Hint WithAttachments(System.Collections.Generic.ICollection attachments) { } + public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachments) { } + public static Sentry.Hint WithAttachments(System.Collections.Generic.IEnumerable attachments) { } } public static class HintTypes { @@ -649,8 +647,11 @@ namespace Sentry public Sentry.Extensibility.ITransport? Transport { get; set; } public bool UseAsyncFileIO { get; set; } public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeSend(System.Func beforeSend) { } public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } } public static class SentryOptionsExtensions diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index a09aa43d9d..afd1f161ad 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -143,10 +143,8 @@ namespace Sentry public Hint(string key, object? value) { } public System.Collections.Generic.ICollection Attachments { get; } public System.Collections.Generic.IDictionary Items { get; } - public void AddAttachments(params Sentry.Attachment[] attachments) { } - public void AddAttachments(System.Collections.Generic.IEnumerable attachments) { } - public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachment) { } - public static Sentry.Hint WithAttachments(System.Collections.Generic.ICollection attachments) { } + public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachments) { } + public static Sentry.Hint WithAttachments(System.Collections.Generic.IEnumerable attachments) { } } public static class HintTypes { @@ -647,8 +645,11 @@ namespace Sentry public Sentry.Extensibility.ITransport? Transport { get; set; } public bool UseAsyncFileIO { get; set; } public void AddJsonConverter(System.Text.Json.Serialization.JsonConverter converter) { } + public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } public void SetBeforeBreadcrumb(System.Func beforeBreadcrumb) { } + public void SetBeforeSend(System.Func beforeSend) { } public void SetBeforeSend(System.Func beforeSend) { } + public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } public void SetBeforeSendTransaction(System.Func beforeSendTransaction) { } } public static class SentryOptionsExtensions From 13c70d61b556454c579cc1e997f66ceddc8e4ef8 Mon Sep 17 00:00:00 2001 From: Matt Johnson-Pint Date: Mon, 15 May 2023 16:24:54 -0700 Subject: [PATCH 24/25] Ensure hint modifications to attachments are sent --- src/Sentry/Protocol/Envelopes/Envelope.cs | 7 ++ src/Sentry/SentryClient.cs | 6 +- test/Sentry.Tests/SentryClientTests.cs | 88 ++++++++++++++++++++++- 3 files changed, 96 insertions(+), 5 deletions(-) diff --git a/src/Sentry/Protocol/Envelopes/Envelope.cs b/src/Sentry/Protocol/Envelopes/Envelope.cs index e4c30ee318..76b3f02d0c 100644 --- a/src/Sentry/Protocol/Envelopes/Envelope.cs +++ b/src/Sentry/Protocol/Envelopes/Envelope.cs @@ -240,6 +240,13 @@ public static Envelope FromEvent( { foreach (var attachment in attachments) { + // Safety check, in case the user forcefully added a null attachment. + if (attachment.IsNull()) + { + logger?.LogWarning("Encountered a null attachment. Skipping."); + continue; + } + try { // We pull the stream out here so we can length check diff --git a/src/Sentry/SentryClient.cs b/src/Sentry/SentryClient.cs index 529df8d087..c31e7dfabf 100644 --- a/src/Sentry/SentryClient.cs +++ b/src/Sentry/SentryClient.cs @@ -276,9 +276,9 @@ private SentryId DoSendEvent(SentryEvent @event, Hint? hint, Scope? scope) return SentryId.Empty; } - return CaptureEnvelope(Envelope.FromEvent(processedEvent, _options.DiagnosticLogger, scope.Attachments, scope.SessionUpdate)) - ? processedEvent.EventId - : SentryId.Empty; + var attachments = hint.Attachments.ToList(); + var envelope = Envelope.FromEvent(processedEvent, _options.DiagnosticLogger, attachments, scope.SessionUpdate); + return CaptureEnvelope(envelope) ? processedEvent.EventId : SentryId.Empty; } private IReadOnlyCollection? ApplyExceptionFilters(Exception? exception) diff --git a/test/Sentry.Tests/SentryClientTests.cs b/test/Sentry.Tests/SentryClientTests.cs index 78638a79ad..7268163a1d 100644 --- a/test/Sentry.Tests/SentryClientTests.cs +++ b/test/Sentry.Tests/SentryClientTests.cs @@ -1,4 +1,3 @@ -using FluentAssertions.Execution; using Sentry.Internal.Http; using BackgroundWorker = Sentry.Internal.BackgroundWorker; @@ -368,7 +367,7 @@ public void CaptureEvent_EventProcessor_Gets_ScopeAttachments() processor.Process(Arg.Any(), Arg.Do(h => hint = h)).Returns(new SentryEvent()); _fixture.SentryOptions.AddEventProcessor(processor); - Scope scope = new Scope(_fixture.SentryOptions); + var scope = new Scope(_fixture.SentryOptions); scope.AddAttachment(AttachmentHelper.FakeAttachment("foo.txt")); // Act @@ -380,6 +379,91 @@ public void CaptureEvent_EventProcessor_Gets_ScopeAttachments() hint.Attachments.Should().Contain(scope.Attachments); } + [Fact] + public void CaptureEvent_Gets_ScopeAttachments() + { + // Arrange + var scope = new Scope(_fixture.SentryOptions); + scope.AddAttachment(AttachmentHelper.FakeAttachment("foo.txt")); + scope.AddAttachment(AttachmentHelper.FakeAttachment("bar.txt")); + + var sut = _fixture.GetSut(); + + // Act + sut.CaptureEvent(new SentryEvent(), scope); + + // Assert + sut.Worker.Received(1).EnqueueEnvelope(Arg.Is(envelope => + envelope.Items.Count(item => item.TryGetType() == "attachment") == 2)); + } + + [Fact] + public void CaptureEvent_Gets_HintAttachments() + { + // Arrange + var scope = new Scope(_fixture.SentryOptions); + _fixture.SentryOptions.SetBeforeSend((e, h) => { + h.Attachments.Add(AttachmentHelper.FakeAttachment("foo.txt")); + h.Attachments.Add(AttachmentHelper.FakeAttachment("bar.txt")); + return e; + }); + + var sut = _fixture.GetSut(); + + // Act + sut.CaptureEvent(new SentryEvent(), scope); + + // Assert + sut.Worker.Received(1).EnqueueEnvelope(Arg.Is(envelope => + envelope.Items.Count(item => item.TryGetType() == "attachment") == 2)); + } + + [Fact] + public void CaptureEvent_Gets_ScopeAndHintAttachments() + { + // Arrange + var scope = new Scope(_fixture.SentryOptions); + scope.AddAttachment(AttachmentHelper.FakeAttachment("foo.txt")); + _fixture.SentryOptions.SetBeforeSend((e, h) => { + h.Attachments.Add(AttachmentHelper.FakeAttachment("bar.txt")); + return e; + }); + + var sut = _fixture.GetSut(); + + // Act + sut.CaptureEvent(new SentryEvent(), scope); + + // Assert + sut.Worker.Received(1).EnqueueEnvelope(Arg.Is(envelope => + envelope.Items.Count(item => item.TryGetType() == "attachment") == 2)); + } + + [Fact] + public void CaptureEvent_CanRemove_ScopetAttachment() + { + // Arrange + var scope = new Scope(_fixture.SentryOptions); + scope.AddAttachment(AttachmentHelper.FakeAttachment("foo.txt")); + scope.AddAttachment(AttachmentHelper.FakeAttachment("bar.txt")); + _fixture.SentryOptions.SetBeforeSend((e, h) => + { + var attachment = h.Attachments.FirstOrDefault(a => a.FileName == "bar.txt"); + h.Attachments.Remove(attachment); + + return e; + }); + + var sut = _fixture.GetSut(); + + // Act + sut.CaptureEvent(new SentryEvent(), scope); + + // Assert + sut.Worker.Received(1).EnqueueEnvelope(Arg.Is(envelope => + envelope.Items.Count(item => item.TryGetType() == "attachment") == 1)); + } + [Fact] public void CaptureEvent_BeforeSend_ModifyEvent() { From af956233b0ba9de521b0df5e9833682a8ab31b3d Mon Sep 17 00:00:00 2001 From: Matt Johnson-Pint Date: Mon, 15 May 2023 16:38:02 -0700 Subject: [PATCH 25/25] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f606696452..44772d4b0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ ### Features - Add `Hint` support ([#2351](https://github.com/getsentry/sentry-dotnet/pull/2351)) + - Currently, this allows you to manipulate attachments in the various "before" event delegates. + - Hints can also be used in event and transaction processors by implementing `ISentryEventProcessorWithHint` or `ISentryTransactionProcessorWithHint`, instead of `ISentryEventProcessor` or `ISentryTransactionProcessor`. + - Note: Obsoletes the `BeforeSend`, `BeforeSendTransaction`, and `BeforeBreadcrumb` properties on the `SentryOptions` class. They have been replaced with `SetBeforeSend`, `SetBeforeSendTransaction`, and `SetBeforeBreadcrumb` respectively. Each one provides overloads both with and without a `Hint` object. + - Allow setting the active span on the scope ([#2364](https://github.com/getsentry/sentry-dotnet/pull/2364)) - Note: Obsoletes the `Scope.GetSpan` method in favor of a `Scope.Span` property (which now has a setter as well).