Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
## Unreleased

### Features

- Adds Metrics Beta ([#5402](https://github.com/getsentry/sentry-react-native/pull/5402))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs changes are here: getsentry/sentry-docs#15646

nit: We could add a link to the news docs here

Suggested change
- Adds Metrics Beta ([#5402](https://github.com/getsentry/sentry-react-native/pull/5402))
- Adds Metrics Beta ([#5402](https://github.com/getsentry/sentry-react-native/pull/5402))
- To learn more visit [the documentation](https://docs.sentry.io/platforms/react-native/metrics).


### Fixes

- Fix `Object.freeze` type pollution from `@sentry-internal/replay` ([#5408](https://github.com/getsentry/sentry-react-native/issues/5408))
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export {
setCurrentClient,
addEventProcessor,
lastEventId,
metrics,
} from '@sentry/core';

export {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/js/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ export const NATIVE: SentryNativeWrapper = {
beforeSend,
beforeBreadcrumb,
beforeSendTransaction,
beforeSendMetric,
integrations,
ignoreErrors,
logsOrigin,
Expand Down
180 changes: 180 additions & 0 deletions packages/core/test/metrics.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { getClient, metrics, setCurrentClient } from '@sentry/core';
import { ReactNativeClient } from '../src/js';
import { getDefaultTestClientOptions } from './mocks/client';
import { NATIVE } from './mockWrapper';

jest.mock('../src/js/wrapper', () => jest.requireActual('./mockWrapper'));

const EXAMPLE_DSN = 'https://[email protected]/148053';

describe('Metrics', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.useFakeTimers();
(NATIVE.isNativeAvailable as jest.Mock).mockImplementation(() => true);
});

afterEach(() => {
const client = getClient();
client?.close();
jest.clearAllTimers();
jest.useRealTimers();
});

describe('beforeSendMetric', () => {
it('is called when enableMetrics is true and a metric is sent', async () => {
const beforeSendMetric = jest.fn(metric => metric);

const client = new ReactNativeClient({
...getDefaultTestClientOptions({
dsn: EXAMPLE_DSN,
enableMetrics: true,
beforeSendMetric,
}),
});

setCurrentClient(client);
client.init();

// Send a metric
metrics.count('test_metric', 1);

jest.advanceTimersByTime(10000);
expect(beforeSendMetric).toHaveBeenCalled();
});

it('is not called when enableMetrics is false', async () => {
const beforeSendMetric = jest.fn(metric => metric);

const client = new ReactNativeClient({
...getDefaultTestClientOptions({
dsn: EXAMPLE_DSN,
enableMetrics: false,
beforeSendMetric,
}),
});

setCurrentClient(client);
client.init();

// Send a metric
metrics.count('test_metric', 1);

jest.advanceTimersByTime(10000);
expect(beforeSendMetric).not.toHaveBeenCalled();
});

it('is called when enableMetrics is undefined (metrics are enabled by default)', async () => {
const beforeSendMetric = jest.fn(metric => metric);

const client = new ReactNativeClient({
...getDefaultTestClientOptions({
dsn: EXAMPLE_DSN,
beforeSendMetric,
}),
});

setCurrentClient(client);
client.init();

// Send a metric
metrics.count('test_metric', 1);

jest.advanceTimersByTime(10000);
expect(beforeSendMetric).toHaveBeenCalled();
});

it('allows beforeSendMetric to modify metrics when enableMetrics is true', async () => {
const beforeSendMetric = jest.fn(metric => {
// Modify the metric
return { ...metric, name: 'modified_metric' };
});

const client = new ReactNativeClient({
...getDefaultTestClientOptions({
dsn: EXAMPLE_DSN,
enableMetrics: true,
beforeSendMetric,
}),
});

setCurrentClient(client);
client.init();

// Send a metric
metrics.count('test_metric', 1);

jest.advanceTimersByTime(10000);
expect(beforeSendMetric).toHaveBeenCalled();
const modifiedMetric = beforeSendMetric.mock.results[0]?.value;
expect(modifiedMetric).toBeDefined();
expect(modifiedMetric.name).toBe('modified_metric');
});

it('allows beforeSendMetric to drop metrics by returning null', async () => {
const beforeSendMetric = jest.fn(() => null);

const client = new ReactNativeClient({
...getDefaultTestClientOptions({
dsn: EXAMPLE_DSN,
enableMetrics: true,
beforeSendMetric,
}),
});

setCurrentClient(client);
client.init();

// Send a metric
metrics.count('test_metric', 1);

// Advance timers
jest.advanceTimersByTime(10000);
expect(beforeSendMetric).toHaveBeenCalled();
expect(beforeSendMetric.mock.results[0]?.value).toBeNull();
});
});

describe('metrics API', () => {
it('metrics.count works when enableMetrics is true', () => {
const client = new ReactNativeClient({
...getDefaultTestClientOptions({
dsn: EXAMPLE_DSN,
enableMetrics: true,
}),
});

setCurrentClient(client);
client.init();

expect(() => {
metrics.count('test_metric', 1);
}).not.toThrow();
});

it('metrics can be sent with tags', async () => {
const beforeSendMetric = jest.fn(metric => metric);

const client = new ReactNativeClient({
...getDefaultTestClientOptions({
dsn: EXAMPLE_DSN,
enableMetrics: true,
beforeSendMetric,
}),
});

setCurrentClient(client);
client.init();

// Send a metric with tags
metrics.count('test_metric', 1, {
attributes: { environment: 'test' },
});

jest.advanceTimersByTime(10000);
expect(beforeSendMetric).toHaveBeenCalled();
const sentMetric = beforeSendMetric.mock.calls[0]?.[0];
expect(sentMetric).toBeDefined();
});
});
});
18 changes: 18 additions & 0 deletions packages/core/test/wrapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,24 @@ describe('Tests Native Wrapper', () => {
expect(NATIVE.enableNative).toBe(true);
});

test('filter beforeSendMetric when initializing Native SDK', async () => {
await NATIVE.initNativeSdk({
dsn: 'test',
enableNative: true,
autoInitializeNativeSdk: true,
beforeSendMetric: jest.fn(),
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('beforeSendMetric');
expect(NATIVE.enableNative).toBe(true);
});

test('filter integrations when initializing Native SDK', async () => {
await NATIVE.initNativeSdk({
dsn: 'test',
Expand Down
26 changes: 25 additions & 1 deletion samples/expo/app/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,30 @@ export default function TabOneScreen() {
}}
/>
</View>
<View style={styles.buttonWrapper}>
<Button
title="Send count metric"
onPress={() => {
Sentry.metrics.count('count_metric', 1);
}}
/>
</View>
<View style={styles.buttonWrapper}>
<Button
title="Send distribution metric"
onPress={() => {
Sentry.metrics.count('distribution_metric', 100);
}}
/>
</View>
<View style={styles.buttonWrapper}>
<Button
title="Send count metric with attributes"
onPress={() => {
Sentry.metrics.count('count_metric', 1, { attributes: { from_test_app: true } });
}}
/>
</View>
<View style={styles.buttonWrapper}>
<Button
title="Flush"
Expand Down Expand Up @@ -202,7 +226,7 @@ export default function TabOneScreen() {
Sentry.logger.warn('expo warn log');
Sentry.logger.error('expo error log');

Sentry.logger.info('expo info log with data', { database: 'admin', number: 123, obj: { password: 'admin'} });
Sentry.logger.info('expo info log with data', { database: 'admin', number: 123, obj: { password: 'admin' } });
}}
/>
</View>
Expand Down
5 changes: 5 additions & 0 deletions samples/expo/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ Sentry.init({
console.log('Transaction beforeSend:', event.event_id);
return event;
},
beforeSendMetric: (metric: Sentry.Metric) => {
console.log('Metric beforeSend:', metric.name, metric.value);
return metric;
},
enableMetrics: true,
// This will be called with a boolean `didCallNativeInit` when the native SDK has been contacted.
onReady: ({ didCallNativeInit }) => {
console.log('onReady called with didCallNativeInit:', didCallNativeInit);
Expand Down
4 changes: 4 additions & 0 deletions samples/react-native-macos/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ Sentry.init({
logWithoutTracing('Transaction beforeSend:', event.event_id);
return event;
},
beforeSendMetric(metric: Sentry.Metric) {
logWithoutTracing('Metric beforeSend:', metric.name, metric.value);
return metric;
},
// This will be called with a boolean `didCallNativeInit` when the native SDK has been contacted.
onReady: ({ didCallNativeInit }) => {
logWithoutTracing(
Expand Down
18 changes: 18 additions & 0 deletions samples/react-native-macos/src/Screens/ErrorsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,24 @@ const ErrorsScreen = (_props: Props) => {
setScopeProperties();
}}
/>
<Button
title="Send count metric"
onPress={() => {
Sentry.metrics.count('count_metric', 1);
}}
/>
<Button
title="Send distribution metric"
onPress={() => {
Sentry.metrics.count('distribution_metric', 100);
}}
/>
<Button
title="Send distribution metric"
onPress={() => {
Sentry.metrics.count('distribution_metric', 100);
}}
/>
<Button
title="Flush"
onPress={async () => {
Expand Down
5 changes: 5 additions & 0 deletions samples/react-native/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ Sentry.init({
logWithoutTracing('Transaction beforeSend:', event.event_id);
return event;
},
beforeSendMetric(metric: Sentry.Metric) {
logWithoutTracing('Metric beforeSend:', metric.name, metric.value);
return metric;
},
// This will be called with a boolean `didCallNativeInit` when the native SDK has been contacted.
onReady: ({ didCallNativeInit }) => {
logWithoutTracing(
Expand Down Expand Up @@ -167,6 +171,7 @@ Sentry.init({
// This should be disabled when manually initializing the native SDK
// Note that options from JS are not passed to the native SDKs when initialized manually
autoInitializeNativeSdk: true,
enableMetrics: true,
});

function BottomTabsNavigator() {
Expand Down
18 changes: 18 additions & 0 deletions samples/react-native/src/Screens/ErrorsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,24 @@ const ErrorsScreen = (_props: Props) => {
setScopeProperties();
}}
/>
<Button
title="Send count metric"
onPress={() => {
Sentry.metrics.count('count_metric', 1);
}}
/>
<Button
title="Send distribution metric"
onPress={() => {
Sentry.metrics.count('distribution_metric', 100);
}}
/>
<Button
title="Send distribution metric"
onPress={() => {
Sentry.metrics.count('distribution_metric', 100);
}}
/>
<Button
title="Flush"
onPress={async () => {
Expand Down
Loading