Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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 ([#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.

We could mention that the feature is beta (similar to feedback and SR in the past)

Suggested change
- Adds metrics ([#5402](https://github.com/getsentry/sentry-react-native/pull/5402))
- Adds Metrics Beta ([#5402](https://github.com/getsentry/sentry-react-native/pull/5402))

Copy link
Collaborator

Choose a reason for hiding this comment

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

also a small snippet on how to use it would be nice here

Copy link
Collaborator

Choose a reason for hiding this comment

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

@antonis do we want to keep it as beta since it no longer is in beta for JavaScript?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would advocate towards shipping as beta since the product is still labeled as beta in JS sidebar


### Dependencies

- Bump Android SDK from v8.27.0 to v8.27.1 ([#5404](https://github.com/getsentry/sentry-react-native/pull/5404))
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
179 changes: 179 additions & 0 deletions packages/core/test/metrics.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { getClient, metrics, setCurrentClient } from '@sentry/core';
import { ReactNativeClient } from '../src/js';
import { getDefaultTestClientOptions, TestClient } 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(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
getClient()?.close();
});

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' } });
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unrelated auto-formatting change

}}
/>
</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
4 changes: 4 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;
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Wdyt of enabling metrics for the sample app and adding an example in the app (e.g. a button or screen that uses metrics)

Suggested change
},
},
enableMetrics: true,

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am surprised it is not on the experimental field

// This will be called with a boolean `didCallNativeInit` when the native SDK has been contacted.
onReady: ({ didCallNativeInit }) => {
logWithoutTracing(
Expand Down
Loading