Skip to content

Callback error isolation: remaining gaps after #5607 and #5610 #5634

Open
3 of 7 issues completed
Open

Description

@ric-oliv

Follow-up to #5535. #5606, #5607 and #5610 isolate the managed callbacks and count what they drop: callback_error for a throw, the usual reason for a null return. This tracks what's left. Most of it is on native events, where the native SDK does the counting.

Spec: hooks spec 1.1.0 (candidate). When a callback fails, SDKs MUST log an error naming the callback, SHOULD drop the data passed to it, and SHOULD record a callback_error client report.

1. iOS native events are counted twice

On iOS, ProcessOnBeforeSend runs the managed event processors and BeforeSend on native events:

if (SentryEventHelper.ProcessEvent(sentryEvent, manualProcessors, null, options, DataCategory.Error)
is not { } processedEvent)
{
return null;
}
processedEvent = SentryEventHelper.DoBeforeSend(processedEvent, new SentryHint(), options);

SentryEventHelper records the drop:

options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, dataCategory);
options.LogError(e, "Event processor {0} threw an exception. The event will be dropped.", processor.GetType().Name);
return null;
}
if (processedEvent == null)
{
options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.EventProcessor, dataCategory);
options.LogInfo("Event dropped by processor {0}", processor.GetType().Name);
break;
}
}
return processedEvent;
}
public static SentryEvent? DoBeforeSend(SentryEvent? @event, SentryHint hint, SentryOptions options)
{
if (@event is null || options.BeforeSendInternal is null)
{
return @event;
}
options.LogDebug("Calling the BeforeSend callback.");
try
{
@event = options.BeforeSendInternal?.Invoke(@event, hint);
if (@event == null) // Rejected event
{
options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.BeforeSend, DataCategory.Error);
options.LogInfo("Event dropped by BeforeSend callback.");
}
}
catch (Exception e)
{
options.ClientReportRecorder.RecordDiscardedEvent(DiscardReason.CallbackError, DataCategory.Error);

.NET then returns nil, and sentry-cocoa records before_send for the same event:
https://github.com/getsentry/sentry-cocoa/blob/9.29.2/Sources/Sentry/SentryClient.m#L1164-L1172

Native event dropped because .NET counts sentry-cocoa counts
Event processor returns null event_processor before_send
Event processor throws callback_error before_send
BeforeSend returns null before_send before_send
BeforeSend throws callback_error before_send

Main already double-counts a null return. For a throw, the two counts now also disagree on the reason.

Needs a decision. Skipping the .NET record for native events fixes the double count, but then a throw on a native iOS event only ever shows up as before_send, until 2 is done.

2. Native events can't be counted as callback_error

Neither native SDK has the reason yet: sentry-java 8.58.0 (DiscardReason.java) and sentry-cocoa 9.29.2 (SentryDiscardReason.swift).

Upstream work:

To investigate once those ship: instead of returning null, the bridges could pass the failure through to the native SDK, by throwing a Java exception from BeforeSendCallback.Execute or an NSException from the Cocoa block. The native SDK would then drop the event and count callback_error itself, and .NET could stop recording for native events, which also fixes 1. This depends on how .NET for Android and .NET for iOS marshal managed exceptions across JNI and Objective-C, which needs checking on a device.

3. Exception filters and exception processors aren't isolated

IExceptionFilter.Filter (SentryClient.cs:449) and ISentryEventExceptionProcessor.Process (SentryClient.cs:364-367) have no try/catch of their own. A throw ends in SentryClient.CaptureEvent's catch-all (SentryClient.cs:84). The event is dropped and nothing is counted. The log, An error occurred when capturing the event '{0}'., doesn't name the callback.

That misses the spec's MUST (log naming the callback) and its SHOULD (callback_error). It's a managed-only fix, following the per-processor catch in SentryEventHelper.ProcessEvent.

4. SDK event processors are counted as callback_error (minor)

SentryEventHelper.ProcessEvent catches every event processor, the SDK's own included. The spec only requires isolating user-registered processors. A bug in an SDK processor would show up in the user's Stats as their callback failing. getsentry/sentry-java#6162 marks SDK-owned processors to tell them apart, which is one way to do it.

5. Each recorded drop allocates 24 B

ThreadsafeCounterDictionary.Add and Increment call _items.GetOrAdd(key, new CounterItem()) (ThreadsafeCounterDictionary.cs:27,33). That allocates a CounterItem on every call, even when the key already exists. #5607 adds call sites on hot paths, such as every dropped log or metric.

Measured: a dropped log went from 0 B to 24 B allocated. The factory overload, GetOrAdd(key, static _ => new CounterItem()), brings it back to 0 B. CollectionsExtensions.cs:10 already uses the factory overload. This one is independent of the rest and can be a small PR off main.

Activity

  1. linear-code commented on Sep 28, 2026

    @linear-code
  2. added
    BugSomething isn't working
    .NETPull requests that update .net code
    on Sep 28, 2026
  3. jamescrosswell commented on Oct 7, 2026

    @jamescrosswell
    Collaborator
    1. iOS native events are counted twice
      ...
      Needs a decision. Skipping the .NET record for native events fixes the double count, but then a throw on a native iOS event only ever shows up as before_send, until 2 is done.

    @ric-oliv I'd say we supress counting in .NET when we know the native SDK is going to do the counting. Eventually the Cocoa SDK will address/correct the reporting category for these and all will be well in the world.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions