Skip to content

Commit 6e8584e

Browse files
feat(sdk): Automatically detect environment if not set by User (#3362)
1 parent 1b92a08 commit 6e8584e

File tree

6 files changed

+151
-7
lines changed

6 files changed

+151
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Features
66

7+
- Automatically detect environment if not set ([#3362](https://github.com/getsentry/sentry-react-native/pull/3362))
78
- Send Source Maps Debug ID for symbolicated Profiles ([#3343](https://github.com/getsentry/sentry-react-native/pull/3343))
89

910
### Fixes

src/js/profiling/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { DebugImage, Envelope, Event, Profile } from '@sentry/types';
22
import { forEachEnvelopeItem, GLOBAL_OBJ, logger } from '@sentry/utils';
33

4+
import { getDefaultEnvironment } from '../utils/environment';
45
import { DEFAULT_BUNDLE_NAME } from './hermes';
56
import type { RawThreadCpuProfile } from './types';
67

@@ -65,7 +66,7 @@ export function createProfilingEvent(profile: RawThreadCpuProfile, event: Event)
6566

6667
return createProfilePayload(profile, {
6768
release: event.release || '',
68-
environment: event.environment || '',
69+
environment: event.environment || getDefaultEnvironment(),
6970
event_id: event.event_id || '',
7071
transaction: event.transaction || '',
7172
start_timestamp: event.start_timestamp ? event.start_timestamp * 1000 : Date.now(),

src/js/sdk.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable complexity */
12
import type { Scope } from '@sentry/core';
23
import { getIntegrationsToSetup, hasTracingEnabled, Hub, initAndBind, makeMain, setExtra } from '@sentry/core';
34
import { HttpClient } from '@sentry/integrations';
@@ -33,6 +34,7 @@ import { TouchEventBoundary } from './touchevents';
3334
import { ReactNativeProfiler, ReactNativeTracing } from './tracing';
3435
import { DEFAULT_BUFFER_SIZE, makeNativeTransportFactory } from './transports/native';
3536
import { makeUtf8TextEncoder } from './transports/TextEncoder';
37+
import { getDefaultEnvironment } from './utils/environment';
3638
import { safeFactory, safeTracesSampler } from './utils/safe';
3739
import { NATIVE } from './wrapper';
3840

@@ -95,6 +97,9 @@ export function init(passedOptions: ReactNativeOptions): void {
9597
initialScope: safeFactory(passedOptions.initialScope, { loggerMessage: 'The initialScope threw an error' }),
9698
tracesSampler: safeTracesSampler(passedOptions.tracesSampler),
9799
};
100+
if (!('environment' in options)) {
101+
options.environment = getDefaultEnvironment();
102+
}
98103

99104
const defaultIntegrations: Integration[] = passedOptions.defaultIntegrations || [];
100105
if (passedOptions.defaultIntegrations === undefined) {

src/js/utils/environment.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,8 @@ export function getHermesVersion(): string | undefined {
3535
RN_GLOBAL_OBJ.HermesInternal.getRuntimeProperties()['OSS Release Version']
3636
);
3737
}
38+
39+
/** Returns default environment based on __DEV__ */
40+
export function getDefaultEnvironment(): 'development' | 'production' {
41+
return typeof __DEV__ !== 'undefined' && __DEV__ ? 'development' : 'production';
42+
}

test/profiling/integration.test.ts

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import type { Envelope, Event, Profile, Transaction, Transport } from '@sentry/t
77

88
import * as Sentry from '../../src/js';
99
import { HermesProfiling } from '../../src/js/integrations';
10+
import type { NativeDeviceContextsResponse } from '../../src/js/NativeRNSentry';
1011
import type * as Hermes from '../../src/js/profiling/hermes';
11-
import { isHermesEnabled } from '../../src/js/utils/environment';
12+
import { getDefaultEnvironment, isHermesEnabled } from '../../src/js/utils/environment';
1213
import { RN_GLOBAL_OBJ } from '../../src/js/utils/worldwide';
1314
import { MOCK_DSN } from '../mockDsn';
1415
import { envelopeItemPayload, envelopeItems } from '../testutils';
@@ -35,7 +36,7 @@ describe('profiling integration', () => {
3536
});
3637

3738
test('should start profile if there is a transaction running when integration is created', () => {
38-
mock = initTestClient(false);
39+
mock = initTestClient({ withProfiling: false });
3940
jest.runAllTimers();
4041
jest.clearAllMocks();
4142

@@ -65,6 +66,96 @@ describe('profiling integration', () => {
6566
]);
6667
});
6768

69+
describe('environment', () => {
70+
beforeEach(() => {
71+
(getDefaultEnvironment as jest.Mock).mockReturnValue('mocked');
72+
mockWrapper.NATIVE.fetchNativeDeviceContexts.mockResolvedValue(<NativeDeviceContextsResponse>{
73+
environment: 'native',
74+
});
75+
});
76+
77+
const expectTransactionWithEnvironment = (envelope: Envelope | undefined, env: string | undefined) => {
78+
const transactionEvent = envelope?.[envelopeItems][0][envelopeItemPayload] as Event;
79+
expect(transactionEvent).toEqual(
80+
expect.objectContaining<Partial<Event>>({
81+
environment: env,
82+
}),
83+
);
84+
};
85+
86+
const expectProfileWithEnvironment = (envelope: Envelope | undefined, env: string | undefined) => {
87+
const profileEvent = (envelope?.[envelopeItems][1] as [{ type: 'profile' }, Profile])[1];
88+
expect(profileEvent).toEqual(
89+
expect.objectContaining<Partial<Profile>>({
90+
environment: env,
91+
}),
92+
);
93+
};
94+
95+
test('should use default environment for transaction and profile', () => {
96+
mock = initTestClient();
97+
98+
const transaction: Transaction = Sentry.startTransaction({
99+
name: 'test-name',
100+
});
101+
transaction.finish();
102+
103+
jest.runAllTimers();
104+
105+
const envelope: Envelope | undefined = mock.transportSendMock.mock.lastCall?.[0];
106+
expectTransactionWithEnvironment(envelope, 'mocked');
107+
expectProfileWithEnvironment(envelope, 'mocked');
108+
});
109+
110+
test('should use native environment for transaction and profile if user value is nullish', () => {
111+
mock = initTestClient({ withProfiling: true, environment: '' });
112+
113+
const transaction: Transaction = Sentry.startTransaction({
114+
name: 'test-name',
115+
});
116+
transaction.finish();
117+
118+
jest.runAllTimers();
119+
120+
const envelope: Envelope | undefined = mock.transportSendMock.mock.lastCall?.[0];
121+
expectTransactionWithEnvironment(envelope, 'native');
122+
expectProfileWithEnvironment(envelope, 'native');
123+
});
124+
125+
test('should keep nullish for transaction and profile uses default', () => {
126+
mockWrapper.NATIVE.fetchNativeDeviceContexts.mockResolvedValue(<NativeDeviceContextsResponse>{
127+
environment: undefined,
128+
});
129+
mock = initTestClient({ withProfiling: true, environment: undefined });
130+
131+
const transaction: Transaction = Sentry.startTransaction({
132+
name: 'test-name',
133+
});
134+
transaction.finish();
135+
136+
jest.runAllTimers();
137+
138+
const envelope: Envelope | undefined = mock.transportSendMock.mock.lastCall?.[0];
139+
expectTransactionWithEnvironment(envelope, undefined);
140+
expectProfileWithEnvironment(envelope, 'mocked');
141+
});
142+
143+
test('should keep custom environment for transaction and profile', () => {
144+
mock = initTestClient({ withProfiling: true, environment: 'custom' });
145+
146+
const transaction: Transaction = Sentry.startTransaction({
147+
name: 'test-name',
148+
});
149+
transaction.finish();
150+
151+
jest.runAllTimers();
152+
153+
const envelope: Envelope | undefined = mock.transportSendMock.mock.lastCall?.[0];
154+
expectTransactionWithEnvironment(envelope, 'custom');
155+
expectProfileWithEnvironment(envelope, 'custom');
156+
});
157+
});
158+
68159
describe('with profiling enabled', () => {
69160
beforeEach(() => {
70161
mock = initTestClient();
@@ -231,17 +322,24 @@ function getCurrentHermesProfilingIntegration(): TestHermesIntegration {
231322
return integration as unknown as TestHermesIntegration;
232323
}
233324

234-
function initTestClient(withProfiling: boolean = true): {
325+
function initTestClient(
326+
testOptions: {
327+
withProfiling?: boolean;
328+
environment?: string;
329+
} = {
330+
withProfiling: true,
331+
},
332+
): {
235333
transportSendMock: jest.Mock<ReturnType<Transport['send']>, Parameters<Transport['send']>>;
236334
} {
237335
const transportSendMock = jest.fn<ReturnType<Transport['send']>, Parameters<Transport['send']>>();
238-
Sentry.init({
336+
const options: Sentry.ReactNativeOptions = {
239337
dsn: MOCK_DSN,
240338
_experiments: {
241339
profilesSampleRate: 1,
242340
},
243341
integrations: integrations => {
244-
if (!withProfiling) {
342+
if (!testOptions.withProfiling) {
245343
return integrations.filter(i => i.name !== 'HermesProfiling');
246344
}
247345
return integrations;
@@ -250,7 +348,11 @@ function initTestClient(withProfiling: boolean = true): {
250348
send: transportSendMock.mockResolvedValue(undefined),
251349
flush: jest.fn().mockResolvedValue(true),
252350
}),
253-
});
351+
};
352+
if ('environment' in testOptions) {
353+
options.environment = testOptions.environment;
354+
}
355+
Sentry.init(options);
254356

255357
// In production integrations are setup only once, but in the tests we want them to setup on every init
256358
const integrations = Sentry.getCurrentHub().getClient()?.getOptions().integrations;

test/sdk.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,36 @@ describe('Tests the SDK functionality', () => {
184184
});
185185
});
186186

187+
describe('environment', () => {
188+
it('detect development environment', () => {
189+
init({
190+
enableNative: true,
191+
});
192+
expect(usedOptions()?.environment).toBe('development');
193+
});
194+
195+
it('uses custom environment', () => {
196+
init({
197+
environment: 'custom',
198+
});
199+
expect(usedOptions()?.environment).toBe('custom');
200+
});
201+
202+
it('it keeps empty string environment', () => {
203+
init({
204+
environment: '',
205+
});
206+
expect(usedOptions()?.environment).toBe('');
207+
});
208+
209+
it('it keeps undefined environment', () => {
210+
init({
211+
environment: undefined,
212+
});
213+
expect(usedOptions()?.environment).toBe(undefined);
214+
});
215+
});
216+
187217
describe('transport options buffer size', () => {
188218
it('uses default transport options buffer size', () => {
189219
init({

0 commit comments

Comments
 (0)