diff --git a/CHANGELOG.md b/CHANGELOG.md index 4390d1fb4e..1770ca1bdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,21 @@ > make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first. +## Unreleased + +### Features + +- 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. + + ```js + Sentry.init({ + dsn: '___DSN___', + enableMetricKit: true, + }); + ``` + ## 8.21.0 ### Features 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();