diff --git a/CHANGELOG.md b/CHANGELOG.md index 658d7d8386..44772d4b0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ### 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). diff --git a/samples/Sentry.Samples.Console.Customized/Program.cs b/samples/Sentry.Samples.Console.Customized/Program.cs index 37b37b5201..ae347ef6f4 100644 --- a/samples/Sentry.Samples.Console.Customized/Program.cs +++ b/samples/Sentry.Samples.Console.Customized/Program.cs @@ -42,19 +42,20 @@ 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 => + o.SetBeforeBreadcrumb((crumb, hint) => { // Don't add breadcrumbs with message containing: if (crumb.Message?.Contains("bad breadcrumb") == true) @@ -62,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.Items.TryGetValue(replaceBreadcrumb, out var replacementMessage)) + { + return new Breadcrumb((string)replacementMessage, null, null, null, BreadcrumbLevel.Critical); + } + return crumb; - }; + }); // Ignore exception by its type: o.AddExceptionFilterForType(); @@ -102,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 870ee78cd4..bf3fdf1dc1 100644 --- a/samples/Sentry.Samples.Console.Profiling/Program.cs +++ b/samples/Sentry.Samples.Console.Profiling/Program.cs @@ -43,19 +43,20 @@ 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 => + o.SetBeforeBreadcrumb((crumb, _) => { // Don't add breadcrumbs with message containing: if (crumb.Message?.Contains("bad breadcrumb") == true) @@ -64,7 +65,7 @@ await SentrySdk.ConfigureScopeAsync(async scope => } return crumb; - }; + }); // Ignore exception by its type: o.AddExceptionFilterForType(); diff --git a/src/Sentry/Extensibility/DisabledHub.cs b/src/Sentry/Extensibility/DisabledHub.cs index 0b76a639fc..109e5ef248 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. /// @@ -126,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 d4917bb08b..607b39aed6 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 . /// @@ -207,6 +215,14 @@ public SentryId CaptureEvent(SentryEvent evt, Action configureScope) 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/Extensibility/ISentryEventProcessor.cs b/src/Sentry/Extensibility/ISentryEventProcessor.cs index 447515d277..229aa3339a 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 ISentryEventProcessorWithHint contextualProcessor) + ? contextualProcessor.Process(@event, hint) + : processor.Process(@event); + } +} diff --git a/src/Sentry/Extensibility/ISentryEventProcessorWithHint.cs b/src/Sentry/Extensibility/ISentryEventProcessorWithHint.cs new file mode 100644 index 0000000000..61b4fcf96d --- /dev/null +++ b/src/Sentry/Extensibility/ISentryEventProcessorWithHint.cs @@ -0,0 +1,21 @@ +namespace Sentry.Extensibility; + +/// +/// Process a SentryEvent during the prepare phase. +/// +public interface ISentryEventProcessorWithHint: 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/ISentryTransactionProcessor.cs b/src/Sentry/Extensibility/ISentryTransactionProcessor.cs index 50a8757dbe..62607ac1c6 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 ISentryTransactionProcessorWithHint contextualProcessor) + ? contextualProcessor.Process(transaction, hint) + : processor.Process(transaction); + } +} diff --git a/src/Sentry/Extensibility/ISentryTransactionProcessorWithHint.cs b/src/Sentry/Extensibility/ISentryTransactionProcessorWithHint.cs new file mode 100644 index 0000000000..91b867e116 --- /dev/null +++ b/src/Sentry/Extensibility/ISentryTransactionProcessorWithHint.cs @@ -0,0 +1,19 @@ +namespace Sentry.Extensibility; + +/// +/// Process a during the prepare phase. +/// +public interface ISentryTransactionProcessorWithHint: 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/Hint.cs b/src/Sentry/Hint.cs new file mode 100644 index 0000000000..44481fbd66 --- /dev/null +++ b/src/Sentry/Hint.cs @@ -0,0 +1,73 @@ +namespace 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 +{ + private readonly List _attachments = new(); + private readonly Dictionary _items = new(); + + /// + /// Creates a new instance of . + /// + public Hint() + { + } + + /// + /// 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; + } + + /// + /// 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 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; + + /// + /// 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. + /// + /// 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. + /// + /// The attachments to add. + /// A Hint having the attachments. + public static Hint WithAttachments(IEnumerable attachments) + { + var hint = new Hint(); + hint._attachments.AddRange(attachments); + return hint; + } +} 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/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/ISentryClient.cs b/src/Sentry/ISentryClient.cs index d229d99e31..01971d9790 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. /// @@ -35,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 978ee49d51..30f4b80409 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -290,7 +290,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) { @@ -302,7 +305,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) { @@ -312,9 +315,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); + + public SentryId CaptureEvent(SentryEvent evt, Hint? hint, Scope? scope = null) => + IsEnabled ? ((IHubEx)this).CaptureEventInternal(evt, hint, scope) : SentryId.Empty; - SentryId IHubEx.CaptureEventInternal(SentryEvent evt, Scope? scope) + SentryId IHubEx.CaptureEventInternal(SentryEvent evt, Hint? hint, Scope? scope) { try { @@ -349,7 +355,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; @@ -387,7 +393,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, @@ -401,20 +409,24 @@ public void CaptureTransaction(Transaction transaction) 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.AddAttachmentsFromScope(scope); + var processedTransaction = transaction; if (transaction.IsSampled != false) { 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); @@ -424,7 +436,8 @@ public void CaptureTransaction(Transaction transaction) } } - currentScope.Value.CaptureTransaction(processedTransaction); + var client = currentScopeAndClient.Value; + client.CaptureTransaction(processedTransaction, hint); } catch (Exception e) { 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/Callbacks/BeforeBreadcrumbCallback.cs b/src/Sentry/Platforms/Android/Callbacks/BeforeBreadcrumbCallback.cs index dd0f5ddd64..17472a06ad 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,13 +17,14 @@ 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) { // 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/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 eaebcb271f..bad53c0039 100644 --- a/src/Sentry/Platforms/Android/SentrySdk.cs +++ b/src/Sentry/Platforms/Android/SentrySdk.cs @@ -101,7 +101,7 @@ private static void InitSentryAndroidSdk(SentryOptions options) }); } - if (options.BeforeBreadcrumb is { } beforeBreadcrumb) + if (options.BeforeBreadcrumbInternal is { } beforeBreadcrumb) { o.BeforeBreadcrumb = new BeforeBreadcrumbCallback(beforeBreadcrumb); } @@ -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/Platforms/iOS/SentrySdk.cs b/src/Sentry/Platforms/iOS/SentrySdk.cs index aafdbbe3bd..7a79d04d5c 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 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/Scope.cs b/src/Sentry/Scope.cs index e07e3d0eac..e950d5eff8 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -244,11 +244,20 @@ 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) + hint.AddAttachmentsFromScope(this); + + if (beforeBreadcrumb(breadcrumb, hint) is { } processedBreadcrumb) { breadcrumb = processedBreadcrumb; } @@ -364,7 +373,6 @@ public void ClearBreadcrumbs() #endif } - /// /// Applies the data from this scope to another event-like object. /// @@ -468,8 +476,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 320802a400..c31e7dfabf 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) { @@ -96,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)) { @@ -132,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); @@ -143,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; } @@ -154,7 +161,7 @@ public void CaptureTransaction(Transaction transaction) try { - return _options.BeforeSendTransaction?.Invoke(transaction); + return _options.BeforeSendTransactionInternal?.Invoke(transaction, hint); } catch (Exception e) { @@ -197,7 +204,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) { @@ -219,6 +226,8 @@ private SentryId DoSendEvent(SentryEvent @event, Scope? scope) } scope ??= new Scope(_options); + hint ??= new Hint(); + hint.AddAttachmentsFromScope(scope); _options.LogInfo("Capturing event."); @@ -249,7 +258,8 @@ private SentryId DoSendEvent(SentryEvent @event, 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); @@ -258,7 +268,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); @@ -266,9 +276,9 @@ private SentryId DoSendEvent(SentryEvent @event, 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) @@ -319,9 +329,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 +339,7 @@ private bool CaptureEnvelope(Envelope envelope) _options.LogDebug("Calling the BeforeSend callback"); try { - @event = _options.BeforeSend?.Invoke(@event!); + @event = _options.BeforeSendInternal?.Invoke(@event!, hint); } catch (Exception e) { diff --git a/src/Sentry/SentryFailedRequestHandler.cs b/src/Sentry/SentryFailedRequestHandler.cs index 283bab5387..10ec349693 100644 --- a/src/Sentry/SentryFailedRequestHandler.cs +++ b/src/Sentry/SentryFailedRequestHandler.cs @@ -75,6 +75,7 @@ public void HandleResponse(HttpResponseMessage response) exception.SetSentryMechanism(MechanismType); var @event = new SentryEvent(exception); + var hint = new Hint(HintTypes.HttpResponseMessage, response); var sentryRequest = new Request { @@ -103,7 +104,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 1da4cbd1a7..559e51a8d5 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -309,15 +309,50 @@ public float? SampleRate /// public string? Dsn { get; set; } + private Func? _beforeSend; + + internal Func? BeforeSendInternal => _beforeSend; + + /// + /// Configures a callback to invoke before sending an event to Sentry + /// + /// + [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); + } + /// - /// A callback to invoke before sending an event to Sentry + /// Configures a callback function to be invoked 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. + /// 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 Func? BeforeSend { get; set; } + 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; /// /// A callback to invoke before sending a transaction to Sentry @@ -327,15 +362,67 @@ public float? SampleRate /// 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); + } /// - /// A callback invoked when a breadcrumb is about to be stored. + /// Configures a callback to invoke before sending a transaction to Sentry + /// + /// The callback + public void SetBeforeSendTransaction(Func beforeSendTransaction) + { + _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; + + /// + /// Sets a callback function to be invoked when a breadcrumb is about to be stored. + /// + /// + [Obsolete("This property will be removed in a future version. Use SetBeforeBreadcrumb instead.")] + public Func? BeforeBreadcrumb { + get => null; + set => _beforeBreadcrumb = value is null ? null : (e, _) => value(e); + } + + /// + /// 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. + /// 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 Func? BeforeBreadcrumb { get; set; } + public void SetBeforeBreadcrumb(Func beforeBreadcrumb) + { + _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; 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/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index d591590284..2215f33ccf 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. /// @@ -409,10 +419,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. @@ -505,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/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.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)); } 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.EntityFramework.Tests/ErrorProcessorTests.cs b/test/Sentry.EntityFramework.Tests/ErrorProcessorTests.cs index 4f93558703..08533cb40b 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..cb301eacf5 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..8f3c49f4d3 100644 --- a/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs +++ b/test/Sentry.Serilog.Tests/SerilogAspNetSentrySdkTestFixture.cs @@ -11,11 +11,7 @@ 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/ApiApprovalTests.Run.Core3_1.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt index 40657ffddc..22230b0b0d 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Core3_1.verified.txt @@ -138,6 +138,19 @@ namespace Sentry { public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } } + public class Hint + { + public Hint() { } + public Hint(string key, object? value) { } + public System.Collections.Generic.ICollection Attachments { get; } + public System.Collections.Generic.IDictionary Items { get; } + public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachments) { } + public static Sentry.Hint WithAttachments(System.Collections.Generic.IEnumerable attachments) { } + } + public static class HintTypes + { + public const string HttpResponseMessage = "http-response-message"; + } public readonly struct HttpStatusCodeRange : System.IEquatable { public HttpStatusCodeRange(int statusCode) { } @@ -157,6 +170,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,8 +249,10 @@ 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 CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint); void CaptureUserFeedback(Sentry.UserFeedback userFeedback); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } @@ -411,6 +427,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) { } @@ -457,8 +474,10 @@ 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 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.")] @@ -567,8 +586,13 @@ 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; } + [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; } @@ -622,6 +646,12 @@ 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 { @@ -672,6 +702,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) { } @@ -679,12 +710,14 @@ 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) { } 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" + @@ -1159,8 +1192,10 @@ 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 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) { } @@ -1195,9 +1230,11 @@ 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) { } + 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) { } @@ -1255,6 +1292,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); @@ -1263,6 +1304,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 a926e47cc1..41046b99a2 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -138,6 +138,19 @@ namespace Sentry { public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } } + public class Hint + { + public Hint() { } + public Hint(string key, object? value) { } + public System.Collections.Generic.ICollection Attachments { get; } + public System.Collections.Generic.IDictionary Items { get; } + public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachments) { } + public static Sentry.Hint WithAttachments(System.Collections.Generic.IEnumerable attachments) { } + } + public static class HintTypes + { + public const string HttpResponseMessage = "http-response-message"; + } public readonly struct HttpStatusCodeRange : System.IEquatable { public HttpStatusCodeRange(int statusCode) { } @@ -157,6 +170,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,8 +249,10 @@ 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 CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint); void CaptureUserFeedback(Sentry.UserFeedback userFeedback); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } @@ -411,6 +427,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) { } @@ -457,8 +474,10 @@ 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 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.")] @@ -568,8 +587,13 @@ 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; } + [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; } @@ -623,6 +647,12 @@ 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 { @@ -673,6 +703,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) { } @@ -680,12 +711,14 @@ 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) { } 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" + @@ -1160,8 +1193,10 @@ 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 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) { } @@ -1196,9 +1231,11 @@ 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) { } + 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) { } @@ -1256,6 +1293,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); @@ -1264,6 +1305,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 a926e47cc1..41046b99a2 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -138,6 +138,19 @@ namespace Sentry { public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } } + public class Hint + { + public Hint() { } + public Hint(string key, object? value) { } + public System.Collections.Generic.ICollection Attachments { get; } + public System.Collections.Generic.IDictionary Items { get; } + public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachments) { } + public static Sentry.Hint WithAttachments(System.Collections.Generic.IEnumerable attachments) { } + } + public static class HintTypes + { + public const string HttpResponseMessage = "http-response-message"; + } public readonly struct HttpStatusCodeRange : System.IEquatable { public HttpStatusCodeRange(int statusCode) { } @@ -157,6 +170,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,8 +249,10 @@ 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 CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint); void CaptureUserFeedback(Sentry.UserFeedback userFeedback); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } @@ -411,6 +427,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) { } @@ -457,8 +474,10 @@ 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 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.")] @@ -568,8 +587,13 @@ 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; } + [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; } @@ -623,6 +647,12 @@ 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 { @@ -673,6 +703,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) { } @@ -680,12 +711,14 @@ 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) { } 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" + @@ -1160,8 +1193,10 @@ 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 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) { } @@ -1196,9 +1231,11 @@ 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) { } + 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) { } @@ -1256,6 +1293,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); @@ -1264,6 +1305,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 f2da37051b..afd1f161ad 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -137,6 +137,19 @@ namespace Sentry { public static void SetTags(this Sentry.IHasTags hasTags, System.Collections.Generic.IEnumerable> tags) { } } + public class Hint + { + public Hint() { } + public Hint(string key, object? value) { } + public System.Collections.Generic.ICollection Attachments { get; } + public System.Collections.Generic.IDictionary Items { get; } + public static Sentry.Hint WithAttachments(params Sentry.Attachment[] attachments) { } + public static Sentry.Hint WithAttachments(System.Collections.Generic.IEnumerable attachments) { } + } + public static class HintTypes + { + public const string HttpResponseMessage = "http-response-message"; + } public readonly struct HttpStatusCodeRange : System.IEquatable { public HttpStatusCodeRange(int statusCode) { } @@ -156,6 +169,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,8 +248,10 @@ 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 CaptureTransaction(Sentry.Transaction transaction, Sentry.Hint? hint); void CaptureUserFeedback(Sentry.UserFeedback userFeedback); System.Threading.Tasks.Task FlushAsync(System.TimeSpan timeout); } @@ -410,6 +426,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) { } @@ -456,8 +473,10 @@ 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 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.")] @@ -566,8 +585,13 @@ 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; } + [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; } @@ -621,6 +645,12 @@ 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 { @@ -671,6 +701,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) { } @@ -678,12 +709,14 @@ 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) { } 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" + @@ -1158,8 +1191,10 @@ 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 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) { } @@ -1194,9 +1229,11 @@ 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) { } + 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) { } @@ -1254,6 +1291,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); @@ -1262,6 +1303,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/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.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..39823fdac5 --- /dev/null +++ b/test/Sentry.Tests/HintTests.cs @@ -0,0 +1,148 @@ +namespace Sentry.Tests; + +public class HintTests +{ + [Fact] + public void AddAttachments_WithAttachments_AddsToHint() + { + // Arrange + var hint = new Hint(); + var attachment1 = AttachmentHelper.FakeAttachment("attachment1"); + var attachment2 = AttachmentHelper.FakeAttachment("attachment2"); + + // Act + hint.Attachments.Add(attachment1); + hint.Attachments.Add(attachment2); + + // Assert + Assert.Equal(2, hint.Attachments.Count); + } + + [Fact] + public void Clear_WithEntries_ClearsHintEntries() + { + // Arrange + var hint = new Hint("key", "value"); + + // Act + hint.Items.Clear(); + + // Assert + hint.Items.Count.Should().Be(0); + } + + [Fact] + public void ClearAttachments_WithAttachments_ClearsHintAttachments() + { + // Arrange + var hint = new Hint(); + var attachment1 = AttachmentHelper.FakeAttachment("attachment1"); + hint.Attachments.Add(attachment1); + + // Act + hint.Attachments.Clear(); + + // Assert + hint.Attachments.Should().BeEmpty(); + } + + [Fact] + public void ContainsKey_ExistingKey_ReturnsTrue() + { + // Arrange + var hint = new Hint("key", "value"); + + // Act + var containsKey = hint.Items.ContainsKey("key"); + + // Assert + containsKey.Should().BeTrue(); + } + + [Fact] + public void ContainsKey_NonExistingKey_ReturnsFalse() + { + // Arrange + var hint = new Hint("key", "value"); + + // Act + var containsKey = hint.Items.ContainsKey("nonExistingKey"); + + // Assert + containsKey.Should().BeFalse(); + } + + [Fact] + public void Count_ReturnsZero_WhenHintIsEmpty() + { + // Arrange + var hint = new Hint(); + + // Act + var count = hint.Items.Count; + + // Assert + count.Should().Be(0); + } + + [Fact] + public void Count_ReturnsCorrectValue_WhenHintHasItems() + { + // Arrange + var hint = new Hint(); + hint.Items["key1"] = "value1"; + hint.Items["key2"] = "value2"; + + // Act + var count = hint.Items.Count; + + // Assert + count.Should().Be(2); + } + + [Fact] + public void Remove_WithExistingKey_RemovesEntry() + { + // Arrange + var hint = new Hint("key", "value"); + + // Act + hint.Items.Remove("key"); + + // Assert + hint.Items.ContainsKey("key").Should().BeFalse(); + } + + [Fact] + public void WithAttachments_ReturnsHintWithAttachments() + { + // Arrange + var attachment1 = AttachmentHelper.FakeAttachment("attachment1"); + var attachment2 = AttachmentHelper.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 = AttachmentHelper.FakeAttachment("attachment1"); + var attachment2 = AttachmentHelper.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/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 7ec2da25c5..4546c0bf37 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()); } @@ -403,6 +407,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() { @@ -1042,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] @@ -1103,7 +1126,88 @@ 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_Client_Gets_Hint() + { + // Arrange + var hub = _fixture.GetSut(); + + // Act + var transaction = hub.StartTransaction("test", "test"); + transaction.Finish(); + + // Assert + _fixture.Client.Received().CaptureTransaction(Arg.Any(), Arg.Any()); + } + + [Fact] + public void CaptureTransaction_Client_Gets_ScopeAttachments() + { + // 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); + } + + [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 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/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/Protocol/TransactionTests.cs b/test/Sentry.Tests/Protocol/TransactionTests.cs index da447c669a..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] @@ -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/ScopeTests.cs b/test/Sentry.Tests/ScopeTests.cs index fad7564e02..2944346f51 100644 --- a/test/Sentry.Tests/ScopeTests.cs +++ b/test/Sentry.Tests/ScopeTests.cs @@ -370,6 +370,50 @@ 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().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/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.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 1a3dbe7c04..6ce33e1e01 100644 --- a/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt +++ b/test/Sentry.Tests/SentryClientTests.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumb.verified.txt @@ -11,4 +11,4 @@ at SentryEvent Sentry.SentryClient.BeforeSend(...) Category: SentryClient, Level: error } -] +] \ No newline at end of file diff --git a/test/Sentry.Tests/SentryClientTests.cs b/test/Sentry.Tests/SentryClientTests.cs index dd71edb322..7268163a1d 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()); @@ -302,10 +302,173 @@ public void CaptureEvent_ExceptionFilter_RecordsDiscard() } [Fact] - public void CaptureEvent_BeforeEvent_ModifyEvent() + 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_BeforeSend_Gets_ScopeAttachments() + { + // 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_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); + + var 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_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() { SentryEvent received = null; - _fixture.SentryOptions.BeforeSend = e => received = e; + _fixture.SentryOptions.SetBeforeSend((e, _) => received = e); var @event = new SentryEvent(); @@ -366,7 +529,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 +545,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(); @@ -670,7 +833,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( @@ -683,11 +846,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") { @@ -705,7 +891,7 @@ public void CaptureTransaction_BeforeSendTransaction_ModifyEvent() public void CaptureTransaction_BeforeSendTransaction_replaced_transaction_captured() { Transaction received = null; - _fixture.SentryOptions.BeforeSendTransaction = tx => + _fixture.SentryOptions.SetBeforeSendTransaction((_, _) => { received = new Transaction("name2", "operation2") { @@ -715,7 +901,7 @@ public void CaptureTransaction_BeforeSendTransaction_replaced_transaction_captur }; return received; - }; + }); var transaction = new Transaction("name", "operation") { @@ -741,7 +927,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") { @@ -759,7 +945,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 45e70e0ce7..47c3d0a2bb 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(); @@ -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") { diff --git a/test/Sentry.Tests/SentryFailedRequestHandlerTests.cs b/test/Sentry.Tests/SentryFailedRequestHandlerTests.cs index 6582f82e23..fd8515f248 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.Items[HintTypes.HttpResponseMessage].Should().Be(response); + } + } } diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index fa1f5debd7..022ca45d32 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); } @@ -434,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!);