From a1b0cb6acf95ffbbeabbd1f2642f14f37ee00c72 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 11 Feb 2025 13:35:26 -0500 Subject: [PATCH 01/39] Initial commit for exposing iOS native before send & crashed last run --- samples/Sentry.Samples.Ios/AppDelegate.cs | 11 +++ .../Sentry.Samples.Ios.csproj | 2 +- src/Sentry.Bindings.Cocoa/ApiDefinitions.cs | 6 ++ .../PrivateApiDefinitions.cs | 10 ++ .../Cocoa/Extensions/SentryEventExtensions.cs | 91 ++++++++++--------- src/Sentry/Platforms/Cocoa/SentryOptions.cs | 10 ++ src/Sentry/Platforms/Cocoa/SentrySdk.cs | 60 +++++++----- 7 files changed, 123 insertions(+), 67 deletions(-) diff --git a/samples/Sentry.Samples.Ios/AppDelegate.cs b/samples/Sentry.Samples.Ios/AppDelegate.cs index 80e9ea09a5..f230730965 100644 --- a/samples/Sentry.Samples.Ios/AppDelegate.cs +++ b/samples/Sentry.Samples.Ios/AppDelegate.cs @@ -28,6 +28,17 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l options.Native.EnableAppHangTracking = true; options.CacheDirectoryPath = Path.GetTempPath(); + + options.Native.BeforeSend = e => + { + e.SetTag("dotnet-iOS-Native-Before", "Hello World"); + return e; + }; + + options.Native.CrashedLastRun = e => + { + Console.WriteLine(e); + }; }); // create a new window instance based on the screen size diff --git a/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj b/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj index 76b122d042..b1820343e6 100644 --- a/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj +++ b/samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj @@ -1,7 +1,7 @@ - net9.0-ios18.0 + net9.0-ios Exe enable true diff --git a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs index 3bbfc298dc..2561268cfb 100644 --- a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs @@ -2454,6 +2454,12 @@ interface PrivateSentrySDKOnly [Static] [Export ("breadcrumbWithDictionary:")] SentryBreadcrumb BreadcrumbWithDictionary (NSDictionary dictionary); + + + // +(SentryEnvelope * _Nonnull)envelopeWithData:(NSData * _Nonnull)data; + [Static] + [Export ("envelopeWithData:")] + SentryEnvelope EnvelopeWithData (NSData data); } // @interface SentryId : NSObject diff --git a/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs index ccd5d1865d..edb64e14a0 100644 --- a/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs @@ -30,3 +30,13 @@ interface SentrySession interface SentryTracer { } + +[Internal] +[DisableDefaultCtor] +[BaseType (typeof(NSObject))] +interface SentryEnvelope +{ + // @property (nonatomic, readonly, strong) NSArray *items; + [Export("items")] + NSArray Items { get; } +} diff --git a/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs b/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs index ebb7c735ba..9984b8a337 100644 --- a/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs +++ b/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs @@ -1,45 +1,46 @@ -// using Sentry.Extensibility; -// using Sentry.Protocol.Envelopes; -// -// namespace Sentry.Cocoa.Extensions; -// -// internal static class SentryEventExtensions -// { -// /* -// * These methods map between a SentryEvent and it's Cocoa counterpart by serializing as JSON into memory on one side, -// * then deserializing back to an object on the other side. It is not expected to be performant, as this code is only -// * used when a BeforeSend option is set, and then only when an event is captured by the Cocoa SDK (which should be -// * relatively rare). -// * -// * This approach avoids having to write to/from methods for the entire object graph. However, it's also important to -// * recognize that there's not necessarily a one-to-one mapping available on all objects (even through serialization) -// * between the two SDKs, so some optional details may be lost when roundtripping. That's generally OK, as this is -// * still better than nothing. If a specific part of the object graph becomes important to roundtrip, we can consider -// * updating the objects on either side. -// */ -// -// public static SentryEvent ToSentryEvent(this CocoaSdk.SentryEvent sentryEvent, SentryCocoaSdkOptions nativeOptions) -// { -// using var stream = sentryEvent.ToJsonStream()!; -// //stream.Seek(0, SeekOrigin.Begin); ?? -// -// using var json = JsonDocument.Parse(stream); -// var exception = sentryEvent.Error == null ? null : new NSErrorException(sentryEvent.Error); -// return SentryEvent.FromJson(json.RootElement, exception); -// } -// -// public static CocoaSdk.SentryEvent ToCocoaSentryEvent(this SentryEvent sentryEvent, SentryOptions options, SentryCocoaSdkOptions nativeOptions) -// { -// var envelope = Envelope.FromEvent(sentryEvent); -// -// using var stream = new MemoryStream(); -// envelope.Serialize(stream, options.DiagnosticLogger); -// stream.Seek(0, SeekOrigin.Begin); -// -// using var data = NSData.FromStream(stream)!; -// var cocoaEnvelope = CocoaSdk.PrivateSentrySDKOnly.EnvelopeWithData(data); -// -// var cocoaEvent = (CocoaSdk.SentryEvent) cocoaEnvelope.Items[0]; -// return cocoaEvent; -// } -// } +using Sentry.Extensibility; +using Sentry.Protocol.Envelopes; + +namespace Sentry.Cocoa.Extensions; + +internal static class SentryEventExtensions +{ + /* + * These methods map between a SentryEvent and it's Cocoa counterpart by serializing as JSON into memory on one side, + * then deserializing back to an object on the other side. It is not expected to be performant, as this code is only + * used when a BeforeSend option is set, and then only when an event is captured by the Cocoa SDK (which should be + * relatively rare). + * + * This approach avoids having to write to/from methods for the entire object graph. However, it's also important to + * recognize that there's not necessarily a one-to-one mapping available on all objects (even through serialization) + * between the two SDKs, so some optional details may be lost when roundtripping. That's generally OK, as this is + * still better than nothing. If a specific part of the object graph becomes important to roundtrip, we can consider + * updating the objects on either side. + */ + + public static SentryEvent ToSentryEvent(this CocoaSdk.SentryEvent sentryEvent, SentryCocoaSdkOptions nativeOptions) + { + using var stream = sentryEvent.ToJsonStream()!; + //stream.Seek(0, SeekOrigin.Begin); ?? + + using var json = JsonDocument.Parse(stream); + var exception = sentryEvent.Error == null ? null : new NSErrorException(sentryEvent.Error); + var ev = SentryEvent.FromJson(json.RootElement, exception); + return ev; + } + + public static CocoaSdk.SentryEvent ToCocoaSentryEvent(this SentryEvent sentryEvent, SentryOptions options, SentryCocoaSdkOptions nativeOptions) + { + var envelope = Envelope.FromEvent(sentryEvent); + + using var stream = new MemoryStream(); + envelope.Serialize(stream, options.DiagnosticLogger); + stream.Seek(0, SeekOrigin.Begin); + + using var data = NSData.FromStream(stream)!; + var cocoaEnvelope = CocoaSdk.PrivateSentrySDKOnly.EnvelopeWithData(data); + + var cocoaEvent = (CocoaSdk.SentryEvent) cocoaEnvelope.Items.GetItem(0); + return cocoaEvent; + } +} diff --git a/src/Sentry/Platforms/Cocoa/SentryOptions.cs b/src/Sentry/Platforms/Cocoa/SentryOptions.cs index 6b0e9587e6..bba31a6c66 100644 --- a/src/Sentry/Platforms/Cocoa/SentryOptions.cs +++ b/src/Sentry/Platforms/Cocoa/SentryOptions.cs @@ -236,6 +236,16 @@ public void AddInAppInclude(string prefix) InAppIncludes ??= new List(); InAppIncludes.Add(prefix); } + + /// + /// Sets native BeforeSend callback + /// + public Func? BeforeSend { get; set; } + + /// + /// Sets native OnCrashedLastRun callback + /// + public Action? CrashedLastRun { get; set; } } // We actually add the profiling integration automatically in InitSentryCocoaSdk(). diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index dcc8e48006..d7d84e27bc 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -35,6 +35,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) nativeOptions.SendDefaultPii = options.SendDefaultPii; nativeOptions.SessionTrackingIntervalMillis = (nuint)options.AutoSessionTrackingInterval.TotalMilliseconds; + // nativeOptions.BeforeSend if (options.Environment is { } environment) { nativeOptions.Environment = environment; @@ -88,27 +89,44 @@ private static void InitSentryCocoaSdk(SentryOptions options) // TODO: Finish SentryEventExtensions to enable these - // if (options.Native.EnableBeforeSend && options.BeforeSend is { } beforeSend) - // { - // nativeOptions.BeforeSend = evt => - // { - // var sentryEvent = evt.ToSentryEvent(nativeOptions); - // var result = beforeSend(sentryEvent)?.ToCocoaSentryEvent(options, nativeOptions); - // - // // Note: Nullable result is allowed but delegate is generated incorrectly - // // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 - // return result!; - // }; - // } - - // if (options.Native.OnCrashedLastRun is { } onCrashedLastRun) - // { - // nativeOptions.OnCrashedLastRun = evt => - // { - // var sentryEvent = evt.ToSentryEvent(nativeOptions); - // onCrashedLastRun(sentryEvent); - // }; - // } + if (options.Native.BeforeSend is { } beforeSend) + { + nativeOptions.BeforeSend = evt => + { + // because we delegate to user code, we need to protect anything that could happen in this event + try + { + var sentryEvent = evt.ToSentryEvent(nativeOptions); + var result = beforeSend(sentryEvent)?.ToCocoaSentryEvent(options, nativeOptions); + + // Note: Nullable result is allowed but delegate is generated incorrectly + // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 + return result!; + } + catch (Exception ex) + { + options.LogError(ex, "Before Send Error"); + return evt; + } + }; + } + + if (options.Native.CrashedLastRun is { } onCrashedLastRun) + { + nativeOptions.OnCrashedLastRun = evt => + { + // because we delegate to user code, we need to protect anything that could happen in this event + try + { + var sentryEvent = evt.ToSentryEvent(nativeOptions); + onCrashedLastRun(sentryEvent); + } + catch (Exception ex) + { + options.LogError(ex, "Crashed Last Run Error"); + } + }; + } // These options are from Cocoa's SentryOptions nativeOptions.AttachScreenshot = options.Native.AttachScreenshot; From 1a8474eb6dea32f5a63cce8b91be5a2860c4b45f Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 11 Feb 2025 14:33:26 -0500 Subject: [PATCH 02/39] Initial feature set is working --- .../Internal/Extensions/JsonExtensions.cs | 24 +++++++++++++++++++ src/Sentry/SentryEvent.cs | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Sentry/Internal/Extensions/JsonExtensions.cs b/src/Sentry/Internal/Extensions/JsonExtensions.cs index fa29909eac..ab245673e9 100644 --- a/src/Sentry/Internal/Extensions/JsonExtensions.cs +++ b/src/Sentry/Internal/Extensions/JsonExtensions.cs @@ -213,6 +213,30 @@ public static void Deconstruct(this JsonProperty jsonProperty, out string name, return double.Parse(json.ToString()!, CultureInfo.InvariantCulture); } + /// + /// Safety value to deal with native serialization - allows datetimeoffset to come in as a long or string value + /// + /// + /// + /// + public static DateTimeOffset? GetSafeDateTimeOffset(this JsonElement json, string propertyName) + { + DateTimeOffset? result = null; + var dtRaw = json.GetPropertyOrNull(propertyName); + if (dtRaw != null) + { + if (dtRaw.Value.ValueKind == JsonValueKind.Number) + { + result = DateTimeOffset.FromUnixTimeSeconds(dtRaw.Value.GetInt64()); + } + else + { + result = dtRaw.Value.GetDateTimeOffset(); + } + } + return result; + } + public static long? GetHexAsLong(this JsonElement json) { // If the address is in json as a number, we can just use it. diff --git a/src/Sentry/SentryEvent.cs b/src/Sentry/SentryEvent.cs index b580a89881..031358a3f0 100644 --- a/src/Sentry/SentryEvent.cs +++ b/src/Sentry/SentryEvent.cs @@ -290,7 +290,7 @@ internal static SentryEvent FromJson(JsonElement json, Exception? exception) { var modules = json.GetPropertyOrNull("modules")?.GetStringDictionaryOrNull(); var eventId = json.GetPropertyOrNull("event_id")?.Pipe(SentryId.FromJson) ?? SentryId.Empty; - var timestamp = json.GetPropertyOrNull("timestamp")?.GetDateTimeOffset(); + var timestamp = json.GetSafeDateTimeOffset("timestamp"); // Native sentryevents are serialized to epoch timestamps var message = json.GetPropertyOrNull("logentry")?.Pipe(SentryMessage.FromJson); var logger = json.GetPropertyOrNull("logger")?.GetString(); var platform = json.GetPropertyOrNull("platform")?.GetString(); From c36be94c52e5c0b06ebdd12453d1972a51be10eb Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Tue, 11 Feb 2025 19:48:59 +0000 Subject: [PATCH 03/39] Format code --- src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs b/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs index 9984b8a337..71196d454a 100644 --- a/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs +++ b/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs @@ -40,7 +40,7 @@ public static CocoaSdk.SentryEvent ToCocoaSentryEvent(this SentryEvent sentryEve using var data = NSData.FromStream(stream)!; var cocoaEnvelope = CocoaSdk.PrivateSentrySDKOnly.EnvelopeWithData(data); - var cocoaEvent = (CocoaSdk.SentryEvent) cocoaEnvelope.Items.GetItem(0); + var cocoaEvent = (CocoaSdk.SentryEvent)cocoaEnvelope.Items.GetItem(0); return cocoaEvent; } } From cd074d4ab57b066a2ee2c4815c99981772a45e65 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 11 Feb 2025 14:49:30 -0500 Subject: [PATCH 04/39] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 674e18a1f8..73b7fac735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Fixed envelopes with oversized attachments getting stuck in __processing ([#3938](https://github.com/getsentry/sentry-dotnet/pull/3938)) - Unknown stack frames in profiles on .NET 8+ ([#3942](https://github.com/getsentry/sentry-dotnet/pull/3942)) - Deduplicate profiling stack frames ([#3941](https://github.com/getsentry/sentry-dotnet/pull/3941)) +- Map iOS native events - OnCrashedLastRun and BeforeSend ([#2102](https://github.com/getsentry/sentry-dotnet/pull/3958)) ## 5.1.0 From 70a677da85dcd18b61933c27a09fe491f78a1630 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Wed, 12 Feb 2025 09:36:53 -0500 Subject: [PATCH 05/39] Merge signal abort code before user based BeforeSend --- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 71 ++++++++++++------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index d7d84e27bc..4bb4cb68a8 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -93,6 +93,39 @@ private static void InitSentryCocoaSdk(SentryOptions options) { nativeOptions.BeforeSend = evt => { + // When we have an unhandled managed exception, we send that to Sentry twice - once managed and once native. + // The managed exception is what a .NET developer would expect, and it is sent by the Sentry.NET SDK + // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. + + // There should only be one exception on the event in this case + if (evt.Exceptions?.Length == 1) + { + // It will match the following characteristics + var ex = evt.Exceptions[0]; + + // Thankfully, sometimes we can see Xamarin's unhandled exception handler on the stack trace, so we can filter + // them out. Here is the function that calls abort(), which we will use as a filter: + // https://github.com/xamarin/xamarin-macios/blob/c55fbdfef95028ba03d0f7a35aebca03bd76f852/runtime/runtime.m#L1114-L1122 + if (ex.Type == "SIGABRT" && ex.Value == "Signal 6, Code 0" && + ex.Stacktrace?.Frames.Any(f => f.Function == "xamarin_unhandled_exception_handler") is true) + { + // Don't send it + options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); + return null!; + } + + // Similar workaround for NullReferenceExceptions. We don't have any easy way to know whether the + // exception is managed code (compiled to native) or original native code though. + // See: https://github.com/getsentry/sentry-dotnet/issues/3776 + if (ex.Type == "EXC_BAD_ACCESS") + { + // Don't send it + options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); + return null!; + } + } + + // we run our SIGABRT checks first before handing over to user events // because we delegate to user code, we need to protect anything that could happen in this event try { @@ -115,6 +148,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) { nativeOptions.OnCrashedLastRun = evt => { + // because we delegate to user code, we need to protect anything that could happen in this event try { @@ -163,43 +197,6 @@ private static void InitSentryCocoaSdk(SentryOptions options) // nativeOptions.DefaultIntegrations // nativeOptions.EnableProfiling (deprecated) - // When we have an unhandled managed exception, we send that to Sentry twice - once managed and once native. - // The managed exception is what a .NET developer would expect, and it is sent by the Sentry.NET SDK - // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. - nativeOptions.BeforeSend = evt => - { - // There should only be one exception on the event in this case - if (evt.Exceptions?.Length == 1) - { - // It will match the following characteristics - var ex = evt.Exceptions[0]; - - // Thankfully, sometimes we can see Xamarin's unhandled exception handler on the stack trace, so we can filter - // them out. Here is the function that calls abort(), which we will use as a filter: - // https://github.com/xamarin/xamarin-macios/blob/c55fbdfef95028ba03d0f7a35aebca03bd76f852/runtime/runtime.m#L1114-L1122 - if (ex.Type == "SIGABRT" && ex.Value == "Signal 6, Code 0" && - ex.Stacktrace?.Frames.Any(f => f.Function == "xamarin_unhandled_exception_handler") is true) - { - // Don't send it - options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); - return null!; - } - - // Similar workaround for NullReferenceExceptions. We don't have any easy way to know whether the - // exception is managed code (compiled to native) or original native code though. - // See: https://github.com/getsentry/sentry-dotnet/issues/3776 - if (ex.Type == "EXC_BAD_ACCESS") - { - // Don't send it - options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); - return null!; - } - } - - // Other event, send as normal - return evt; - }; - // Set hybrid SDK name SentryCocoaHybridSdk.SetSdkName("sentry.cocoa.dotnet"); From 4a6462c0af6f858edcca5537b028cf76dcb1f8b3 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Fri, 14 Feb 2025 13:03:06 -0500 Subject: [PATCH 06/39] Move native BeforeSend & OnCrashedLastRun events to SentryOptions - OnCrashedLastRun only available to IOS/MacCatalyst for now --- samples/Sentry.Samples.Ios/AppDelegate.cs | 10 +++++----- src/Sentry/Platforms/Cocoa/SentryOptions.cs | 10 ---------- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 6 +++--- src/Sentry/SentryOptions.cs | 8 ++++++++ 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/samples/Sentry.Samples.Ios/AppDelegate.cs b/samples/Sentry.Samples.Ios/AppDelegate.cs index f230730965..2d50a320c3 100644 --- a/samples/Sentry.Samples.Ios/AppDelegate.cs +++ b/samples/Sentry.Samples.Ios/AppDelegate.cs @@ -29,13 +29,13 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l options.CacheDirectoryPath = Path.GetTempPath(); - options.Native.BeforeSend = e => + options.SetBeforeSend((evt, _) => { - e.SetTag("dotnet-iOS-Native-Before", "Hello World"); - return e; - }; + evt.SetTag("dotnet-iOS-Native-Before", "Hello World"); + return evt; + }); - options.Native.CrashedLastRun = e => + options.OnCrashedLastRun = e => { Console.WriteLine(e); }; diff --git a/src/Sentry/Platforms/Cocoa/SentryOptions.cs b/src/Sentry/Platforms/Cocoa/SentryOptions.cs index bba31a6c66..6b0e9587e6 100644 --- a/src/Sentry/Platforms/Cocoa/SentryOptions.cs +++ b/src/Sentry/Platforms/Cocoa/SentryOptions.cs @@ -236,16 +236,6 @@ public void AddInAppInclude(string prefix) InAppIncludes ??= new List(); InAppIncludes.Add(prefix); } - - /// - /// Sets native BeforeSend callback - /// - public Func? BeforeSend { get; set; } - - /// - /// Sets native OnCrashedLastRun callback - /// - public Action? CrashedLastRun { get; set; } } // We actually add the profiling integration automatically in InitSentryCocoaSdk(). diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 4bb4cb68a8..ee0098745a 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -89,7 +89,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) // TODO: Finish SentryEventExtensions to enable these - if (options.Native.BeforeSend is { } beforeSend) + if (options.BeforeSendInternal is { } beforeSend) { nativeOptions.BeforeSend = evt => { @@ -130,7 +130,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) try { var sentryEvent = evt.ToSentryEvent(nativeOptions); - var result = beforeSend(sentryEvent)?.ToCocoaSentryEvent(options, nativeOptions); + var result = beforeSend(sentryEvent, null!)?.ToCocoaSentryEvent(options, nativeOptions); // Note: Nullable result is allowed but delegate is generated incorrectly // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 @@ -144,7 +144,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) }; } - if (options.Native.CrashedLastRun is { } onCrashedLastRun) + if (options.OnCrashedLastRun is { } onCrashedLastRun) { nativeOptions.OnCrashedLastRun = evt => { diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index 7c99988acf..ef1c2cc6e9 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -1075,6 +1075,14 @@ public StackTraceMode StackTraceMode /// public Func? CrashedLastRun { get; set; } + #if IOS || MACCATALYST + // this event currently isn't being pushed from Android + /// + /// Delegate which is run with event information if the application crashed during last run. + /// + public Action? OnCrashedLastRun { get; set; } + #endif + /// /// /// Gets the used to create spans. From 91145ab92483dcb1bd86ab1433167f37d68a2ccd Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Fri, 14 Feb 2025 13:12:45 -0500 Subject: [PATCH 07/39] Always run NativeOptions.BeforeSend so sig aborts can be filtered --- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 92 ++++++++++++------------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index ee0098745a..26ab0db511 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -87,61 +87,57 @@ private static void InitSentryCocoaSdk(SentryOptions options) } } - // TODO: Finish SentryEventExtensions to enable these - - if (options.BeforeSendInternal is { } beforeSend) + nativeOptions.BeforeSend = evt => { - nativeOptions.BeforeSend = evt => - { - // When we have an unhandled managed exception, we send that to Sentry twice - once managed and once native. - // The managed exception is what a .NET developer would expect, and it is sent by the Sentry.NET SDK - // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. + // When we have an unhandled managed exception, we send that to Sentry twice - once managed and once native. + // The managed exception is what a .NET developer would expect, and it is sent by the Sentry.NET SDK + // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. - // There should only be one exception on the event in this case - if (evt.Exceptions?.Length == 1) + // There should only be one exception on the event in this case + if (evt.Exceptions?.Length == 1) + { + // It will match the following characteristics + var ex = evt.Exceptions[0]; + + // Thankfully, sometimes we can see Xamarin's unhandled exception handler on the stack trace, so we can filter + // them out. Here is the function that calls abort(), which we will use as a filter: + // https://github.com/xamarin/xamarin-macios/blob/c55fbdfef95028ba03d0f7a35aebca03bd76f852/runtime/runtime.m#L1114-L1122 + if (ex.Type == "SIGABRT" && ex.Value == "Signal 6, Code 0" && + ex.Stacktrace?.Frames.Any(f => f.Function == "xamarin_unhandled_exception_handler") is true) { - // It will match the following characteristics - var ex = evt.Exceptions[0]; - - // Thankfully, sometimes we can see Xamarin's unhandled exception handler on the stack trace, so we can filter - // them out. Here is the function that calls abort(), which we will use as a filter: - // https://github.com/xamarin/xamarin-macios/blob/c55fbdfef95028ba03d0f7a35aebca03bd76f852/runtime/runtime.m#L1114-L1122 - if (ex.Type == "SIGABRT" && ex.Value == "Signal 6, Code 0" && - ex.Stacktrace?.Frames.Any(f => f.Function == "xamarin_unhandled_exception_handler") is true) - { - // Don't send it - options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); - return null!; - } - - // Similar workaround for NullReferenceExceptions. We don't have any easy way to know whether the - // exception is managed code (compiled to native) or original native code though. - // See: https://github.com/getsentry/sentry-dotnet/issues/3776 - if (ex.Type == "EXC_BAD_ACCESS") - { - // Don't send it - options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); - return null!; - } + // Don't send it + options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); + return null!; } - // we run our SIGABRT checks first before handing over to user events - // because we delegate to user code, we need to protect anything that could happen in this event - try - { - var sentryEvent = evt.ToSentryEvent(nativeOptions); - var result = beforeSend(sentryEvent, null!)?.ToCocoaSentryEvent(options, nativeOptions); - - // Note: Nullable result is allowed but delegate is generated incorrectly - // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 - return result!; - } - catch (Exception ex) + // Similar workaround for NullReferenceExceptions. We don't have any easy way to know whether the + // exception is managed code (compiled to native) or original native code though. + // See: https://github.com/getsentry/sentry-dotnet/issues/3776 + if (ex.Type == "EXC_BAD_ACCESS") { - options.LogError(ex, "Before Send Error"); - return evt; + // Don't send it + options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); + return null!; } - }; + } + + // we run our SIGABRT checks first before handing over to user events + // because we delegate to user code, we need to protect anything that could happen in this event + try + { + var sentryEvent = evt.ToSentryEvent(nativeOptions); + var result = options.BeforeSendInternal?.Invoke(sentryEvent, null!)?.ToCocoaSentryEvent(options, nativeOptions); + + // Note: Nullable result is allowed but delegate is generated incorrectly + // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 + return result!; + } + catch (Exception ex) + { + options.LogError(ex, "Before Send Error"); + return evt; + } + }; } if (options.OnCrashedLastRun is { } onCrashedLastRun) From fd4802611235b61f8c9f9e63c2c14303dda23c47 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Fri, 14 Feb 2025 13:13:08 -0500 Subject: [PATCH 08/39] Update SentrySdk.cs --- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 26ab0db511..a38a26fa4e 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -138,7 +138,6 @@ private static void InitSentryCocoaSdk(SentryOptions options) return evt; } }; - } if (options.OnCrashedLastRun is { } onCrashedLastRun) { From c29a78511b29f4022e35e99a3b05a63bc447897d Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 18 Feb 2025 11:40:36 -0500 Subject: [PATCH 09/39] Update CHANGELOG.md --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a441bc47d..32605286b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Map iOS native events - OnCrashedLastRun and BeforeSend ([#2102](https://github.com/getsentry/sentry-dotnet/pull/3958)) + ## 5.1.1 ### Fixes @@ -10,7 +16,6 @@ - Fixed envelopes with oversized attachments getting stuck in __processing ([#3938](https://github.com/getsentry/sentry-dotnet/pull/3938)) - Unknown stack frames in profiles on .NET 8+ ([#3942](https://github.com/getsentry/sentry-dotnet/pull/3942)) - Deduplicate profiling stack frames ([#3941](https://github.com/getsentry/sentry-dotnet/pull/3941)) -- Map iOS native events - OnCrashedLastRun and BeforeSend ([#2102](https://github.com/getsentry/sentry-dotnet/pull/3958)) - OperatingSystem will now return macOS as OS name instead of 'Darwin' as well as the proper version. ([#2710](https://github.com/getsentry/sentry-dotnet/pull/3956)) - Ignore null value on CocoaScopeObserver.SetTag ([#3948](https://github.com/getsentry/sentry-dotnet/pull/3948)) - Deduplicate profiling stack frames ([#3969](https://github.com/getsentry/sentry-dotnet/pull/3969)) From 50a555484db96d566b92cc24639a29deac5f147c Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Tue, 18 Feb 2025 16:55:29 +0000 Subject: [PATCH 10/39] Format code --- src/Sentry/SentryOptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index ef1c2cc6e9..69a87685a7 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -1075,13 +1075,13 @@ public StackTraceMode StackTraceMode /// public Func? CrashedLastRun { get; set; } - #if IOS || MACCATALYST +#if IOS || MACCATALYST // this event currently isn't being pushed from Android /// /// Delegate which is run with event information if the application crashed during last run. /// public Action? OnCrashedLastRun { get; set; } - #endif +#endif /// /// From 63e3f555ade709a793a3a827b8e46c2d5b4f3c8e Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 18 Feb 2025 18:09:47 -0500 Subject: [PATCH 11/39] WIP - removing serialization mechanics as they cause a native crash due to SentryEnvelopeItem being deserialized instead of SentryEvent --- src/Sentry.Bindings.Cocoa/ApiDefinitions.cs | 6 -- .../PrivateApiDefinitions.cs | 10 ---- .../Cocoa/Extensions/SentryEventExtensions.cs | 58 +++++++++++++++---- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 31 ++++++---- 4 files changed, 67 insertions(+), 38 deletions(-) diff --git a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs index 2561268cfb..3bbfc298dc 100644 --- a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs @@ -2454,12 +2454,6 @@ interface PrivateSentrySDKOnly [Static] [Export ("breadcrumbWithDictionary:")] SentryBreadcrumb BreadcrumbWithDictionary (NSDictionary dictionary); - - - // +(SentryEnvelope * _Nonnull)envelopeWithData:(NSData * _Nonnull)data; - [Static] - [Export ("envelopeWithData:")] - SentryEnvelope EnvelopeWithData (NSData data); } // @interface SentryId : NSObject diff --git a/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs index edb64e14a0..ccd5d1865d 100644 --- a/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/PrivateApiDefinitions.cs @@ -30,13 +30,3 @@ interface SentrySession interface SentryTracer { } - -[Internal] -[DisableDefaultCtor] -[BaseType (typeof(NSObject))] -interface SentryEnvelope -{ - // @property (nonatomic, readonly, strong) NSArray *items; - [Export("items")] - NSArray Items { get; } -} diff --git a/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs b/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs index 71196d454a..4e1b7d3739 100644 --- a/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs +++ b/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs @@ -1,4 +1,5 @@ using Sentry.Extensibility; +using Sentry.Internal.Extensions; using Sentry.Protocol.Envelopes; namespace Sentry.Cocoa.Extensions; @@ -18,10 +19,11 @@ internal static class SentryEventExtensions * updating the objects on either side. */ - public static SentryEvent ToSentryEvent(this CocoaSdk.SentryEvent sentryEvent, SentryCocoaSdkOptions nativeOptions) + public static SentryEvent? ToSentryEvent(this CocoaSdk.SentryEvent sentryEvent) { - using var stream = sentryEvent.ToJsonStream()!; - //stream.Seek(0, SeekOrigin.Begin); ?? + using var stream = sentryEvent.ToJsonStream(); + if (stream == null) + return null; using var json = JsonDocument.Parse(stream); var exception = sentryEvent.Error == null ? null : new NSErrorException(sentryEvent.Error); @@ -29,18 +31,50 @@ public static SentryEvent ToSentryEvent(this CocoaSdk.SentryEvent sentryEvent, S return ev; } - public static CocoaSdk.SentryEvent ToCocoaSentryEvent(this SentryEvent sentryEvent, SentryOptions options, SentryCocoaSdkOptions nativeOptions) + public static CocoaSdk.SentryEvent ToCocoaSentryEvent(this SentryEvent sentryEvent, SentryOptions options) { - var envelope = Envelope.FromEvent(sentryEvent); + var native = new CocoaSdk.SentryEvent(); - using var stream = new MemoryStream(); - envelope.Serialize(stream, options.DiagnosticLogger); - stream.Seek(0, SeekOrigin.Begin); + native.ServerName = sentryEvent.ServerName; + native.Dist = sentryEvent.Distribution; + native.Logger = sentryEvent.Logger; + native.ReleaseName = sentryEvent.Release; + native.Environment = sentryEvent.Environment; + native.Platform = sentryEvent.Platform!; + native.Transaction = sentryEvent.TransactionName!; + native.Fingerprint = sentryEvent.Fingerprint?.ToArray(); + native.Timestamp = sentryEvent.Timestamp.ToNSDate(); + native.Modules = sentryEvent.Modules.ToDictionary(kv => kv.Key, kv => kv.Value); - using var data = NSData.FromStream(stream)!; - var cocoaEnvelope = CocoaSdk.PrivateSentrySDKOnly.EnvelopeWithData(data); + native.Tags = sentryEvent.Tags?.ToDictionary(kv => kv.Key, kv => kv.Value); + native.EventId = sentryEvent.EventId.ToCocoaSentryId(); + native.Extra = sentryEvent.Extra?.ToDictionary(kv => kv.Key, kv => kv.Value); + native.Breadcrumbs = sentryEvent.Breadcrumbs?.Select(x => x.ToCocoaBreadcrumb()).ToArray(); + native.User = sentryEvent.User?.ToCocoaUser(); + // native.Error = NSError.FromDomain() sentryEvent.Exception + //sentryEvent.Request; + // native.Level = sentryEvent.Level; + // native.Sdk = sentryEvent.Sdk + // native.Context = sentryEvent.Contexts; + // sentryEvent.DebugImages + // sentryEvent.SentryExceptions + // native.Type = sentryEvent.T + // native.Message = sentryEvent.Message + // native.Threads = sentryEvent.SentryThreads - var cocoaEvent = (CocoaSdk.SentryEvent)cocoaEnvelope.Items.GetItem(0); - return cocoaEvent; + return native; } } +// var envelope = Envelope.FromEvent(sentryEvent); +// var native = new CocoaSdk.SentryEvent(); +// +// using var stream = new MemoryStream(); +// envelope.Serialize(stream, options.DiagnosticLogger); +// stream.Seek(0, SeekOrigin.Begin); +// +// using var data = NSData.FromStream(stream)!; +// var cocoaEnvelope = CocoaSdk.PrivateSentrySDKOnly.EnvelopeWithData(data); +// +// var cocoaEvent = (CocoaSdk.SentryEvent)cocoaEnvelope.Items.GetItem(0); +// // this will return a SentryEnvelopeItem which has isCrashEvent that causes native serialization panic +// return cocoaEvent; diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index a38a26fa4e..60971abee5 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -106,7 +106,8 @@ private static void InitSentryCocoaSdk(SentryOptions options) ex.Stacktrace?.Frames.Any(f => f.Function == "xamarin_unhandled_exception_handler") is true) { // Don't send it - options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); + options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, + ex.Value); return null!; } @@ -116,7 +117,8 @@ private static void InitSentryCocoaSdk(SentryOptions options) if (ex.Type == "EXC_BAD_ACCESS") { // Don't send it - options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, ex.Value); + options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, + ex.Value); return null!; } } @@ -125,12 +127,19 @@ private static void InitSentryCocoaSdk(SentryOptions options) // because we delegate to user code, we need to protect anything that could happen in this event try { - var sentryEvent = evt.ToSentryEvent(nativeOptions); - var result = options.BeforeSendInternal?.Invoke(sentryEvent, null!)?.ToCocoaSentryEvent(options, nativeOptions); + var sentryEvent = evt.ToSentryEvent(); + if (sentryEvent != null) + { + var result = options + .BeforeSendInternal? + .Invoke(sentryEvent, null!)? + .ToCocoaSentryEvent(options); - // Note: Nullable result is allowed but delegate is generated incorrectly - // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 - return result!; + // // Note: Nullable result is allowed but delegate is generated incorrectly + // // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 + return result!; + } + return evt; } catch (Exception ex) { @@ -143,12 +152,14 @@ private static void InitSentryCocoaSdk(SentryOptions options) { nativeOptions.OnCrashedLastRun = evt => { - // because we delegate to user code, we need to protect anything that could happen in this event try { - var sentryEvent = evt.ToSentryEvent(nativeOptions); - onCrashedLastRun(sentryEvent); + var sentryEvent = evt.ToSentryEvent(); + if (sentryEvent != null) + { + onCrashedLastRun(sentryEvent); + } } catch (Exception ex) { From 5c06eb55c07b59ba9226baa897a4c0700279301d Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Fri, 21 Feb 2025 10:31:42 -0500 Subject: [PATCH 12/39] Transferring transformed sentryevent back to original native event instead of trying to remap the entire object --- src/Sentry.Bindings.Cocoa/ApiDefinitions.cs | 3 +- .../Cocoa/Extensions/CocoaExtensions.cs | 115 ++++++++++++++++++ .../Cocoa/Extensions/SentryEventExtensions.cs | 80 ------------ src/Sentry/Platforms/Cocoa/SentryOptions.cs | 15 +++ src/Sentry/Platforms/Cocoa/SentrySdk.cs | 47 +++++-- 5 files changed, 168 insertions(+), 92 deletions(-) delete mode 100644 src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs diff --git a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs index 3bbfc298dc..57e18f8790 100644 --- a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs @@ -27,8 +27,7 @@ namespace Sentry.CocoaSdk; // typedef SentryEvent * _Nullable (^SentryBeforeSendEventCallback)(SentryEvent * _Nonnull); [Internal] -[return: NullAllowed] -delegate SentryEvent SentryBeforeSendEventCallback (SentryEvent @event); +delegate SentryEvent? SentryBeforeSendEventCallback (SentryEvent @event); // typedef id _Nullable (^SentryBeforeSendSpanCallback)(id _Nonnull); [Internal] diff --git a/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs b/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs index e0fb5e7bc0..1d53321166 100644 --- a/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs +++ b/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs @@ -169,6 +169,24 @@ public static NSDictionary ToNSDictionary( d.Keys.ToArray()); } + public static NSDictionary ToNSDictionaryStrings( + this IEnumerable> dict) + { + var d = new Dictionary(); + foreach (var item in dict) + { + if (item.Value != null) + { + d.Add((NSString)item.Key, new NSString(item.Value)); + } + } + + return NSDictionary + .FromObjectsAndKeys( + d.Values.ToArray(), + d.Keys.ToArray()); + } + public static NSDictionary? ToNullableNSDictionary( this ICollection> dict) => dict.Count == 0 ? null : dict.ToNSDictionary(); @@ -224,4 +242,101 @@ public static object ToObject(this NSNumber n) $"NSNumber \"{n.StringValue}\" has an unknown ObjCType \"{n.ObjCType}\" (Class: \"{n.Class.Name}\")") }; } + + + public static SentryEvent? ToSentryEvent(this CocoaSdk.SentryEvent sentryEvent) + { + using var stream = sentryEvent.ToJsonStream(); + if (stream == null) + return null; + + using var json = JsonDocument.Parse(stream); + var exception = sentryEvent.Error == null ? null : new NSErrorException(sentryEvent.Error); + var ev = SentryEvent.FromJson(json.RootElement, exception); + return ev; + } + + public static CocoaSdk.SentryMessage ToCocoaSentryMessage(this SentryMessage msg) + { + var native = new CocoaSdk.SentryMessage(msg.Formatted ?? string.Empty); + native.Params = msg.Params?.Select(x => x.ToString()!).ToArray() ?? new string[0]; + + return native; + } + + // not tested or needed yet - leaving for future just in case + // public static CocoaSdk.SentryThread ToCocoaSentryThread(this SentryThread thread) + // { + // var id = NSNumber.FromInt32(thread.Id ?? 0); + // var native = new CocoaSdk.SentryThread(id); + // native.Crashed = thread.Crashed; + // native.Current = thread.Current; + // native.Name = thread.Name; + // native.Stacktrace = thread.Stacktrace?.ToCocoaSentryStackTrace(); + // // native.IsMain = not in dotnet + // return native; + // } + // + // public static CocoaSdk.SentryRequest ToCocoaSentryRequest(this SentryRequest request) + // { + // var native = new CocoaSdk.SentryRequest(); + // native.Cookies = request.Cookies; + // native.Headers = request.Headers?.ToNSDictionaryStrings(); + // native.Method = request.Method; + // native.QueryString = request.QueryString; + // native.Url = request.Url; + // + // // native.BodySize does not exist in dotnet + // return native; + // } + // + + // public static CocoaSdk.SentryException ToCocoaSentryException(this SentryException ex) + // { + // var native = new CocoaSdk.SentryException(ex.Value ?? string.Empty, ex.Type ?? string.Empty); + // native.Module = ex.Module; + // native.Mechanism = ex.Mechanism?.ToCocoaSentryMechanism(); + // native.Stacktrace = ex.Stacktrace?.ToCocoaSentryStackTrace(); + // // not part of native - ex.ThreadId; + // return native; + // } + // + // public static CocoaSdk.SentryStacktrace ToCocoaSentryStackTrace(this SentryStackTrace stackTrace) + // { + // var frames = stackTrace.Frames?.Select(x => x.ToCocoaSentryFrame()).ToArray() ?? new CocoaSdk.SentryFrame[0]; + // var native = new CocoaSdk.SentryStacktrace(frames, new NSDictionary()); + // // native.Register & native.Snapshot missing in dotnet + // return native; + // } + // + // public static CocoaSdk.SentryFrame ToCocoaSentryFrame(this SentryStackFrame frame) + // { + // var native = new CocoaSdk.SentryFrame(); + // native.Module = frame.Module; + // native.Package = frame.Package; + // native.InstructionAddress = frame.InstructionAddress?.ToString(); + // native.Function = frame.Function; + // native.Platform = frame.Platform; + // native.ColumnNumber = frame.ColumnNumber; + // native.FileName = frame.FileName; + // native.InApp = frame.InApp; + // native.ImageAddress = frame.ImageAddress?.ToString(); + // native.LineNumber = frame.LineNumber; + // native.SymbolAddress = frame.SymbolAddress?.ToString(); + // + // // native.StackStart = doesn't exist in dotnet + // return native; + // } + // + // public static CocoaSdk.SentryMechanism ToCocoaSentryMechanism(this Mechanism mechanism) + // { + // var native = new CocoaSdk.SentryMechanism(mechanism.Type); + // native.Synthetic = mechanism.Synthetic; + // native.Handled = mechanism.Handled; + // native.Desc = mechanism.Description; + // native.HelpLink = mechanism.HelpLink; + // native.Data = mechanism.Data?.ToNSDictionary(); + // // TODO: Meta does not currently translate in dotnet - native.Meta = null; + // return native; + // } } diff --git a/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs b/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs deleted file mode 100644 index 4e1b7d3739..0000000000 --- a/src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Sentry.Extensibility; -using Sentry.Internal.Extensions; -using Sentry.Protocol.Envelopes; - -namespace Sentry.Cocoa.Extensions; - -internal static class SentryEventExtensions -{ - /* - * These methods map between a SentryEvent and it's Cocoa counterpart by serializing as JSON into memory on one side, - * then deserializing back to an object on the other side. It is not expected to be performant, as this code is only - * used when a BeforeSend option is set, and then only when an event is captured by the Cocoa SDK (which should be - * relatively rare). - * - * This approach avoids having to write to/from methods for the entire object graph. However, it's also important to - * recognize that there's not necessarily a one-to-one mapping available on all objects (even through serialization) - * between the two SDKs, so some optional details may be lost when roundtripping. That's generally OK, as this is - * still better than nothing. If a specific part of the object graph becomes important to roundtrip, we can consider - * updating the objects on either side. - */ - - public static SentryEvent? ToSentryEvent(this CocoaSdk.SentryEvent sentryEvent) - { - using var stream = sentryEvent.ToJsonStream(); - if (stream == null) - return null; - - using var json = JsonDocument.Parse(stream); - var exception = sentryEvent.Error == null ? null : new NSErrorException(sentryEvent.Error); - var ev = SentryEvent.FromJson(json.RootElement, exception); - return ev; - } - - public static CocoaSdk.SentryEvent ToCocoaSentryEvent(this SentryEvent sentryEvent, SentryOptions options) - { - var native = new CocoaSdk.SentryEvent(); - - native.ServerName = sentryEvent.ServerName; - native.Dist = sentryEvent.Distribution; - native.Logger = sentryEvent.Logger; - native.ReleaseName = sentryEvent.Release; - native.Environment = sentryEvent.Environment; - native.Platform = sentryEvent.Platform!; - native.Transaction = sentryEvent.TransactionName!; - native.Fingerprint = sentryEvent.Fingerprint?.ToArray(); - native.Timestamp = sentryEvent.Timestamp.ToNSDate(); - native.Modules = sentryEvent.Modules.ToDictionary(kv => kv.Key, kv => kv.Value); - - native.Tags = sentryEvent.Tags?.ToDictionary(kv => kv.Key, kv => kv.Value); - native.EventId = sentryEvent.EventId.ToCocoaSentryId(); - native.Extra = sentryEvent.Extra?.ToDictionary(kv => kv.Key, kv => kv.Value); - native.Breadcrumbs = sentryEvent.Breadcrumbs?.Select(x => x.ToCocoaBreadcrumb()).ToArray(); - native.User = sentryEvent.User?.ToCocoaUser(); - // native.Error = NSError.FromDomain() sentryEvent.Exception - //sentryEvent.Request; - // native.Level = sentryEvent.Level; - // native.Sdk = sentryEvent.Sdk - // native.Context = sentryEvent.Contexts; - // sentryEvent.DebugImages - // sentryEvent.SentryExceptions - // native.Type = sentryEvent.T - // native.Message = sentryEvent.Message - // native.Threads = sentryEvent.SentryThreads - - return native; - } -} -// var envelope = Envelope.FromEvent(sentryEvent); -// var native = new CocoaSdk.SentryEvent(); -// -// using var stream = new MemoryStream(); -// envelope.Serialize(stream, options.DiagnosticLogger); -// stream.Seek(0, SeekOrigin.Begin); -// -// using var data = NSData.FromStream(stream)!; -// var cocoaEnvelope = CocoaSdk.PrivateSentrySDKOnly.EnvelopeWithData(data); -// -// var cocoaEvent = (CocoaSdk.SentryEvent)cocoaEnvelope.Items.GetItem(0); -// // this will return a SentryEnvelopeItem which has isCrashEvent that causes native serialization panic -// return cocoaEvent; diff --git a/src/Sentry/Platforms/Cocoa/SentryOptions.cs b/src/Sentry/Platforms/Cocoa/SentryOptions.cs index 6b0e9587e6..f1d346eccd 100644 --- a/src/Sentry/Platforms/Cocoa/SentryOptions.cs +++ b/src/Sentry/Platforms/Cocoa/SentryOptions.cs @@ -197,6 +197,21 @@ internal NativeOptions(SentryOptions options) /// public NSUrlSessionDelegate? UrlSessionDelegate { get; set; } = null; + /// + /// + /// Whether to suppress capturing SIGSEGV (Segfault) errors in the Native SDK. + /// + /// + /// When managed code results in a NullReferenceException, this also causes a SIGSEGV (Segfault). Duplicate + /// events (one managed and one native) can be prevented by suppressing native Segfaults, which may be + /// convenient. + /// + /// + /// Enabling this may prevent the capture of Segfault originating from native (not managed) code... so it may + /// prevent the capture of genuine native Segfault errors. + /// + /// + public bool SuppressSegfaults { get; set; } = false; // ---------- Other ---------- diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 60971abee5..e53fb3d016 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -94,7 +94,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. // There should only be one exception on the event in this case - if (evt.Exceptions?.Length == 1) + if (options.Native.SuppressSegfaults && evt.Exceptions?.Length == 1) { // It will match the following characteristics var ex = evt.Exceptions[0]; @@ -125,21 +125,48 @@ private static void InitSentryCocoaSdk(SentryOptions options) // we run our SIGABRT checks first before handing over to user events // because we delegate to user code, we need to protect anything that could happen in this event + if (options.BeforeSendInternal == null) + return evt; + try { var sentryEvent = evt.ToSentryEvent(); - if (sentryEvent != null) + if (sentryEvent == null) + return evt; + + var result = options.BeforeSendInternal(sentryEvent, null!); +#pragma warning disable 8603 + // returning null is fine - the native binding even has this set, but the tooling doesn't want to obey, so the pragma was necessary + if (result == null) + return null; +#pragma warning restore 8603 + + evt.ServerName = result.ServerName; + evt.Dist = result.Distribution; + evt.Logger = result.Logger; + evt.ReleaseName = result.Release; + evt.Environment = result.Environment; + evt.Platform = result.Platform!; + evt.Transaction = result.TransactionName!; + evt.Message = result.Message?.ToCocoaSentryMessage(); + evt.Tags = result.Tags?.ToNSDictionaryStrings(); + evt.Extra = result.Extra?.ToNSDictionary(); + evt.Breadcrumbs = result.Breadcrumbs?.Select(x => x.ToCocoaBreadcrumb()).ToArray(); + evt.User = result.User?.ToCocoaUser(); + + if (result.Level != null) { - var result = options - .BeforeSendInternal? - .Invoke(sentryEvent, null!)? - .ToCocoaSentryEvent(options); + evt.Level = result.Level.Value.ToCocoaSentryLevel(); + } - // // Note: Nullable result is allowed but delegate is generated incorrectly - // // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 - return result!; + if (result.Exception != null) + { + evt.Error = new NSError(new NSString(result.Exception.ToString()), IntPtr.Zero); } - return evt; + + // Note: Nullable result is allowed but delegate is generated incorrectly + // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 + return evt!; } catch (Exception ex) { From 000b3244c33cfde9257ccedc73fbd9190201941a Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Fri, 21 Feb 2025 10:48:08 -0500 Subject: [PATCH 13/39] Remove unnecessary hacks --- src/Sentry.Bindings.Cocoa/ApiDefinitions.cs | 2 +- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs index 57e18f8790..77ad1d5797 100644 --- a/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs +++ b/src/Sentry.Bindings.Cocoa/ApiDefinitions.cs @@ -27,7 +27,7 @@ namespace Sentry.CocoaSdk; // typedef SentryEvent * _Nullable (^SentryBeforeSendEventCallback)(SentryEvent * _Nonnull); [Internal] -delegate SentryEvent? SentryBeforeSendEventCallback (SentryEvent @event); +delegate SentryEvent SentryBeforeSendEventCallback (SentryEvent @event); // typedef id _Nullable (^SentryBeforeSendSpanCallback)(id _Nonnull); [Internal] diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index e53fb3d016..15d5441bf8 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -135,11 +135,9 @@ private static void InitSentryCocoaSdk(SentryOptions options) return evt; var result = options.BeforeSendInternal(sentryEvent, null!); -#pragma warning disable 8603 // returning null is fine - the native binding even has this set, but the tooling doesn't want to obey, so the pragma was necessary if (result == null) - return null; -#pragma warning restore 8603 + return null!; evt.ServerName = result.ServerName; evt.Dist = result.Distribution; From bdfe3a9c2b609a308c8158151c0d7317702fab58 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Fri, 21 Feb 2025 15:55:06 +0000 Subject: [PATCH 14/39] Format code --- src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs b/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs index 1d53321166..4048161af1 100644 --- a/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs +++ b/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs @@ -263,7 +263,7 @@ public static CocoaSdk.SentryMessage ToCocoaSentryMessage(this SentryMessage msg return native; } - + // not tested or needed yet - leaving for future just in case // public static CocoaSdk.SentryThread ToCocoaSentryThread(this SentryThread thread) // { From bf4d33db66b4295c64c97c483c30924948f06765 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Fri, 21 Feb 2025 11:41:55 -0500 Subject: [PATCH 15/39] Add missing bindable option for native iOS sentryoptions --- src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs b/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs index f8dae26213..5914f33459 100644 --- a/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs +++ b/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs @@ -26,6 +26,7 @@ public class NativeOptions public bool? EnableUIViewControllerTracing { get; set; } public bool? EnableUserInteractionTracing { get; set; } public bool? EnableTracing { get; set; } + public bool? SuppressSegfaults { get; set; } public void ApplyTo(SentryOptions.NativeOptions options) { @@ -47,6 +48,7 @@ public void ApplyTo(SentryOptions.NativeOptions options) options.EnableUIViewControllerTracing = EnableUIViewControllerTracing ?? options.EnableUIViewControllerTracing; options.EnableUserInteractionTracing = EnableUserInteractionTracing ?? options.EnableUserInteractionTracing; options.EnableTracing = EnableTracing ?? options.EnableTracing; + options.SuppressSegfaults = SuppressSegfaults ?? options.SuppressSegfaults; } } } From 52f0699d20e85b25b98d1ca0209bb69269824c01 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 24 Feb 2025 10:09:31 -0500 Subject: [PATCH 16/39] Update SentrySdk.cs --- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 15d5441bf8..4104a75a50 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -135,10 +135,10 @@ private static void InitSentryCocoaSdk(SentryOptions options) return evt; var result = options.BeforeSendInternal(sentryEvent, null!); - // returning null is fine - the native binding even has this set, but the tooling doesn't want to obey, so the pragma was necessary if (result == null) return null!; + // we only support a subset of mutated data to be passed back to the native SDK at this time evt.ServerName = result.ServerName; evt.Dist = result.Distribution; evt.Logger = result.Logger; From dab118edb39b6a8b854dfaab7109356b236ff66e Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 24 Feb 2025 10:10:14 -0500 Subject: [PATCH 17/39] Update SentrySdk.cs --- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 4104a75a50..52a514b4c3 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -35,7 +35,6 @@ private static void InitSentryCocoaSdk(SentryOptions options) nativeOptions.SendDefaultPii = options.SendDefaultPii; nativeOptions.SessionTrackingIntervalMillis = (nuint)options.AutoSessionTrackingInterval.TotalMilliseconds; - // nativeOptions.BeforeSend if (options.Environment is { } environment) { nativeOptions.Environment = environment; From 1f721b8e01833c423021128521461c9131ee59c1 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 24 Feb 2025 10:55:58 -0500 Subject: [PATCH 18/39] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df2c86f412..ec5196fa91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ### Fixes -- Map iOS native events - OnCrashedLastRun and BeforeSend ([#2102](https://github.com/getsentry/sentry-dotnet/pull/3958)) +- Native iOS events are now exposed to the dotnet layer for users to hook through SentryOptions.BeforeSend and SentryOptions.OnCrashedLastRun ([#2102](https://github.com/getsentry/sentry-dotnet/pull/3958)) + ### Features - Serilog scope properties are now sent with Sentry events ([#3976](https://github.com/getsentry/sentry-dotnet/pull/3976)) From 4830f8e968feb6654f0fd6e0c381220791336d8d Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 24 Feb 2025 10:56:23 -0500 Subject: [PATCH 19/39] Update CHANGELOG.md --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec5196fa91..89497da2e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,9 @@ ## Unreleased -### Fixes - -- Native iOS events are now exposed to the dotnet layer for users to hook through SentryOptions.BeforeSend and SentryOptions.OnCrashedLastRun ([#2102](https://github.com/getsentry/sentry-dotnet/pull/3958)) - ### Features +- Native iOS events are now exposed to the dotnet layer for users to hook through SentryOptions.BeforeSend and SentryOptions.OnCrashedLastRun ([#2102](https://github.com/getsentry/sentry-dotnet/pull/3958)) - Serilog scope properties are now sent with Sentry events ([#3976](https://github.com/getsentry/sentry-dotnet/pull/3976)) - The sample seed used for sampling decisions is now propagated, for use in downstream custom trace samplers ([#3951](https://github.com/getsentry/sentry-dotnet/pull/3951)) From b8b08cc93c549190736fea93c828bc0fede440d8 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 24 Feb 2025 13:20:41 -0500 Subject: [PATCH 20/39] Unit tests mapping checks to/from native iOS --- .../Cocoa/Extensions/CocoaExtensions.cs | 26 +++++ src/Sentry/Platforms/Cocoa/SentrySdk.cs | 23 +--- .../Platforms/iOS/NativeSerializationTests.cs | 106 ++++++++++++++++++ 3 files changed, 133 insertions(+), 22 deletions(-) create mode 100644 test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs diff --git a/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs b/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs index 4048161af1..2f0cddcd17 100644 --- a/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs +++ b/src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs @@ -243,6 +243,32 @@ public static object ToObject(this NSNumber n) }; } + public static void CopyToCocoaSentryEvent(this SentryEvent managed, CocoaSdk.SentryEvent native) + { + // we only support a subset of mutated data to be passed back to the native SDK at this time + native.ServerName = managed.ServerName; + native.Dist = managed.Distribution; + native.Logger = managed.Logger; + native.ReleaseName = managed.Release; + native.Environment = managed.Environment; + native.Platform = managed.Platform!; + native.Transaction = managed.TransactionName!; + native.Message = managed.Message?.ToCocoaSentryMessage(); + native.Tags = managed.Tags?.ToNSDictionaryStrings(); + native.Extra = managed.Extra?.ToNSDictionary(); + native.Breadcrumbs = managed.Breadcrumbs?.Select(x => x.ToCocoaBreadcrumb()).ToArray(); + native.User = managed.User?.ToCocoaUser(); + + if (managed.Level != null) + { + native.Level = managed.Level.Value.ToCocoaSentryLevel(); + } + + if (managed.Exception != null) + { + native.Error = new NSError(new NSString(managed.Exception.ToString()), IntPtr.Zero); + } + } public static SentryEvent? ToSentryEvent(this CocoaSdk.SentryEvent sentryEvent) { diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 52a514b4c3..00a07b8c05 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -138,28 +138,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) return null!; // we only support a subset of mutated data to be passed back to the native SDK at this time - evt.ServerName = result.ServerName; - evt.Dist = result.Distribution; - evt.Logger = result.Logger; - evt.ReleaseName = result.Release; - evt.Environment = result.Environment; - evt.Platform = result.Platform!; - evt.Transaction = result.TransactionName!; - evt.Message = result.Message?.ToCocoaSentryMessage(); - evt.Tags = result.Tags?.ToNSDictionaryStrings(); - evt.Extra = result.Extra?.ToNSDictionary(); - evt.Breadcrumbs = result.Breadcrumbs?.Select(x => x.ToCocoaBreadcrumb()).ToArray(); - evt.User = result.User?.ToCocoaUser(); - - if (result.Level != null) - { - evt.Level = result.Level.Value.ToCocoaSentryLevel(); - } - - if (result.Exception != null) - { - evt.Error = new NSError(new NSString(result.Exception.ToString()), IntPtr.Zero); - } + result.CopyToCocoaSentryEvent(evt); // Note: Nullable result is allowed but delegate is generated incorrectly // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 diff --git a/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs b/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs new file mode 100644 index 0000000000..cf2ab8241e --- /dev/null +++ b/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Foundation; +using Sentry.Cocoa.Extensions; +using Xunit; + +namespace Sentry.Tests.Platforms.iOS; + +public class NativeSerializationTests +{ + [Fact] + public void Managed_To_Native() + { + var evt = new SentryEvent(new Exception("Test Exception")); + + evt.Level = SentryLevel.Debug; + evt.ServerName = "test server name"; + evt.Distribution = "test distribution"; + evt.Logger = "test logger"; + evt.Release = "test release"; + evt.Environment = "test environment"; + evt.Platform = "test platform"; + evt.TransactionName = "test transaction name"; + evt.Message = new SentryMessage { Params = ["Test"] }; + evt.SetTag("TestTagKey", "TestTagValue"); + evt.AddBreadcrumb(new Breadcrumb(DateTimeOffset.Now, "test breadcrumb", "test type")); + evt.SetExtra("TestExtraKey", "TestExtraValue"); + evt.User = new SentryUser + { + Id = "user id", + Username = "test", + Email = "test@sentry.io", + IpAddress = "127.0.0.1" + }; + + var native = new CocoaSdk.SentryEvent(); + evt.CopyToCocoaSentryEvent(native); + + AssertEqual(evt, native); + } + + + [Fact] + public void Native_To_Managed() + { + var native = new CocoaSdk.SentryEvent(); + + native.ServerName = "native server name"; + native.Dist = "native dist"; + native.Logger = "native logger"; + native.ReleaseName = "native release"; + native.Environment = "native env"; + native.Platform = "native platform"; + native.Transaction = "native transaction"; + native.Message = new SentryMessage { Params = ["Test"] }.ToCocoaSentryMessage(); + native.Tags = new Dictionary {{ "TestTagKey", "TestTagValue"}}.ToNSDictionaryStrings(); + native.Extra = new Dictionary {{ "TestExtraKey", "TestExtraValue"}}.ToNSDictionaryStrings(); + native.Breadcrumbs = + [ + new CocoaSdk.SentryBreadcrumb(CocoaSdk.SentryLevel.Debug, "category") + ]; + native.User = new CocoaSdk.SentryUser + { + UserId = "user id", + Username = "test", + Email = "test@sentry.io", + IpAddress = "127.0.0.1" + }; + var managed = native.ToSentryEvent(); + AssertEqual(managed, native); + } + + static void AssertEqual(SentryEvent managed, CocoaSdk.SentryEvent native) + { + native.ServerName.Should().Be(managed.ServerName); + native.Dist.Should().Be(managed.Distribution); + native.Logger.Should().Be(managed.Logger); + native.ReleaseName.Should().Be(managed.Release); + native.Environment.Should().Be(managed.Environment); + native.Platform.Should().Be(managed.Platform!); + native.Transaction.Should().Be(managed.TransactionName!); + + native.Message.Params.First().Should().Be(managed.Message.Params.First()); + + native.Extra.Keys[0].Should().Be(new NSString(managed.Extra.Keys.First())); + native.Extra.Values[0].Should().Be(NSObject.FromObject(managed.Extra.Values.First())); + + native.Tags.Keys[0].Should().Be(new NSString(managed.Tags.Keys.First())); + native.Tags.Values[0].Should().Be(new NSString(managed.Tags.Values.First())); + + var nb = native.Breadcrumbs.First(); + var mb = managed.Breadcrumbs.First(); + nb.Message.Should().Be(mb.Message); + nb.Type.Should().Be(mb.Type); + + native.User.UserId.Should().Be(managed.User.Id); + native.User.Email.Should().Be(managed.User.Email); + native.User.Username.Should().Be(managed.User.Username); + native.User.IpAddress.Should().Be(managed.User.IpAddress); + + native.Level.ToString().Equals(managed.Level.ToString()); + + native.Error.Domain.Should().Be(managed.Exception.ToString()); + } +} From a46bd43b2b3f1fda2c124de1c6c9c735e6d33106 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Mon, 24 Feb 2025 18:36:23 +0000 Subject: [PATCH 21/39] Format code --- test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs b/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs index cf2ab8241e..f44bba0ec4 100644 --- a/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs +++ b/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs @@ -54,8 +54,8 @@ public void Native_To_Managed() native.Platform = "native platform"; native.Transaction = "native transaction"; native.Message = new SentryMessage { Params = ["Test"] }.ToCocoaSentryMessage(); - native.Tags = new Dictionary {{ "TestTagKey", "TestTagValue"}}.ToNSDictionaryStrings(); - native.Extra = new Dictionary {{ "TestExtraKey", "TestExtraValue"}}.ToNSDictionaryStrings(); + native.Tags = new Dictionary { { "TestTagKey", "TestTagValue" } }.ToNSDictionaryStrings(); + native.Extra = new Dictionary { { "TestExtraKey", "TestExtraValue" } }.ToNSDictionaryStrings(); native.Breadcrumbs = [ new CocoaSdk.SentryBreadcrumb(CocoaSdk.SentryLevel.Debug, "category") @@ -71,7 +71,7 @@ public void Native_To_Managed() AssertEqual(managed, native); } - static void AssertEqual(SentryEvent managed, CocoaSdk.SentryEvent native) + private static void AssertEqual(SentryEvent managed, CocoaSdk.SentryEvent native) { native.ServerName.Should().Be(managed.ServerName); native.Dist.Should().Be(managed.Distribution); From 5dca9e23d1d4170163b2cacbe1d9e9ff9b7137ce Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 24 Feb 2025 14:41:58 -0500 Subject: [PATCH 22/39] Update NativeSerializationTests.cs --- test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs b/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs index f44bba0ec4..139abc568f 100644 --- a/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs +++ b/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs @@ -24,7 +24,7 @@ public void Managed_To_Native() evt.TransactionName = "test transaction name"; evt.Message = new SentryMessage { Params = ["Test"] }; evt.SetTag("TestTagKey", "TestTagValue"); - evt.AddBreadcrumb(new Breadcrumb(DateTimeOffset.Now, "test breadcrumb", "test type")); + evt.AddBreadcrumb(new Breadcrumb("test breadcrumb", "test type")); evt.SetExtra("TestExtraKey", "TestExtraValue"); evt.User = new SentryUser { @@ -55,7 +55,7 @@ public void Native_To_Managed() native.Transaction = "native transaction"; native.Message = new SentryMessage { Params = ["Test"] }.ToCocoaSentryMessage(); native.Tags = new Dictionary { { "TestTagKey", "TestTagValue" } }.ToNSDictionaryStrings(); - native.Extra = new Dictionary { { "TestExtraKey", "TestExtraValue" } }.ToNSDictionaryStrings(); + native.Extra = new Dictionary { { "TestExtraKey", "TestExtraValue" } }.ToNSDictionary(); native.Breadcrumbs = [ new CocoaSdk.SentryBreadcrumb(CocoaSdk.SentryLevel.Debug, "category") @@ -81,7 +81,7 @@ private static void AssertEqual(SentryEvent managed, CocoaSdk.SentryEvent native native.Platform.Should().Be(managed.Platform!); native.Transaction.Should().Be(managed.TransactionName!); - native.Message.Params.First().Should().Be(managed.Message.Params.First()); + native.Message.Params.First().Should().Be(managed.Message.Params.First().ToString()); native.Extra.Keys[0].Should().Be(new NSString(managed.Extra.Keys.First())); native.Extra.Values[0].Should().Be(NSObject.FromObject(managed.Extra.Values.First())); From 00cbc04c73a9d5e835a6a88f248d8f7f4faea072 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 24 Feb 2025 14:43:09 -0500 Subject: [PATCH 23/39] Revert "Add missing bindable option for native iOS sentryoptions" This reverts commit bf4d33db66b4295c64c97c483c30924948f06765. --- src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs b/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs index 5914f33459..f8dae26213 100644 --- a/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs +++ b/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs @@ -26,7 +26,6 @@ public class NativeOptions public bool? EnableUIViewControllerTracing { get; set; } public bool? EnableUserInteractionTracing { get; set; } public bool? EnableTracing { get; set; } - public bool? SuppressSegfaults { get; set; } public void ApplyTo(SentryOptions.NativeOptions options) { @@ -48,7 +47,6 @@ public void ApplyTo(SentryOptions.NativeOptions options) options.EnableUIViewControllerTracing = EnableUIViewControllerTracing ?? options.EnableUIViewControllerTracing; options.EnableUserInteractionTracing = EnableUserInteractionTracing ?? options.EnableUserInteractionTracing; options.EnableTracing = EnableTracing ?? options.EnableTracing; - options.SuppressSegfaults = SuppressSegfaults ?? options.SuppressSegfaults; } } } From d2776e1600d8fa97b4a9f943df55f6acaa592f3f Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 24 Feb 2025 14:45:42 -0500 Subject: [PATCH 24/39] Remove iOS native event suppress configuration --- src/Sentry/Platforms/Cocoa/SentryOptions.cs | 16 ---------------- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 2 +- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/Sentry/Platforms/Cocoa/SentryOptions.cs b/src/Sentry/Platforms/Cocoa/SentryOptions.cs index f1d346eccd..91172d9938 100644 --- a/src/Sentry/Platforms/Cocoa/SentryOptions.cs +++ b/src/Sentry/Platforms/Cocoa/SentryOptions.cs @@ -197,22 +197,6 @@ internal NativeOptions(SentryOptions options) /// public NSUrlSessionDelegate? UrlSessionDelegate { get; set; } = null; - /// - /// - /// Whether to suppress capturing SIGSEGV (Segfault) errors in the Native SDK. - /// - /// - /// When managed code results in a NullReferenceException, this also causes a SIGSEGV (Segfault). Duplicate - /// events (one managed and one native) can be prevented by suppressing native Segfaults, which may be - /// convenient. - /// - /// - /// Enabling this may prevent the capture of Segfault originating from native (not managed) code... so it may - /// prevent the capture of genuine native Segfault errors. - /// - /// - public bool SuppressSegfaults { get; set; } = false; - // ---------- Other ---------- /// diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 00a07b8c05..8908cd78c0 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -93,7 +93,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. // There should only be one exception on the event in this case - if (options.Native.SuppressSegfaults && evt.Exceptions?.Length == 1) + if (evt.Exceptions?.Length == 1) { // It will match the following characteristics var ex = evt.Exceptions[0]; From 4be3abb2b4f142c1b69d5ef50892585eefe81541 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 24 Feb 2025 14:48:45 -0500 Subject: [PATCH 25/39] Ability to suppress native errors --- .../Platforms/Cocoa/BindableSentryOptions.cs | 2 ++ src/Sentry/Platforms/Cocoa/SentryOptions.cs | 16 ++++++++++++++++ src/Sentry/Platforms/Cocoa/SentrySdk.cs | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs b/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs index f8dae26213..5914f33459 100644 --- a/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs +++ b/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs @@ -26,6 +26,7 @@ public class NativeOptions public bool? EnableUIViewControllerTracing { get; set; } public bool? EnableUserInteractionTracing { get; set; } public bool? EnableTracing { get; set; } + public bool? SuppressSegfaults { get; set; } public void ApplyTo(SentryOptions.NativeOptions options) { @@ -47,6 +48,7 @@ public void ApplyTo(SentryOptions.NativeOptions options) options.EnableUIViewControllerTracing = EnableUIViewControllerTracing ?? options.EnableUIViewControllerTracing; options.EnableUserInteractionTracing = EnableUserInteractionTracing ?? options.EnableUserInteractionTracing; options.EnableTracing = EnableTracing ?? options.EnableTracing; + options.SuppressSegfaults = SuppressSegfaults ?? options.SuppressSegfaults; } } } diff --git a/src/Sentry/Platforms/Cocoa/SentryOptions.cs b/src/Sentry/Platforms/Cocoa/SentryOptions.cs index 91172d9938..f1d346eccd 100644 --- a/src/Sentry/Platforms/Cocoa/SentryOptions.cs +++ b/src/Sentry/Platforms/Cocoa/SentryOptions.cs @@ -197,6 +197,22 @@ internal NativeOptions(SentryOptions options) /// public NSUrlSessionDelegate? UrlSessionDelegate { get; set; } = null; + /// + /// + /// Whether to suppress capturing SIGSEGV (Segfault) errors in the Native SDK. + /// + /// + /// When managed code results in a NullReferenceException, this also causes a SIGSEGV (Segfault). Duplicate + /// events (one managed and one native) can be prevented by suppressing native Segfaults, which may be + /// convenient. + /// + /// + /// Enabling this may prevent the capture of Segfault originating from native (not managed) code... so it may + /// prevent the capture of genuine native Segfault errors. + /// + /// + public bool SuppressSegfaults { get; set; } = false; + // ---------- Other ---------- /// diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 8908cd78c0..00a07b8c05 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -93,7 +93,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. // There should only be one exception on the event in this case - if (evt.Exceptions?.Length == 1) + if (options.Native.SuppressSegfaults && evt.Exceptions?.Length == 1) { // It will match the following characteristics var ex = evt.Exceptions[0]; From fc85bce59d9b0328c11fd369a3c64f8ab65fd152 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 24 Feb 2025 14:53:30 -0500 Subject: [PATCH 26/39] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89497da2e7..445dae267e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Features +- Using SentryOptions.SuppressSegfaults, users can now block duplicate errors from native due to dotnet NullReferenceExceptions - Defaults to false ([#3998](https://github.com/getsentry/sentry-dotnet/pull/3998)) - Native iOS events are now exposed to the dotnet layer for users to hook through SentryOptions.BeforeSend and SentryOptions.OnCrashedLastRun ([#2102](https://github.com/getsentry/sentry-dotnet/pull/3958)) - Serilog scope properties are now sent with Sentry events ([#3976](https://github.com/getsentry/sentry-dotnet/pull/3976)) - The sample seed used for sampling decisions is now propagated, for use in downstream custom trace samplers ([#3951](https://github.com/getsentry/sentry-dotnet/pull/3951)) From 5e8134ac09b9ff6413085430eaa90aa1a45a7553 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 24 Feb 2025 16:04:19 -0500 Subject: [PATCH 27/39] Update CHANGELOG.md --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89497da2e7..4dacd3c289 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,8 +26,6 @@ - Native SIGSEGV errors resulting from managed NullReferenceExceptions are now suppressed on Android ([#3903](https://github.com/getsentry/sentry-dotnet/pull/3903)) - OTel activities that are marked as not recorded are no longer sent to Sentry ([#3890](https://github.com/getsentry/sentry-dotnet/pull/3890)) - Fixed envelopes with oversized attachments getting stuck in __processing ([#3938](https://github.com/getsentry/sentry-dotnet/pull/3938)) -- Unknown stack frames in profiles on .NET 8+ ([#3942](https://github.com/getsentry/sentry-dotnet/pull/3942)) -- Deduplicate profiling stack frames ([#3941](https://github.com/getsentry/sentry-dotnet/pull/3941)) - OperatingSystem will now return macOS as OS name instead of 'Darwin' as well as the proper version. ([#2710](https://github.com/getsentry/sentry-dotnet/pull/3956)) - Ignore null value on CocoaScopeObserver.SetTag ([#3948](https://github.com/getsentry/sentry-dotnet/pull/3948)) - Deduplicate profiling stack frames ([#3969](https://github.com/getsentry/sentry-dotnet/pull/3969)) From d8ac6ea62d1ad938fbc6f4c9cfddfb2ef7281762 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 25 Feb 2025 12:04:26 -0500 Subject: [PATCH 28/39] Rename tests according to review --- ...{NativeSerializationTests.cs => CocoaExtensionsTests.cs} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename test/Sentry.Tests/Platforms/iOS/{NativeSerializationTests.cs => CocoaExtensionsTests.cs} (96%) diff --git a/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs b/test/Sentry.Tests/Platforms/iOS/CocoaExtensionsTests.cs similarity index 96% rename from test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs rename to test/Sentry.Tests/Platforms/iOS/CocoaExtensionsTests.cs index 139abc568f..496c9b0a3d 100644 --- a/test/Sentry.Tests/Platforms/iOS/NativeSerializationTests.cs +++ b/test/Sentry.Tests/Platforms/iOS/CocoaExtensionsTests.cs @@ -7,10 +7,10 @@ namespace Sentry.Tests.Platforms.iOS; -public class NativeSerializationTests +public class CocoaExtensionsTests { [Fact] - public void Managed_To_Native() + public void CopyToCocoaSentryEvent_CopiesProperties() { var evt = new SentryEvent(new Exception("Test Exception")); @@ -42,7 +42,7 @@ public void Managed_To_Native() [Fact] - public void Native_To_Managed() + public void ToSentryEvent_ConvertToManaged() { var native = new CocoaSdk.SentryEvent(); From 63326ecb9c23501bf192da6b40dc56b8ecfc8ab4 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 25 Feb 2025 13:53:57 -0500 Subject: [PATCH 29/39] add timestamp for deserialize test --- test/Sentry.Tests/Platforms/iOS/CocoaExtensionsTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Sentry.Tests/Platforms/iOS/CocoaExtensionsTests.cs b/test/Sentry.Tests/Platforms/iOS/CocoaExtensionsTests.cs index 496c9b0a3d..6860268854 100644 --- a/test/Sentry.Tests/Platforms/iOS/CocoaExtensionsTests.cs +++ b/test/Sentry.Tests/Platforms/iOS/CocoaExtensionsTests.cs @@ -46,6 +46,7 @@ public void ToSentryEvent_ConvertToManaged() { var native = new CocoaSdk.SentryEvent(); + native.Timestamp = DateTimeOffset.UtcNow.ToNSDate(); native.ServerName = "native server name"; native.Dist = "native dist"; native.Logger = "native logger"; From d1feffc1aba688645f60d62fed549bb6e5527f38 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Wed, 26 Feb 2025 10:56:34 -0500 Subject: [PATCH 30/39] Account for fractions of seconds --- src/Sentry/Internal/Extensions/JsonExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Sentry/Internal/Extensions/JsonExtensions.cs b/src/Sentry/Internal/Extensions/JsonExtensions.cs index ab245673e9..8bc25eba83 100644 --- a/src/Sentry/Internal/Extensions/JsonExtensions.cs +++ b/src/Sentry/Internal/Extensions/JsonExtensions.cs @@ -227,7 +227,8 @@ public static void Deconstruct(this JsonProperty jsonProperty, out string name, { if (dtRaw.Value.ValueKind == JsonValueKind.Number) { - result = DateTimeOffset.FromUnixTimeSeconds(dtRaw.Value.GetInt64()); + var epoch = Convert.ToInt64(dtRaw.Value.GetDouble()); + result = DateTimeOffset.FromUnixTimeSeconds(epoch); } else { From 9270d50b2a364444f2ee317776a79536ab80a2d1 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Wed, 26 Feb 2025 11:41:41 -0500 Subject: [PATCH 31/39] Add unit tests for suppress segfaults and beforesend --- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 131 ++++++++++++------------ test/Sentry.Tests/SentrySdkTests.cs | 88 ++++++++++++++++ 2 files changed, 155 insertions(+), 64 deletions(-) diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 00a07b8c05..6e829c3e36 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -86,70 +86,8 @@ private static void InitSentryCocoaSdk(SentryOptions options) } } - nativeOptions.BeforeSend = evt => - { - // When we have an unhandled managed exception, we send that to Sentry twice - once managed and once native. - // The managed exception is what a .NET developer would expect, and it is sent by the Sentry.NET SDK - // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. - - // There should only be one exception on the event in this case - if (options.Native.SuppressSegfaults && evt.Exceptions?.Length == 1) - { - // It will match the following characteristics - var ex = evt.Exceptions[0]; - - // Thankfully, sometimes we can see Xamarin's unhandled exception handler on the stack trace, so we can filter - // them out. Here is the function that calls abort(), which we will use as a filter: - // https://github.com/xamarin/xamarin-macios/blob/c55fbdfef95028ba03d0f7a35aebca03bd76f852/runtime/runtime.m#L1114-L1122 - if (ex.Type == "SIGABRT" && ex.Value == "Signal 6, Code 0" && - ex.Stacktrace?.Frames.Any(f => f.Function == "xamarin_unhandled_exception_handler") is true) - { - // Don't send it - options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, - ex.Value); - return null!; - } - - // Similar workaround for NullReferenceExceptions. We don't have any easy way to know whether the - // exception is managed code (compiled to native) or original native code though. - // See: https://github.com/getsentry/sentry-dotnet/issues/3776 - if (ex.Type == "EXC_BAD_ACCESS") - { - // Don't send it - options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, - ex.Value); - return null!; - } - } - - // we run our SIGABRT checks first before handing over to user events - // because we delegate to user code, we need to protect anything that could happen in this event - if (options.BeforeSendInternal == null) - return evt; - - try - { - var sentryEvent = evt.ToSentryEvent(); - if (sentryEvent == null) - return evt; - - var result = options.BeforeSendInternal(sentryEvent, null!); - if (result == null) - return null!; - - // we only support a subset of mutated data to be passed back to the native SDK at this time - result.CopyToCocoaSentryEvent(evt); - - // Note: Nullable result is allowed but delegate is generated incorrectly - // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 - return evt!; - } - catch (Exception ex) - { - options.LogError(ex, "Before Send Error"); - return evt; - } - }; + // HACK: generated native binding does not set nullable, but null is an acceptable return value here + nativeOptions.BeforeSend = evt => ProcessOnBeforeSend(options, evt)!; if (options.OnCrashedLastRun is { } onCrashedLastRun) { @@ -250,4 +188,69 @@ private static CocoaSdk.SentryHttpStatusCodeRange[] GetFailedRequestStatusCodes( return nativeRanges; } + + internal static CocoaSdk.SentryEvent? ProcessOnBeforeSend(SentryOptions options, CocoaSdk.SentryEvent evt) + { + // When we have an unhandled managed exception, we send that to Sentry twice - once managed and once native. + // The managed exception is what a .NET developer would expect, and it is sent by the Sentry.NET SDK + // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. + + // There should only be one exception on the event in this case + if (options.Native.SuppressSegfaults && evt.Exceptions?.Length == 1) + { + // It will match the following characteristics + var ex = evt.Exceptions[0]; + + // Thankfully, sometimes we can see Xamarin's unhandled exception handler on the stack trace, so we can filter + // them out. Here is the function that calls abort(), which we will use as a filter: + // https://github.com/xamarin/xamarin-macios/blob/c55fbdfef95028ba03d0f7a35aebca03bd76f852/runtime/runtime.m#L1114-L1122 + if (ex.Type == "SIGABRT" && ex.Value == "Signal 6, Code 0" && + ex.Stacktrace?.Frames.Any(f => f.Function == "xamarin_unhandled_exception_handler") is true) + { + // Don't send it + options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, + ex.Value); + return null!; + } + + // Similar workaround for NullReferenceExceptions. We don't have any easy way to know whether the + // exception is managed code (compiled to native) or original native code though. + // See: https://github.com/getsentry/sentry-dotnet/issues/3776 + if (ex.Type == "EXC_BAD_ACCESS") + { + // Don't send it + options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, + ex.Value); + return null!; + } + } + + // we run our SIGABRT checks first before handing over to user events + // because we delegate to user code, we need to protect anything that could happen in this event + if (options.BeforeSendInternal == null) + return evt; + + try + { + var sentryEvent = evt.ToSentryEvent(); + if (sentryEvent == null) + return evt; + + var result = options.BeforeSendInternal(sentryEvent, null!); + if (result == null) + return null!; + + // we only support a subset of mutated data to be passed back to the native SDK at this time + result.CopyToCocoaSentryEvent(evt); + + // Note: Nullable result is allowed but delegate is generated incorrectly + // See https://github.com/xamarin/xamarin-macios/issues/15299#issuecomment-1201863294 + return evt!; + } + catch (Exception ex) + { + options.LogError(ex, "Before Send Error"); + return evt; + } + } } diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index 4f8a45fec6..b85980a530 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -1,5 +1,17 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using NSubstitute; +using Sentry.Extensibility; +using Sentry.Internal; using Sentry.Internal.Http; using Sentry.Internal.ScopeStack; +using Sentry.Protocol.Envelopes; +using Sentry.Testing; +using Xunit; +using Xunit.Abstractions; using static Sentry.Internal.Constants; namespace Sentry.Tests; @@ -831,6 +843,82 @@ public void InitHub_DebugEnabled_DebugLogsLogged() Arg.Any()); } + #if __IOS__ + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ProcessOnBeforeSend_SegfaultsSuppressed(bool suppressSegfaults) + { + var options = new SentryOptions + { + Dsn = ValidDsn, + DiagnosticLogger = _logger, + IsGlobalModeEnabled = true, + Debug = true, + AutoSessionTracking = false, + BackgroundWorker = Substitute.For(), + InitNativeSdks = false + }; + options.Native.SuppressSegfaults = suppressSegfaults; + + var evt = new Sentry.CocoaSdk.SentryEvent(); + var ex = new Sentry.CocoaSdk.SentryException("Signal 6, Code 0", "SIGABRT"); + var st = new Sentry.CocoaSdk.SentryStacktrace( + [new Sentry.CocoaSdk.SentryFrame + { + Function = "xamarin_unhandled_exception_handler" + }], + new Foundation.NSDictionary() + ); + ex.Stacktrace = st; + evt.Exceptions = [ex]; + var result = SentrySdk.ProcessOnBeforeSend(options, evt); + + if (suppressSegfaults) + { + result.Should().BeNull(); + } + else + { + result.Should().NotBeNull(); + result.Should.Exceptions.First().Type.Should().Be("SIGABRT"); + } + } + + [Fact] + public void ProcessOnBeforeSend_OptionsBeforeOnSendRuns() + { + var options = new SentryOptions + { + Dsn = ValidDsn, + DiagnosticLogger = _logger, + IsGlobalModeEnabled = true, + Debug = true, + AutoSessionTracking = false, + BackgroundWorker = Substitute.For(), + InitNativeSdks = false + }; + + var native = new Sentry.CocoaSdk.SentryEvent(); + native.ServerName = "server name"; + native.Dist = "dist"; + native.Logger = "logger"; + native.ReleaseName = "release name"; + native.Environment = "environment"; + native.Platform = "platform"; + native.Transaction = "transaction name"; + + options.SetBeforeSend(e => + { + e.Platform = "dotnet"; + return e; + }); + var result = SentrySdk.ProcessOnBeforeSend(options, native); + result.Should().NotBeNull(); + result.Platform.Should().Be("dotnet"); + } + #endif + public void Dispose() { SentrySdk.Close(); From 92d38b6a9ab2dae0f8cd01de3dda947da504ebdd Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Wed, 26 Feb 2025 17:07:33 +0000 Subject: [PATCH 32/39] Format code --- test/Sentry.Tests/SentrySdkTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index b85980a530..6d1d7cf224 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -843,7 +843,7 @@ public void InitHub_DebugEnabled_DebugLogsLogged() Arg.Any()); } - #if __IOS__ +#if __IOS__ [Theory] [InlineData(true)] [InlineData(false)] @@ -917,7 +917,7 @@ public void ProcessOnBeforeSend_OptionsBeforeOnSendRuns() result.Should().NotBeNull(); result.Platform.Should().Be("dotnet"); } - #endif +#endif public void Dispose() { From 30b3bd3ae7d49b9787b0e66e55d0971d8a2b56c0 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Wed, 26 Feb 2025 14:54:50 -0500 Subject: [PATCH 33/39] Update SentrySdkTests.cs --- test/Sentry.Tests/SentrySdkTests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index 6d1d7cf224..05925578c4 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -859,6 +859,11 @@ public void ProcessOnBeforeSend_SegfaultsSuppressed(bool suppressSegfaults) BackgroundWorker = Substitute.For(), InitNativeSdks = false }; + options.SetBeforeSend(e => + { + // we return a result for suppress segfaults because it expects null and null for the opposite case + return suppressSegfaults ? e : null; + }); options.Native.SuppressSegfaults = suppressSegfaults; var evt = new Sentry.CocoaSdk.SentryEvent(); @@ -881,7 +886,7 @@ [new Sentry.CocoaSdk.SentryFrame else { result.Should().NotBeNull(); - result.Should.Exceptions.First().Type.Should().Be("SIGABRT"); + result.Exceptions.First().Type.Should().Be("SIGABRT"); } } From de9afa5e63ec552d27cb6f3c1e589f165337aa82 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 3 Mar 2025 17:42:37 -0500 Subject: [PATCH 34/39] Update CHANGELOG.md --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ca6bed4be..73306a4595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +### Fixes + +- Using SentryOptions.SuppressSegfaults, users can now block duplicate errors from native due to dotnet NullReferenceExceptions - Defaults to false ([#3998](https://github.com/getsentry/sentry-dotnet/pull/3998)) +- Native iOS events are now exposed to the dotnet layer for users to hook through SentryOptions.BeforeSend and SentryOptions.OnCrashedLastRun ([#2102](https://github.com/getsentry/sentry-dotnet/pull/3958)) + ### Dependencies - Bump Native SDK from v0.8.0 to v0.8.1 ([#4014](https://github.com/getsentry/sentry-dotnet/pull/4014)) @@ -12,8 +17,6 @@ ### Features -- Using SentryOptions.SuppressSegfaults, users can now block duplicate errors from native due to dotnet NullReferenceExceptions - Defaults to false ([#3998](https://github.com/getsentry/sentry-dotnet/pull/3998)) -- Native iOS events are now exposed to the dotnet layer for users to hook through SentryOptions.BeforeSend and SentryOptions.OnCrashedLastRun ([#2102](https://github.com/getsentry/sentry-dotnet/pull/3958)) - Users can now register their own MAUI controls for breadcrumb creation ([#3997](https://github.com/getsentry/sentry-dotnet/pull/3997)) - Serilog scope properties are now sent with Sentry events ([#3976](https://github.com/getsentry/sentry-dotnet/pull/3976)) - The sample seed used for sampling decisions is now propagated, for use in downstream custom trace samplers ([#3951](https://github.com/getsentry/sentry-dotnet/pull/3951)) From 599185fc47885da69a5c5547b929d98b1bfc6b90 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 4 Mar 2025 14:33:17 -0500 Subject: [PATCH 35/39] Update SentrySdkTests.cs --- test/Sentry.Tests/SentrySdkTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index 05925578c4..8dddd45ccc 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -857,8 +857,9 @@ public void ProcessOnBeforeSend_SegfaultsSuppressed(bool suppressSegfaults) Debug = true, AutoSessionTracking = false, BackgroundWorker = Substitute.For(), - InitNativeSdks = false + InitNativeSdks = false, }; + options.Native.SuppressSegfaults = suppressSegfaults; options.SetBeforeSend(e => { // we return a result for suppress segfaults because it expects null and null for the opposite case @@ -910,17 +911,16 @@ public void ProcessOnBeforeSend_OptionsBeforeOnSendRuns() native.Logger = "logger"; native.ReleaseName = "release name"; native.Environment = "environment"; - native.Platform = "platform"; native.Transaction = "transaction name"; options.SetBeforeSend(e => { - e.Platform = "dotnet"; + e.TransactionName = "dotnet"; return e; }); var result = SentrySdk.ProcessOnBeforeSend(options, native); result.Should().NotBeNull(); - result.Platform.Should().Be("dotnet"); + result.Transaction.Should().Be("dotnet"); } #endif From 3da967a6144ad483043225965aed129c8ec0d209 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 4 Mar 2025 14:46:14 -0500 Subject: [PATCH 36/39] Updating additional changes requested from code review --- .../Platforms/Cocoa/BindableSentryOptions.cs | 6 ++-- src/Sentry/Platforms/Cocoa/SentryOptions.cs | 28 +++++++++++++++---- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 6 ++-- test/Sentry.Tests/SentrySdkTests.cs | 19 ++++--------- 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs b/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs index 5914f33459..edc529dc86 100644 --- a/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs +++ b/src/Sentry/Platforms/Cocoa/BindableSentryOptions.cs @@ -26,7 +26,8 @@ public class NativeOptions public bool? EnableUIViewControllerTracing { get; set; } public bool? EnableUserInteractionTracing { get; set; } public bool? EnableTracing { get; set; } - public bool? SuppressSegfaults { get; set; } + public bool? SuppressSignalAborts { get; set; } + public bool? SuppressExcBadAccess { get; set; } public void ApplyTo(SentryOptions.NativeOptions options) { @@ -48,7 +49,8 @@ public void ApplyTo(SentryOptions.NativeOptions options) options.EnableUIViewControllerTracing = EnableUIViewControllerTracing ?? options.EnableUIViewControllerTracing; options.EnableUserInteractionTracing = EnableUserInteractionTracing ?? options.EnableUserInteractionTracing; options.EnableTracing = EnableTracing ?? options.EnableTracing; - options.SuppressSegfaults = SuppressSegfaults ?? options.SuppressSegfaults; + options.SuppressSignalAborts = SuppressSignalAborts ?? options.SuppressSignalAborts; + options.SuppressExcBadAccess = SuppressExcBadAccess ?? options.SuppressExcBadAccess; } } } diff --git a/src/Sentry/Platforms/Cocoa/SentryOptions.cs b/src/Sentry/Platforms/Cocoa/SentryOptions.cs index f1d346eccd..21ead2c413 100644 --- a/src/Sentry/Platforms/Cocoa/SentryOptions.cs +++ b/src/Sentry/Platforms/Cocoa/SentryOptions.cs @@ -199,19 +199,35 @@ internal NativeOptions(SentryOptions options) /// /// - /// Whether to suppress capturing SIGSEGV (Segfault) errors in the Native SDK. + /// Whether to suppress capturing SIGABRT errors in the Native SDK. /// /// - /// When managed code results in a NullReferenceException, this also causes a SIGSEGV (Segfault). Duplicate - /// events (one managed and one native) can be prevented by suppressing native Segfaults, which may be + /// When managed code results in a NullReferenceException, this also causes a SIGABRT. Duplicate + /// events (one managed and one native) can be prevented by suppressing native SIGABRT, which may be /// convenient. /// /// - /// Enabling this may prevent the capture of Segfault originating from native (not managed) code... so it may - /// prevent the capture of genuine native Segfault errors. + /// Enabling this may prevent the capture of SIGABRT originating from native (not managed) code... so it may + /// prevent the capture of genuine native SIGABRT errors. /// /// - public bool SuppressSegfaults { get; set; } = false; + public bool SuppressSignalAborts { get; set; } = false; + + /// + /// + /// Whether to suppress capturing EXC_BAD_ACCESS errors in the Native SDK. + /// + /// + /// When managed code results in a NullReferenceException, this also causes a EXC_BAD_ACCESS. Duplicate + /// events (one managed and one native) can be prevented by suppressing native EXC_BAD_ACCESS, which may be + /// convenient. + /// + /// + /// Enabling this may prevent the capture of EXC_BAD_ACCESS originating from native (not managed) code... so it may + /// prevent the capture of genuine native EXC_BAD_ACCESS errors. + /// + /// + public bool SuppressExcBadAccess { get; set; } = false; // ---------- Other ---------- diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index d044e91f95..5e60eb100d 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -195,7 +195,7 @@ private static CocoaSdk.SentryHttpStatusCodeRange[] GetFailedRequestStatusCodes( // But we also get a native SIGABRT since it crashed the application, which is sent by the Sentry Cocoa SDK. // There should only be one exception on the event in this case - if (options.Native.SuppressSegfaults && evt.Exceptions?.Length == 1) + if ((options.Native.SuppressSignalAborts || options.Native.SuppressExcBadAccess) && evt.Exceptions?.Length == 1) { // It will match the following characteristics var ex = evt.Exceptions[0]; @@ -203,7 +203,7 @@ private static CocoaSdk.SentryHttpStatusCodeRange[] GetFailedRequestStatusCodes( // Thankfully, sometimes we can see Xamarin's unhandled exception handler on the stack trace, so we can filter // them out. Here is the function that calls abort(), which we will use as a filter: // https://github.com/xamarin/xamarin-macios/blob/c55fbdfef95028ba03d0f7a35aebca03bd76f852/runtime/runtime.m#L1114-L1122 - if (ex.Type == "SIGABRT" && ex.Value == "Signal 6, Code 0" && + if (options.Native.SuppressSignalAborts && ex.Type == "SIGABRT" && ex.Value == "Signal 6, Code 0" && ex.Stacktrace?.Frames.Any(f => f.Function == "xamarin_unhandled_exception_handler") is true) { // Don't send it @@ -215,7 +215,7 @@ private static CocoaSdk.SentryHttpStatusCodeRange[] GetFailedRequestStatusCodes( // Similar workaround for NullReferenceExceptions. We don't have any easy way to know whether the // exception is managed code (compiled to native) or original native code though. // See: https://github.com/getsentry/sentry-dotnet/issues/3776 - if (ex.Type == "EXC_BAD_ACCESS") + if (options.Native.SuppressExcBadAccess && ex.Type == "EXC_BAD_ACCESS") { // Don't send it options.LogDebug("Discarded {0} error ({1}). Captured as managed exception instead.", ex.Type, diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index 8dddd45ccc..61207dea0f 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -847,7 +847,7 @@ public void InitHub_DebugEnabled_DebugLogsLogged() [Theory] [InlineData(true)] [InlineData(false)] - public void ProcessOnBeforeSend_SegfaultsSuppressed(bool suppressSegfaults) + public void ProcessOnBeforeSend_NativeErrorSuppression(bool suppressNativeErrors) { var options = new SentryOptions { @@ -859,28 +859,19 @@ public void ProcessOnBeforeSend_SegfaultsSuppressed(bool suppressSegfaults) BackgroundWorker = Substitute.For(), InitNativeSdks = false, }; - options.Native.SuppressSegfaults = suppressSegfaults; + options.Native.SuppressExcBadAccess = suppressNativeErrors; options.SetBeforeSend(e => { // we return a result for suppress segfaults because it expects null and null for the opposite case - return suppressSegfaults ? e : null; + return suppressNativeErrors ? e : null; }); - options.Native.SuppressSegfaults = suppressSegfaults; var evt = new Sentry.CocoaSdk.SentryEvent(); - var ex = new Sentry.CocoaSdk.SentryException("Signal 6, Code 0", "SIGABRT"); - var st = new Sentry.CocoaSdk.SentryStacktrace( - [new Sentry.CocoaSdk.SentryFrame - { - Function = "xamarin_unhandled_exception_handler" - }], - new Foundation.NSDictionary() - ); - ex.Stacktrace = st; + var ex = new Sentry.CocoaSdk.SentryException("Not checked", "EXC_BAD_ACCESS"); evt.Exceptions = [ex]; var result = SentrySdk.ProcessOnBeforeSend(options, evt); - if (suppressSegfaults) + if (suppressNativeErrors) { result.Should().BeNull(); } From 0f159b71a2f197c7859b01a8c76ddd5c1e66353e Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 4 Mar 2025 16:41:14 -0500 Subject: [PATCH 37/39] Test fixes --- test/Sentry.Tests/SentrySdkTests.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index 61207dea0f..70576053ea 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -860,12 +860,13 @@ public void ProcessOnBeforeSend_NativeErrorSuppression(bool suppressNativeErrors InitNativeSdks = false, }; options.Native.SuppressExcBadAccess = suppressNativeErrors; + + var called = false; options.SetBeforeSend(e => { - // we return a result for suppress segfaults because it expects null and null for the opposite case - return suppressNativeErrors ? e : null; + called = true; + return e; }); - var evt = new Sentry.CocoaSdk.SentryEvent(); var ex = new Sentry.CocoaSdk.SentryException("Not checked", "EXC_BAD_ACCESS"); evt.Exceptions = [ex]; @@ -873,11 +874,12 @@ public void ProcessOnBeforeSend_NativeErrorSuppression(bool suppressNativeErrors if (suppressNativeErrors) { + called.Should().BeFalse(); result.Should().BeNull(); } else { - result.Should().NotBeNull(); + called.Should().BeTrue(); result.Exceptions.First().Type.Should().Be("SIGABRT"); } } From dd37709762649887e253706904a64beec78e5eb1 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 4 Mar 2025 16:54:18 -0500 Subject: [PATCH 38/39] Update SentrySdkTests.cs --- test/Sentry.Tests/SentrySdkTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index 70576053ea..f88876e914 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -880,7 +880,7 @@ public void ProcessOnBeforeSend_NativeErrorSuppression(bool suppressNativeErrors else { called.Should().BeTrue(); - result.Exceptions.First().Type.Should().Be("SIGABRT"); + result.Exceptions.First().Type.Should().Be("EXC_BAD_ACCESS"); } } From a4ba9821a6fb963529324d8ba81e9a87de47e267 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 4 Mar 2025 17:08:37 -0500 Subject: [PATCH 39/39] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a94fb29178..cf29d0999a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Fixes -- Using SentryOptions.SuppressSegfaults, users can now block duplicate errors from native due to dotnet NullReferenceExceptions - Defaults to false ([#3998](https://github.com/getsentry/sentry-dotnet/pull/3998)) +- Using SentryOptions.Native.SuppressExcBadAccess and SentryOptions.Native.SuppressSignalAborts, users can now block duplicate errors from native due to dotnet NullReferenceExceptions - Defaults to false ([#3998](https://github.com/getsentry/sentry-dotnet/pull/3998)) - Native iOS events are now exposed to the dotnet layer for users to hook through SentryOptions.BeforeSend and SentryOptions.OnCrashedLastRun ([#2102](https://github.com/getsentry/sentry-dotnet/pull/3958)) - Prevent crashes from occurring on Android during OnBeforeSend ([#4022](https://github.com/getsentry/sentry-dotnet/pull/4022))