···11+import Foundation
22+import Sentry
33+44+/// Client-side filter for Sentry events.
55+///
66+/// Wired into `SentrySDK.start { options.beforeSend = SentryEventFilter.filterSystemHang }`.
77+enum SentryEventFilter {
88+ /// Known stack frame function-name fragments that indicate a system-induced
99+ /// App Hang (wake-from-sleep, active space change, external display connect,
1010+ /// menu bar redraw, etc.). These hangs are observable but have no app-level
1111+ /// remedy — filter them out to avoid drowning real hangs in noise.
1212+ static let systemHangSignatures = [
1313+ "_NSMenuBarDisplayManagerActiveSpaceChanged",
1414+ "NSMenuBarLocalDisplayWindow",
1515+ "NSMenuBarPresentationInstance",
1616+ "NSMenuBarReplicantWindow",
1717+ ]
1818+1919+ /// Drop App Hang events whose stack contains zero in-app frames AND matches
2020+ /// at least one known system signature. Conservative by design: if the hang
2121+ /// involves any app code, keep it; if the stack is all-system but matches no
2222+ /// known pattern, keep it too (so we still see novel system-level issues).
2323+ static func filterSystemHang(_ event: Event) -> Event? {
2424+ guard let exception = event.exceptions?.first,
2525+ exception.mechanism?.type == "AppHang"
2626+ else {
2727+ return event
2828+ }
2929+ let frames = exception.stacktrace?.frames ?? []
3030+ let hasAppFrame = frames.contains { $0.inApp?.boolValue == true }
3131+ let hasSystemSignature = frames.contains { frame in
3232+ guard let function = frame.function else { return false }
3333+ return systemHangSignatures.contains { function.contains($0) }
3434+ }
3535+ if !hasAppFrame && hasSystemSignature {
3636+ return nil
3737+ }
3838+ return event
3939+ }
4040+}