Skip to content

Commit bdbad69

Browse files
Map iOS Native BeforeSend & OnCrashedLastEvent to SentryOptions.Native (#3958)
* Initial commit for exposing iOS native before send & crashed last run * Initial feature set is working * Format code * Update CHANGELOG.md * Merge signal abort code before user based BeforeSend * Move native BeforeSend & OnCrashedLastRun events to SentryOptions - OnCrashedLastRun only available to IOS/MacCatalyst for now * Always run NativeOptions.BeforeSend so sig aborts can be filtered * Update SentrySdk.cs * Update CHANGELOG.md * Format code * WIP - removing serialization mechanics as they cause a native crash due to SentryEnvelopeItem being deserialized instead of SentryEvent * Transferring transformed sentryevent back to original native event instead of trying to remap the entire object * Remove unnecessary hacks * Format code * Add missing bindable option for native iOS sentryoptions * Update SentrySdk.cs * Update SentrySdk.cs * Update CHANGELOG.md * Update CHANGELOG.md * Unit tests mapping checks to/from native iOS * Format code * Update NativeSerializationTests.cs * Revert "Add missing bindable option for native iOS sentryoptions" This reverts commit bf4d33d. * Remove iOS native event suppress configuration * Update CHANGELOG.md * Rename tests according to review * add timestamp for deserialize test * Account for fractions of seconds * Platforms should not change * Update CocoaExtensionsTests.cs * Update CocoaExtensionsTests.cs * Update CocoaExtensionsTests.cs * Update CocoaExtensionsTests.cs * Safety SentryLevel in both directions * Update EnumExtensions.cs * Format code --------- Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io>
1 parent 7953f21 commit bdbad69

File tree

13 files changed

+419
-112
lines changed

13 files changed

+419
-112
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
### Features
1414

15+
- 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))
1516
- Users can now register their own MAUI controls for breadcrumb creation ([#3997](https://github.com/getsentry/sentry-dotnet/pull/3997))
1617
- Serilog scope properties are now sent with Sentry events ([#3976](https://github.com/getsentry/sentry-dotnet/pull/3976))
1718
- 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))

samples/Sentry.Samples.Ios/AppDelegate.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l
2828
options.Native.EnableAppHangTracking = true;
2929

3030
options.CacheDirectoryPath = Path.GetTempPath();
31+
32+
options.SetBeforeSend((evt, _) =>
33+
{
34+
evt.SetTag("dotnet-iOS-Native-Before", "Hello World");
35+
return evt;
36+
});
37+
38+
options.OnCrashedLastRun = e =>
39+
{
40+
Console.WriteLine(e);
41+
};
3142
});
3243

3344
// create a new window instance based on the screen size

samples/Sentry.Samples.Ios/Sentry.Samples.Ios.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net9.0-ios18.0</TargetFramework>
4+
<TargetFramework>net9.0-ios</TargetFramework>
55
<OutputType>Exe</OutputType>
66
<Nullable>enable</Nullable>
77
<ImplicitUsings>true</ImplicitUsings>

src/Sentry.Bindings.Cocoa/ApiDefinitions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ namespace Sentry.CocoaSdk;
2727

2828
// typedef SentryEvent * _Nullable (^SentryBeforeSendEventCallback)(SentryEvent * _Nonnull);
2929
[Internal]
30-
[return: NullAllowed]
3130
delegate SentryEvent SentryBeforeSendEventCallback (SentryEvent @event);
3231

3332
// typedef id<SentrySpan> _Nullable (^SentryBeforeSendSpanCallback)(id<SentrySpan> _Nonnull);

src/Sentry/Internal/Extensions/JsonExtensions.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,31 @@ public static void Deconstruct(this JsonProperty jsonProperty, out string name,
213213
return double.Parse(json.ToString()!, CultureInfo.InvariantCulture);
214214
}
215215

216+
/// <summary>
217+
/// Safety value to deal with native serialization - allows datetimeoffset to come in as a long or string value
218+
/// </summary>
219+
/// <param name="json"></param>
220+
/// <param name="propertyName"></param>
221+
/// <returns></returns>
222+
public static DateTimeOffset? GetSafeDateTimeOffset(this JsonElement json, string propertyName)
223+
{
224+
DateTimeOffset? result = null;
225+
var dtRaw = json.GetPropertyOrNull(propertyName);
226+
if (dtRaw != null)
227+
{
228+
if (dtRaw.Value.ValueKind == JsonValueKind.Number)
229+
{
230+
var epoch = Convert.ToInt64(dtRaw.Value.GetDouble());
231+
result = DateTimeOffset.FromUnixTimeSeconds(epoch);
232+
}
233+
else
234+
{
235+
result = dtRaw.Value.GetDateTimeOffset();
236+
}
237+
}
238+
return result;
239+
}
240+
216241
public static long? GetHexAsLong(this JsonElement json)
217242
{
218243
// If the address is in json as a number, we can just use it.

src/Sentry/Platforms/Cocoa/Extensions/CocoaExtensions.cs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,24 @@ public static NSDictionary<NSString, NSObject> ToNSDictionary<TValue>(
169169
d.Keys.ToArray());
170170
}
171171

172+
public static NSDictionary<NSString, NSString> ToNSDictionaryStrings(
173+
this IEnumerable<KeyValuePair<string, string>> dict)
174+
{
175+
var d = new Dictionary<NSString, NSString>();
176+
foreach (var item in dict)
177+
{
178+
if (item.Value != null)
179+
{
180+
d.Add((NSString)item.Key, new NSString(item.Value));
181+
}
182+
}
183+
184+
return NSDictionary<NSString, NSString>
185+
.FromObjectsAndKeys(
186+
d.Values.ToArray(),
187+
d.Keys.ToArray());
188+
}
189+
172190
public static NSDictionary<NSString, NSObject>? ToNullableNSDictionary<TValue>(
173191
this ICollection<KeyValuePair<string, TValue>> dict) =>
174192
dict.Count == 0 ? null : dict.ToNSDictionary();
@@ -224,4 +242,126 @@ public static object ToObject(this NSNumber n)
224242
$"NSNumber \"{n.StringValue}\" has an unknown ObjCType \"{n.ObjCType}\" (Class: \"{n.Class.Name}\")")
225243
};
226244
}
245+
246+
public static void CopyToCocoaSentryEvent(this SentryEvent managed, CocoaSdk.SentryEvent native)
247+
{
248+
// we only support a subset of mutated data to be passed back to the native SDK at this time
249+
native.ServerName = managed.ServerName;
250+
native.Dist = managed.Distribution;
251+
native.Logger = managed.Logger;
252+
native.ReleaseName = managed.Release;
253+
native.Environment = managed.Environment;
254+
native.Transaction = managed.TransactionName!;
255+
native.Message = managed.Message?.ToCocoaSentryMessage();
256+
native.Tags = managed.Tags?.ToNSDictionaryStrings();
257+
native.Extra = managed.Extra?.ToNSDictionary();
258+
native.Breadcrumbs = managed.Breadcrumbs?.Select(x => x.ToCocoaBreadcrumb()).ToArray();
259+
native.User = managed.User?.ToCocoaUser();
260+
261+
if (managed.Level != null)
262+
{
263+
native.Level = managed.Level.Value.ToCocoaSentryLevel();
264+
}
265+
266+
if (managed.Exception != null)
267+
{
268+
native.Error = new NSError(new NSString(managed.Exception.ToString()), IntPtr.Zero);
269+
}
270+
}
271+
272+
public static SentryEvent? ToSentryEvent(this CocoaSdk.SentryEvent sentryEvent)
273+
{
274+
using var stream = sentryEvent.ToJsonStream();
275+
if (stream == null)
276+
return null;
277+
278+
using var json = JsonDocument.Parse(stream);
279+
var exception = sentryEvent.Error == null ? null : new NSErrorException(sentryEvent.Error);
280+
var ev = SentryEvent.FromJson(json.RootElement, exception);
281+
return ev;
282+
}
283+
284+
public static CocoaSdk.SentryMessage ToCocoaSentryMessage(this SentryMessage msg)
285+
{
286+
var native = new CocoaSdk.SentryMessage(msg.Formatted ?? string.Empty);
287+
native.Params = msg.Params?.Select(x => x.ToString()!).ToArray() ?? new string[0];
288+
289+
return native;
290+
}
291+
292+
// not tested or needed yet - leaving for future just in case
293+
// public static CocoaSdk.SentryThread ToCocoaSentryThread(this SentryThread thread)
294+
// {
295+
// var id = NSNumber.FromInt32(thread.Id ?? 0);
296+
// var native = new CocoaSdk.SentryThread(id);
297+
// native.Crashed = thread.Crashed;
298+
// native.Current = thread.Current;
299+
// native.Name = thread.Name;
300+
// native.Stacktrace = thread.Stacktrace?.ToCocoaSentryStackTrace();
301+
// // native.IsMain = not in dotnet
302+
// return native;
303+
// }
304+
//
305+
// public static CocoaSdk.SentryRequest ToCocoaSentryRequest(this SentryRequest request)
306+
// {
307+
// var native = new CocoaSdk.SentryRequest();
308+
// native.Cookies = request.Cookies;
309+
// native.Headers = request.Headers?.ToNSDictionaryStrings();
310+
// native.Method = request.Method;
311+
// native.QueryString = request.QueryString;
312+
// native.Url = request.Url;
313+
//
314+
// // native.BodySize does not exist in dotnet
315+
// return native;
316+
// }
317+
//
318+
319+
// public static CocoaSdk.SentryException ToCocoaSentryException(this SentryException ex)
320+
// {
321+
// var native = new CocoaSdk.SentryException(ex.Value ?? string.Empty, ex.Type ?? string.Empty);
322+
// native.Module = ex.Module;
323+
// native.Mechanism = ex.Mechanism?.ToCocoaSentryMechanism();
324+
// native.Stacktrace = ex.Stacktrace?.ToCocoaSentryStackTrace();
325+
// // not part of native - ex.ThreadId;
326+
// return native;
327+
// }
328+
//
329+
// public static CocoaSdk.SentryStacktrace ToCocoaSentryStackTrace(this SentryStackTrace stackTrace)
330+
// {
331+
// var frames = stackTrace.Frames?.Select(x => x.ToCocoaSentryFrame()).ToArray() ?? new CocoaSdk.SentryFrame[0];
332+
// var native = new CocoaSdk.SentryStacktrace(frames, new NSDictionary<NSString, NSString>());
333+
// // native.Register & native.Snapshot missing in dotnet
334+
// return native;
335+
// }
336+
//
337+
// public static CocoaSdk.SentryFrame ToCocoaSentryFrame(this SentryStackFrame frame)
338+
// {
339+
// var native = new CocoaSdk.SentryFrame();
340+
// native.Module = frame.Module;
341+
// native.Package = frame.Package;
342+
// native.InstructionAddress = frame.InstructionAddress?.ToString();
343+
// native.Function = frame.Function;
344+
// native.Platform = frame.Platform;
345+
// native.ColumnNumber = frame.ColumnNumber;
346+
// native.FileName = frame.FileName;
347+
// native.InApp = frame.InApp;
348+
// native.ImageAddress = frame.ImageAddress?.ToString();
349+
// native.LineNumber = frame.LineNumber;
350+
// native.SymbolAddress = frame.SymbolAddress?.ToString();
351+
//
352+
// // native.StackStart = doesn't exist in dotnet
353+
// return native;
354+
// }
355+
//
356+
// public static CocoaSdk.SentryMechanism ToCocoaSentryMechanism(this Mechanism mechanism)
357+
// {
358+
// var native = new CocoaSdk.SentryMechanism(mechanism.Type);
359+
// native.Synthetic = mechanism.Synthetic;
360+
// native.Handled = mechanism.Handled;
361+
// native.Desc = mechanism.Description;
362+
// native.HelpLink = mechanism.HelpLink;
363+
// native.Data = mechanism.Data?.ToNSDictionary();
364+
// // TODO: Meta does not currently translate in dotnet - native.Meta = null;
365+
// return native;
366+
// }
227367
}

