From afd3c22d93c395006c981c2b3db96304c02ea69d Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 30 Jul 2026 11:12:16 +0200 Subject: [PATCH 1/3] feat(ios): Introduce enableMetricKit option Expose `enableMetricKit` in the React Native SDK options so the Cocoa MetricKit integration can be enabled from `Sentry.init()` instead of only through native initialization. The option name matches the Cocoa `SentryOptions` property, so it is decoded by `SentryOptionsInternal initWithDict:` and needs no additional mapping in `RNSentryStart.m`. Fixes #6457 --- CHANGELOG.md | 11 +++++ .../RNSentryCocoaTesterTests/RNSentryTests.m | 44 +++++++++++++++++++ packages/core/src/js/options.ts | 18 ++++++++ packages/core/test/wrapper.test.ts | 35 +++++++++++++++ 4 files changed, 108 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ea29b7f08..5fa9ae78d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,17 @@ ### Features +- Add `enableMetricKit` option to enable the iOS MetricKit integration ([#6457](https://github.com/getsentry/sentry-react-native/issues/6457)) + + When enabled, the iOS SDK sends `MXDiskWriteExceptionDiagnostic`, `MXCPUExceptionDiagnostic` and `MXHangDiagnostic` reports to Sentry. Requires iOS 15 or later and is disabled by default. MetricKit hang diagnostics are reported by the operating system and are separate from the app hangs captured by `enableAppHangTracking`, so enabling both can result in the same hang being reported twice. + + ```js + Sentry.init({ + dsn: '___DSN___', + enableMetricKit: true, + }); + ``` + - Attach a per-`(module, method)` TurboModule breakdown to active spans on `spanEnd`, plus `native.turbo_module` breadcrumbs for slow async calls ([#6478](https://github.com/getsentry/sentry-react-native/pull/6478)) When a root span ends (idle nav spans from `reactNavigationIntegration` / `expoRouterIntegration`, or a user's own `Sentry.startSpan(...)`), the integration writes `turbo_module...{call_count,duration_ms,error_count}` attributes plus summary keys (`turbo_module.total_call_count`, `turbo_module.total_duration_ms`, `turbo_module.top_module`). Async calls above `slowCallThresholdMs` (default 500ms) additionally record a `native.turbo_module` breadcrumb. Both surfaces enabled by default; new knobs `enableSpanAttribution`, `slowCallThresholdMs`, `maxTopModulesPerSpan` on `turboModuleContextIntegration`. diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m index 862625f944..e4ae187fcd 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m @@ -403,6 +403,50 @@ - (void)testStartCreateOptionsWithDictionaryAutoPerformanceTracingDisabled @"Did not disable Auto Performance Tracing"); } +- (void)testStartCreateOptionsWithDictionaryMetricKitEnabled +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"enableMetricKit" : @YES, + }; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + XCTAssertTrue(actualOptions.enableMetricKit, @"Did not enable MetricKit"); +} + +- (void)testStartCreateOptionsWithDictionaryMetricKitDisabled +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + @"enableMetricKit" : @NO, + }; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + XCTAssertFalse(actualOptions.enableMetricKit, @"Did not disable MetricKit"); +} + +- (void)testStartCreateOptionsWithDictionaryMetricKitDefault +{ + NSError *error = nil; + + NSDictionary *_Nonnull mockedReactNativeDictionary = @{ + @"dsn" : @"https://abcd@efgh.ingest.sentry.io/123456", + }; + SentryOptions *actualOptions = + [RNSentryStart createOptionsWithDictionary:mockedReactNativeDictionary error:&error]; + XCTAssertNotNil(actualOptions, @"Did not create sentry options"); + XCTAssertNil(error, @"Should not pass no error"); + XCTAssertFalse(actualOptions.enableMetricKit, @"MetricKit should be disabled by default"); +} + - (void)testStartCreateOptionsWithDictionarySpotlightEnabled { NSError *error = nil; diff --git a/packages/core/src/js/options.ts b/packages/core/src/js/options.ts index 9149fd3581..701296cab2 100644 --- a/packages/core/src/js/options.ts +++ b/packages/core/src/js/options.ts @@ -196,6 +196,24 @@ export interface BaseReactNativeOptions { */ appHangTimeoutInterval?: number; + /** + * Use this feature to enable the Sentry MetricKit integration. + * + * When enabled, the SDK sends `MXDiskWriteExceptionDiagnostic`, `MXCPUExceptionDiagnostic` and + * `MXHangDiagnostic` to Sentry. Requires iOS 15 or later, because only on these versions MetricKit + * delivers diagnostic reports immediately. On earlier versions this option has no effect. + * + * MetricKit hang diagnostics are reported by the operating system and are distinct from the app + * hangs captured by `enableAppHangTracking`. Enabling both can result in the same hang being + * reported twice, from two different sources. + * + * iOS only + * + * @default false + * @platform ios + */ + enableMetricKit?: boolean; + /** * The max queue size for capping the number of envelopes waiting to be sent by Transport. */ diff --git a/packages/core/test/wrapper.test.ts b/packages/core/test/wrapper.test.ts index c43dfb84fd..c272985023 100644 --- a/packages/core/test/wrapper.test.ts +++ b/packages/core/test/wrapper.test.ts @@ -299,6 +299,41 @@ describe('Tests Native Wrapper', () => { expect(NATIVE.enableNative).toBe(true); }); + test('passes enableMetricKit to the Native SDK when set', async () => { + await NATIVE.initNativeSdk({ + dsn: VALID_DSN, + enableNative: true, + autoInitializeNativeSdk: true, + enableMetricKit: true, + devServerUrl: undefined, + defaultSidecarUrl: undefined, + mobileReplayOptions: undefined, + }); + + expect(RNSentry.initNativeSdk).toHaveBeenCalled(); + // @ts-expect-error mock value + const initParameter = RNSentry.initNativeSdk.mock.calls[0][0]; + expect(initParameter).toEqual(expect.objectContaining({ enableMetricKit: true })); + expect(NATIVE.enableNative).toBe(true); + }); + + test('does not pass enableMetricKit to the Native SDK when not set', async () => { + await NATIVE.initNativeSdk({ + dsn: VALID_DSN, + enableNative: true, + autoInitializeNativeSdk: true, + devServerUrl: undefined, + defaultSidecarUrl: undefined, + mobileReplayOptions: undefined, + }); + + expect(RNSentry.initNativeSdk).toHaveBeenCalled(); + // @ts-expect-error mock value + const initParameter = RNSentry.initNativeSdk.mock.calls[0][0]; + expect(initParameter).not.toHaveProperty('enableMetricKit'); + expect(NATIVE.enableNative).toBe(true); + }); + test('does not initialize with autoInitializeNativeSdk: false', async () => { NATIVE.enableNative = false; debug.warn = jest.fn(); From 092b3b9e4d4f29dbef63e4ab503c41e04fcc8a47 Mon Sep 17 00:00:00 2001 From: Alexander Date: Thu, 30 Jul 2026 11:56:20 +0200 Subject: [PATCH 2/3] Update CHANGELOG.md Co-authored-by: Antonis Lilis --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fa9ae78d3..c4462ce55e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ### Features -- Add `enableMetricKit` option to enable the iOS MetricKit integration ([#6457](https://github.com/getsentry/sentry-react-native/issues/6457)) +- Add `enableMetricKit` option to enable the iOS MetricKit integration ([#6540](https://github.com/getsentry/sentry-react-native/pull/6540)) When enabled, the iOS SDK sends `MXDiskWriteExceptionDiagnostic`, `MXCPUExceptionDiagnostic` and `MXHangDiagnostic` reports to Sentry. Requires iOS 15 or later and is disabled by default. MetricKit hang diagnostics are reported by the operating system and are separate from the app hangs captured by `enableAppHangTracking`, so enabling both can result in the same hang being reported twice. From 1a1187cdae502dd9173a8ff20c0dabeca9c66fbc Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 30 Jul 2026 15:14:08 +0200 Subject: [PATCH 3/3] docs(changelog): Move enableMetricKit entry to Unreleased --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ac4abc32b..1770ca1bdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ > make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first. -## 8.21.0 +## Unreleased ### Features @@ -21,6 +21,10 @@ }); ``` +## 8.21.0 + +### Features + - Add check and download timing spans to Expo Updates listener integration ([#6430](https://github.com/getsentry/sentry-react-native/pull/6430)) - Attach a per-`(module, method)` TurboModule breakdown to active spans on `spanEnd`, plus `native.turbo_module` breadcrumbs for slow async calls ([#6478](https://github.com/getsentry/sentry-react-native/pull/6478))