Sentry Cocoa SDK

repository·main·Indexed 22 days ago

https://github.com/getsentry/sentry-cocoa

The official Sentry SDK for Apple platforms, providing error reporting, performance monitoring, and crash reporting for iOS, iPadOS, tvOS, macOS, watchOS, and visionOS. Includes third-party logging integrations for CocoaLumberjack, Sentry Pulse, SwiftLog, and SwiftyBeaver.

Tokens
43.2K
Snippets
83
Records
198
Agent score
77%

What's inside sentry-cocoa

  1. Understand the SentryCrash architecture and components

    main

    SentryCrash is the core crash detection and reporting engine for the Sentry Cocoa SDK. It is a specialized subsystem written primarily in C, with Objective-C, C++, and Swift wrappers.

    Key architectural components include:

    • Monitors: Detect various crash types including Mach kernel exceptions, POSIX signals, Objective-C exceptions, and C++ exceptions.
    • Recording: Handles the core logic of capturing crash details, writing reports to disk, and managing report storage/retrieval.
    • Tools: A large collection of low-level utilities for CPU context handling (arm, x86, etc.), stack unwinding, Mach kernel interfaces, and JSON encoding.
    • Reporting: Provides mechanisms for filtering crash reports.
    • Installations: Manages the lifecycle of the crash handler and report delivery.
  2. Configure view masking with SentryUIRedactBuilder

    main

    The SentryUIRedactBuilder is the default implementation for identifying areas to mask. It is designed to be highly configurable to prevent privacy leaks while avoiding over-masking (which can make screenshots useless).

    Key Configuration Capabilities:

    • Class-Based Redaction/Ignoring: Uses string identifiers (e.g., type(of: view).description()) rather than AnyClass to avoid crashing when performing lookups on background threads.
    • Container Overrides:
      • Ignore Containers: Mark a container so that all its direct children are treated as safe/unmasked.
      • Redact Containers: Force-mask an entire subtree.
    • Heuristics: Automatically handles special cases like UIImageView (skipping tiny or bundle-resident images) and specific SwiftUI/React Native renderers.
    • Layer Filtering: Can disambiguate views by checking the underlying CALayer type (e.g., distinguishing a SwiftUI text renderer from a structural element).
  3. Understand Apple definitions for Hangs and Hitches

    main

    When troubleshooting app responsiveness using Sentry, it is important to distinguish between Apple's definitions of hangs and hitches:

    • Hang: Occurs when the main run loop is unresponsive for 250 ms or more. It is measured as the time between CFRunLoopActivity states .afterWaiting and .beforeWaiting.
    • Hitch: A frame that appears on screen later than expected during continuous interactions (like scrolling, dragging, or animation). This is caused by delays in the commit or render phase of the render loop. Note that hitch duration refers only to the overshoot past the frame deadline, not the total frame time.
    • Watchdog termination: When the OS terminates an app for blocking the main thread for a significant time. This uses termination code 0x8badf00d and covers both launch (scene-create) and ongoing responsiveness (scene-update) failures. Timeouts vary by context (e.g., ~19.97s for iOS foreground, ~15s for watchOS background tasks).
  4. Boxing resilient value types in SentryObjC

    main

    When building for distribution (BUILD_LIBRARY_FOR_DISTRIBUTION=YES), the Swift compiler treats cross-module value types (structs and enums) as resilient. This means their in-memory size is unknown at compile time.

    If an @objc class attempts to store a resilient value type directly as an instance variable (ivar), it can cause architecture-specific failures (e.g., the class symbol might exist on arm64 but be missing on x86_64).

    Solution: Wrap any cross-module struct or enum in a Box<T> to ensure the stored property is pointer-sized and has a known layout.

    Example Fix:

    // Use Box<T> for resilient cross-module structs
    @objc(SentryObjCFoo) public final class SentryObjCFoo: NSObject {
        internal let wrapped: Box<FooStruct> 
    
        internal init(_ wrapped: FooStruct) {
            self.wrapped = Box(wrapped)
        }
    
        @objc public var name: String { wrapped.value.name }
    }
    @objc(SentryObjCFoo) public final class SentryObjCFoo: NSObject {
        internal let wrapped: Box<FooStruct>     // pointer-sized, known layout
    
        internal init(_ wrapped: FooStruct) {
            self.wrapped = Box(wrapped)
        }
    
        @objc public var name: String { wrapped.value.name }
    }
  5. Limitations of Accessibility-Based Redaction for Sentry

    main

    While the SentryAccessibilityRedactBuilder exists as an alternative for identifying views to mask, it is not suitable for production use in the Sentry SDK due to the following reasons:

    1. VoiceOver Dependency: On real iOS devices, accessibility information is not populated unless VoiceOver is enabled system-wide. This makes it unreliable for capturing session replays from general users.
    2. App Store Compliance: Attempting to force accessibility automation (e.g., by patching the _AXSAutomationEnabled flag via dlsym) requires accessing private APIs and system frameworks, which violates Apple's App Store Review Guidelines.
    3. Sandboxing Restrictions: Writing to accessibility preferences outside of an application's container is blocked by iOS sandboxing, leading to permission errors.
  6. Compare Wireframe vs. Defensive-Unredacting Masking Strategies

    main

    When considering how to handle sensitive view masking, two primary conceptual strategies are discussed:

    1. Wireframe Based Approach

    • Philosophy: Replace pixels with geometric primitives.
    • Best For: Session Replay where privacy and performance are prioritized over visual fidelity.
    • Pros: High privacy, high performance, low data payload, potential for RRWeb-style structured data integration.
    • Cons: Lower visual fidelity, complex heuristics for categorization, poor SwiftUI support.

    2. Defensive-Unredacting Approach

    • Philosophy: "Assume everything is sensitive unless proven otherwise."
    • Mechanism: Start with a fully masked (redacted) screenshot and only remove redaction from regions that can be proven safe with 100% certainty (e.g., empty views or known system UI).
    • Pros: Maximum privacy protection; eliminates false negatives (missed sensitive content).
    • Cons: Extremely difficult to implement reliably; likely to over-mask; requires absolute certainty which is hard to achieve with transparency or custom drawing; not usable for session replay due to the complexity of proving safety.
  7. Identify Objective-C method details for swizzling

    main

    Before swizzling, you must identify the following details about the target Objective-C method:

    • Receiver: The object receiving the message (the self inside the method). Note that the Receiver type (the instance type accepted by the interceptor) may differ from the classToSwizzle (the runtime class whose implementation is replaced, such as a superclass).
    • Selector: Use #selector for public methods. Use NSSelectorFromString only for private selectors that cannot be referenced by Swift.
    • Return type
    • Argument types and order: Explicitly define these.
    • Nullability: Determine if arguments or the result can be nil.
    • Platform availability: Which platforms the method exists on.
    • Visibility: Whether the method is public or private.
    let selector = #selector(URLSessionTask.resume)
    let privateSelector = NSSelectorFromString("setState:")
  8. Maintain Sampling Invariants

    main

    The SDK makes local sampling decisions using the logic sample_rand < sample_rate. To ensure Relay makes the same decision, the transmitted values must maintain this invariant.

    Critical Invariant: sample_rand < sample_rate ⟺ sampled

    Best Practices:

    • Use full-precision values for the decision logic, but be careful with rounding during transmission. If the transmitted sample_rate is rounded down, the boundary invariant might flip.
    • sample_rand should be seeded once at the start of a trace and reused throughout.
  9. Understand SentryCrash Monitors and Context

    main

    A Monitor is a specialized component responsible for detecting a specific type of crash. Different monitors handle different failure modes:

    • MachException Monitor: Handles Mach kernel exceptions.
    • Signal Monitor: Handles POSIX signals.
    • NSException Monitor: Handles Objective-C exceptions.
    • C++ Exception Monitor: Handles C++ exceptions via __cxa_throw hooks.

    When a monitor detects a crash, it populates a SentryCrash_MonitorContext. This is a unified structure that holds the crash details. This context is then passed to the shared onCrash() callback, which orchestrates writing the report to disk and executing best-effort diagnostic callbacks (like saving screenshots or view hierarchies).

  10. Async-signal-safety rules for SentryCrash

    main

    When writing or modifying code that runs within a crash handler or signal context, you must adhere to async-signal-safety rules to avoid deadlocks or undefined behavior.

    Requirements:

    • Use SENTRY_ASYNC_SAFE_LOG_* macros for logging. Do not use malloc, NSLog, or printf in signal contexts.
    • The handleSignal() function must only use async-safe functions.
    • The Mach exception handler is designed to run on a dedicated thread rather than in the signal context.

    Intentional Non-Safe Exceptions: Certain non-async-signal-safe operations are performed after a report is saved, such as screenshot or view hierarchy capture. These are considered best-effort and are acceptable because the application is already in a crashed state.

  11. How the coverage check (surface-map self-audit) works

    main

    To prevent the audit from becoming a "silent coverage hole," a coverage check runs alongside the main audit to ensure references/surface-map.md is up to date. It performs two checks:

    1. SDK side: It scans the Cocoa SDK for protocol-relevant code (e.g., serialize implementations, envelope construction, header reads in Sources/Swift/Networking/ or Sources/Swift/Protocol/) that is not currently listed in the surface map.
    2. Relay side: It verifies that the Relay/spec sources cited in the map still exist and checks Relay's protocol enums (like DataCategory or ItemType) for new members that the current map wouldn't catch.

    Outcomes:

    • If the map is accurate: coverage: OK is printed.
    • If gaps are found: coverage: GAPS is printed, and the tool automatically opens a draft PR to update references/surface-map.md with the missing information.