Skip to content

Commit 967f2a9

Browse files
authored
Merge pull request #33 from contentpass/chore-add-more-details-in-sentry
chore: add more details in sentry
2 parents 89c539e + b9234e9 commit 967f2a9

File tree

4 files changed

+116
-50
lines changed

4 files changed

+116
-50
lines changed

src/Contentpass.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ describe('Contentpass', () => {
5858
let sendStatsSpy: jest.SpyInstance;
5959
let sendPageViewEventSpy: jest.SpyInstance;
6060
let enableLoggerSpy: jest.SpyInstance;
61+
let initSentrySpy: jest.SpyInstance;
6162

6263
beforeEach(() => {
6364
jest.useFakeTimers({ now: NOW });
@@ -71,6 +72,10 @@ describe('Contentpass', () => {
7172
.spyOn(SentryIntegrationModule, 'reportError')
7273
.mockReturnValue(undefined);
7374

75+
initSentrySpy = jest
76+
.spyOn(SentryIntegrationModule, 'initSentry')
77+
.mockReturnValue(undefined);
78+
7479
oidcAuthStorageMock = {
7580
storeOidcAuthState: jest.fn(),
7681
getOidcAuthState: jest.fn(),
@@ -125,6 +130,12 @@ describe('Contentpass', () => {
125130
).toThrow('Sampling rate must be between 0 and 1');
126131
});
127132

133+
it('should initialise sentry', () => {
134+
expect(initSentrySpy).toHaveBeenCalledWith({
135+
propertyId: config.propertyId,
136+
});
137+
});
138+
128139
it('should initialise contentpass state', () => {
129140
const contentpassStates: ContentpassState[] = [];
130141
contentpass.registerObserver((state) => {

src/Contentpass.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { RefreshTokenStrategy } from './types/RefreshTokenStrategy';
1616
import fetchContentpassToken from './contentpassTokenUtils/fetchContentpassToken';
1717
import validateSubscription from './contentpassTokenUtils/validateSubscription';
1818
import type { ContentpassConfig } from './types/ContentpassConfig';
19-
import { reportError, setSentryExtraAttribute } from './sentryIntegration';
19+
import { initSentry, reportError } from './sentryIntegration';
2020
import sendStats from './countImpressionUtils/sendStats';
2121
import sendPageViewEvent from './countImpressionUtils/sendPageViewEvent';
2222
import logger, { enableLogger } from './logger';
@@ -62,7 +62,7 @@ export default class Contentpass implements ContentpassInterface {
6262
this.samplingRate = config.samplingRate || DEFAULT_SAMPLING_RATE;
6363
this.authStateStorage = new OidcAuthStateStorage(config.propertyId);
6464
this.config = config;
65-
setSentryExtraAttribute('propertyId', config.propertyId);
65+
initSentry({ propertyId: config.propertyId });
6666
this.initialiseAuthState();
6767
}
6868

src/sentryIntegration.test.ts

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,72 @@
1-
import * as Sentry from '@sentry/react-native';
21
import { defaultStackParser, makeFetchTransport } from '@sentry/react';
32
import * as SentryReactNativeModule from '@sentry/react-native';
4-
import { reportError, setSentryExtraAttribute } from './sentryIntegration';
5-
6-
jest.mock('@sentry/react-native', () => {
7-
const scope = {
8-
setClient: jest.fn(),
9-
addBreadcrumb: jest.fn(),
10-
captureException: jest.fn(),
11-
setExtra: jest.fn(),
12-
};
13-
return {
14-
ReactNativeClient: jest.fn().mockImplementation(() => ({
15-
init: jest.fn(),
16-
captureException: jest.fn(),
17-
})),
18-
Scope: jest.fn().mockImplementation(() => scope),
3+
import {
4+
__internal_reset_sentry_scope,
5+
initSentry,
6+
reportError,
7+
} from './sentryIntegration';
8+
import logger from './logger';
199

20-
// Only for internal testing
21-
__test_getScope: () => scope,
10+
jest.mock('react-native', () => {
11+
return {
12+
Platform: {
13+
OS: 'android',
14+
Version: '10.1.2',
15+
},
2216
};
2317
});
2418

25-
describe('sentryScope', () => {
19+
describe('sentryIntegration', () => {
2620
let addBreadcrumbMock: jest.Mock;
2721
let captureExceptionMock: jest.Mock;
22+
let setTagsMock: jest.Mock;
23+
let ReactNativeClientSpy: jest.SpyInstance;
24+
let ScopeSpy: jest.SpyInstance;
2825

2926
beforeEach(() => {
3027
addBreadcrumbMock = jest.fn();
3128
captureExceptionMock = jest.fn();
32-
jest.spyOn(SentryReactNativeModule, 'Scope').mockReturnValue({
29+
setTagsMock = jest.fn();
30+
ScopeSpy = jest.spyOn(SentryReactNativeModule, 'Scope').mockReturnValue({
3331
setClient: jest.fn(),
32+
init: jest.fn(),
3433
addBreadcrumb: addBreadcrumbMock,
3534
captureException: captureExceptionMock,
35+
setTags: setTagsMock,
3636
} as any);
37+
38+
ReactNativeClientSpy = jest
39+
.spyOn(SentryReactNativeModule, 'ReactNativeClient')
40+
.mockImplementation(
41+
() =>
42+
({
43+
init: jest.fn(),
44+
}) as any
45+
);
46+
47+
jest.spyOn(logger, 'error').mockImplementation(() => {});
48+
jest.spyOn(logger, 'warn').mockImplementation(() => {});
3749
});
3850

3951
afterEach(() => {
4052
jest.restoreAllMocks();
4153
jest.resetAllMocks();
54+
__internal_reset_sentry_scope();
55+
});
56+
57+
it('should not initialise sentry scope if already initialised', () => {
58+
initSentry({ propertyId: 'test-id' });
59+
initSentry({ propertyId: 'test-id' });
60+
61+
expect(logger.warn).toHaveBeenCalledWith('Sentry already initialized');
62+
expect(ReactNativeClientSpy).toHaveBeenCalledTimes(1);
63+
expect(ScopeSpy).toHaveBeenCalledTimes(1);
4264
});
4365

4466
it('should initialise sentry scope with correct options', () => {
45-
expect(Sentry.ReactNativeClient).toHaveBeenCalledWith({
67+
initSentry({ propertyId: 'test-id' });
68+
69+
expect(ReactNativeClientSpy).toHaveBeenCalledWith({
4670
attachStacktrace: true,
4771
autoInitializeNativeSdk: false,
4872
dsn: 'https://[email protected]/8',
@@ -57,52 +81,58 @@ describe('sentryScope', () => {
5781
enableStallTracking: true,
5882
enableUserInteractionTracing: false,
5983
enableWatchdogTerminationTracking: false,
84+
environment: 'development',
6085
maxQueueSize: 30,
6186
parentSpanIsAlwaysRootSpan: true,
6287
patchGlobalPromise: true,
6388
sendClientReports: true,
64-
integrations: [],
89+
integrations: undefined,
6590
stackParser: defaultStackParser,
6691
transport: makeFetchTransport,
6792
});
93+
94+
expect(setTagsMock).toHaveBeenCalledWith({
95+
OS: 'android',
96+
platformVersion: '10.1.2',
97+
propertyId: 'test-id',
98+
});
6899
});
69100

70101
describe('reportError', () => {
71102
it('should add breadcrumb with message if provided', () => {
103+
initSentry({ propertyId: 'test-id' });
72104
const err = new Error('test');
73105
const msg = 'test message';
74-
// @ts-ignore
75-
const { addBreadcrumb, captureException } = Sentry.__test_getScope();
106+
76107
reportError(err, { msg });
77108

78-
expect(addBreadcrumb).toHaveBeenCalledWith({
109+
expect(addBreadcrumbMock).toHaveBeenCalledWith({
79110
category: 'Error',
80111
message: msg,
81112
level: 'log',
82113
});
83-
expect(captureException).toHaveBeenCalledWith(err);
114+
expect(captureExceptionMock).toHaveBeenCalledWith(err);
84115
});
85116

86117
it('should not add breadcrumb if message is not provided', () => {
118+
initSentry({ propertyId: 'test-id' });
87119
const err = new Error('test');
88-
// @ts-ignore
89-
const { addBreadcrumb, captureException } = Sentry.__test_getScope();
120+
90121
reportError(err);
91122

92-
expect(addBreadcrumb).not.toHaveBeenCalled();
93-
expect(captureException).toHaveBeenCalledWith(err);
123+
expect(addBreadcrumbMock).not.toHaveBeenCalled();
124+
expect(captureExceptionMock).toHaveBeenCalledWith(err);
94125
});
95-
});
96126

97-
describe('setSentryExtraAttribute', () => {
98-
it('should set extra attribute', () => {
99-
const key = 'testKey';
100-
const value = 'testValue';
101-
// @ts-ignore
102-
const { setExtra } = Sentry.__test_getScope();
103-
setSentryExtraAttribute(key, value);
127+
it('should not throw error when reportError is called before initSentry', () => {
128+
const err = new Error('test');
129+
const msg = 'test message';
130+
131+
reportError(err, { msg });
104132

105-
expect(setExtra).toHaveBeenCalledWith(key, value);
133+
expect(logger.error).toHaveBeenCalledWith({ err }, msg);
134+
expect(addBreadcrumbMock).not.toHaveBeenCalled();
135+
expect(captureExceptionMock).not.toHaveBeenCalledWith(err);
106136
});
107137
});
108138
});

src/sentryIntegration.ts

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as Sentry from '@sentry/react-native';
22
import { defaultStackParser, makeFetchTransport } from '@sentry/react';
33
import { getDefaultIntegrations } from '@sentry/react-native/dist/js/integrations/default';
44
import logger from './logger';
5+
import { Platform } from 'react-native';
56

67
// as it's only the open source package, we want to have minimal sentry configuration here to not override sentry instance,
78
// which can be used in the application
@@ -27,24 +28,48 @@ const options = {
2728
stackParser: defaultStackParser,
2829
transport: makeFetchTransport,
2930
integrations: [],
31+
environment: __DEV__ ? 'development' : 'production',
3032
};
3133

32-
const sentryClient = new Sentry.ReactNativeClient({
33-
...options,
34-
integrations: getDefaultIntegrations(options),
35-
});
34+
type InitSentryArgs = {
35+
propertyId: string;
36+
};
37+
38+
let sentryScope: Sentry.Scope | null = null;
39+
40+
export const initSentry = (args: InitSentryArgs) => {
41+
if (sentryScope) {
42+
logger.warn('Sentry already initialized');
43+
return;
44+
}
45+
46+
const sentryClient = new Sentry.ReactNativeClient({
47+
...options,
48+
integrations: getDefaultIntegrations(options),
49+
});
3650

37-
const sentryScope = new Sentry.Scope();
38-
sentryScope.setClient(sentryClient);
51+
sentryScope = new Sentry.Scope();
52+
sentryScope.setClient(sentryClient);
3953

40-
sentryClient.init();
54+
sentryClient.init();
55+
56+
sentryScope.setTags({
57+
propertyId: args.propertyId,
58+
OS: Platform.OS,
59+
platformVersion: Platform.Version,
60+
});
61+
};
4162

4263
type ReportErrorOptions = {
4364
msg?: string;
4465
};
4566

4667
export const reportError = (err: Error, { msg }: ReportErrorOptions = {}) => {
4768
logger.error({ err }, msg || 'Unexpected error');
69+
if (!sentryScope) {
70+
return;
71+
}
72+
4873
if (msg) {
4974
sentryScope.addBreadcrumb({
5075
category: 'Error',
@@ -56,6 +81,6 @@ export const reportError = (err: Error, { msg }: ReportErrorOptions = {}) => {
5681
sentryScope.captureException(err);
5782
};
5883

59-
export const setSentryExtraAttribute = (key: string, value: string) => {
60-
sentryScope.setExtra(key, value);
84+
export const __internal_reset_sentry_scope = () => {
85+
sentryScope = null;
6186
};

0 commit comments

Comments
 (0)