src/Sentry/Platforms/Cocoa/Extensions/EnumExtensions.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,16 @@ internal static class EnumExtensions
44
{
55
// These align, so we can just cast
66
public static SentryLevel ToSentryLevel(this CocoaSdk.SentryLevel level) => (SentryLevel)level;
7-
public static CocoaSdk.SentryLevel ToCocoaSentryLevel(this SentryLevel level) => (CocoaSdk.SentryLevel)level;
7+
8+
public static CocoaSdk.SentryLevel ToCocoaSentryLevel(this SentryLevel level) => level switch
9+
{
10+
SentryLevel.Debug => CocoaSdk.SentryLevel.Debug,
11+
SentryLevel.Info => CocoaSdk.SentryLevel.Info,
12+
SentryLevel.Warning => CocoaSdk.SentryLevel.Warning,
13+
SentryLevel.Error => CocoaSdk.SentryLevel.Error,
14+
SentryLevel.Fatal => CocoaSdk.SentryLevel.Fatal,
15+
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null)
16+
};
817

918
public static BreadcrumbLevel ToBreadcrumbLevel(this CocoaSdk.SentryLevel level) =>
1019
level switch

src/Sentry/Platforms/Cocoa/Extensions/SentryEventExtensions.cs

Lines changed: 0 additions & 45 deletions
This file was deleted.

src/Sentry/Platforms/Cocoa/SentryOptions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,6 @@ internal NativeOptions(SentryOptions options)
197197
/// </remarks>
198198
public NSUrlSessionDelegate? UrlSessionDelegate { get; set; } = null;
199199

200-
201200
// ---------- Other ----------
202201

203202
/// <summary>

0 commit comments

Comments
 (0)