Skip to content

Commit 3eb8dbf

Browse files
committed
feat: Add initWith method + reactnativeprofiler
1 parent 0ce7ead commit 3eb8dbf

File tree

8 files changed

+144
-46
lines changed

8 files changed

+144
-46
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,7 @@ wheelhouse
6666

6767
# Android
6868
.idea/
69+
70+
# Yalc
71+
.yalc
72+
yalc.lock

sample/src/App.tsx

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ const reactNavigationV5Instrumentation = new Sentry.ReactNavigationV5Instrumenta
2525
routeChangeTimeoutMs: 500, // How long it will wait for the route change to complete. Default is 1000ms
2626
},
2727
);
28-
29-
Sentry.init({
28+
const options = {
3029
// Replace the example DSN below with your own DSN:
3130
dsn: SENTRY_INTERNAL_DSN,
3231
debug: true,
@@ -63,46 +62,38 @@ Sentry.init({
6362
// otherwise they will not work.
6463
release: packageVersion,
6564
dist: `${packageVersion}.0`,
66-
});
65+
};
66+
67+
// Sentry.init(options);
6768

6869
const Stack = createStackNavigator();
6970

7071
const App = () => {
7172
const navigation = React.useRef<NavigationContainerRef>();
7273

7374
return (
74-
<Sentry.Profiler name="App">
75-
<Provider store={store}>
76-
<NavigationContainer
77-
ref={navigation}
78-
onReady={() => {
79-
reactNavigationV5Instrumentation.registerNavigationContainer(
80-
navigation,
81-
);
82-
}}>
83-
<Stack.Navigator>
84-
<Stack.Screen name="Home" component={HomeScreen} />
85-
<Stack.Screen name="Tracker" component={TrackerScreen} />
86-
<Stack.Screen
87-
name="ManualTracker"
88-
component={ManualTrackerScreen}
89-
/>
90-
<Stack.Screen
91-
name="PerformanceTiming"
92-
component={PerformanceTimingScreen}
93-
/>
94-
<Stack.Screen name="Redux" component={ReduxScreen} />
95-
<Stack.Screen
96-
name="EndToEndTests"
97-
component={EndToEndTestsScreen}
98-
/>
99-
</Stack.Navigator>
100-
</NavigationContainer>
101-
</Provider>
102-
</Sentry.Profiler>
75+
<Provider store={store}>
76+
<NavigationContainer
77+
ref={navigation}
78+
onReady={() => {
79+
reactNavigationV5Instrumentation.registerNavigationContainer(
80+
navigation,
81+
);
82+
}}>
83+
<Stack.Navigator>
84+
<Stack.Screen name="Home" component={HomeScreen} />
85+
<Stack.Screen name="Tracker" component={TrackerScreen} />
86+
<Stack.Screen name="ManualTracker" component={ManualTrackerScreen} />
87+
<Stack.Screen
88+
name="PerformanceTiming"
89+
component={PerformanceTimingScreen}
90+
/>
91+
<Stack.Screen name="Redux" component={ReduxScreen} />
92+
<Stack.Screen name="EndToEndTests" component={EndToEndTestsScreen} />
93+
</Stack.Navigator>
94+
</NavigationContainer>
95+
</Provider>
10396
);
10497
};
10598

106-
export default Sentry.withTouchEventBoundary(App, {
107-
ignoreNames: ['Provider', 'UselessName', /^SomeRegex/],
108-
});
99+
export default Sentry.initWith(App, options);

src/js/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,18 @@ import { SDK_NAME, SDK_VERSION } from "./version";
5959
export { ReactNativeBackend } from "./backend";
6060
export { ReactNativeOptions } from "./options";
6161
export { ReactNativeClient } from "./client";
62-
// eslint-disable-next-line deprecation/deprecation
63-
export { init, setDist, setRelease, nativeCrash, flush, close } from "./sdk";
62+
63+
export {
64+
init,
65+
initWith,
66+
// eslint-disable-next-line deprecation/deprecation
67+
setDist,
68+
// eslint-disable-next-line deprecation/deprecation
69+
setRelease,
70+
nativeCrash,
71+
flush,
72+
close,
73+
} from "./sdk";
6474
export { TouchEventBoundary, withTouchEventBoundary } from "./touchevents";
6575

6676
export {

src/js/options.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { BrowserOptions } from "@sentry/react";
2+
import { ErrorBoundaryProps } from "@sentry/react/dist/errorboundary";
3+
import { ProfilerProps } from "@sentry/react/dist/profiler";
4+
5+
import { TouchEventBoundaryProps } from "./touchevents";
26

37
/**
48
* Configuration options for the Sentry ReactNative SDK.
@@ -72,3 +76,9 @@ export interface ReactNativeOptions extends BrowserOptions {
7276
/** Enable auto performance tracking by default. */
7377
enableAutoPerformanceTracking?: boolean;
7478
}
79+
80+
export interface ReactNativeWrapperOptions extends ReactNativeOptions {
81+
errorBoundaryProps?: ErrorBoundaryProps;
82+
profilerProps?: ProfilerProps;
83+
touchEventBoundaryProps?: TouchEventBoundaryProps;
84+
}

src/js/sdk.ts renamed to src/js/sdk.tsx

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { initAndBind, setExtra } from "@sentry/core";
22
import { Hub, makeMain } from "@sentry/hub";
33
import { RewriteFrames } from "@sentry/integrations";
4-
import { defaultIntegrations, getCurrentHub } from "@sentry/react";
4+
import {
5+
defaultIntegrations,
6+
ErrorBoundary,
7+
getCurrentHub,
8+
} from "@sentry/react";
59
import { StackFrame } from "@sentry/types";
610
import { getGlobalObject, logger } from "@sentry/utils";
11+
import * as React from "react";
712

813
import { ReactNativeClient } from "./client";
914
import {
@@ -13,9 +18,10 @@ import {
1318
Release,
1419
StallTracking,
1520
} from "./integrations";
16-
import { ReactNativeOptions } from "./options";
21+
import { ReactNativeOptions, ReactNativeWrapperOptions } from "./options";
1722
import { ReactNativeScope } from "./scope";
18-
import { ReactNativeTracing } from "./tracing";
23+
import { TouchEventBoundary } from "./touchevents";
24+
import { ReactNativeProfiler, ReactNativeTracing } from "./tracing";
1925

2026
const IGNORED_DEFAULT_INTEGRATIONS = [
2127
"GlobalHandlers", // We will use the react-native internal handlers
@@ -31,9 +37,9 @@ const DEFAULT_OPTIONS: ReactNativeOptions = {
3137
};
3238

3339
/**
34-
* Inits the SDK
40+
* Inits the SDK and returns the final options.
3541
*/
36-
export function init(passedOptions: ReactNativeOptions): void {
42+
function _init<O = ReactNativeOptions>(passedOptions: O): O {
3743
const reactNativeHub = new Hub(undefined, new ReactNativeScope());
3844
makeMain(reactNativeHub);
3945

@@ -106,6 +112,44 @@ export function init(passedOptions: ReactNativeOptions): void {
106112
if (getGlobalObject<any>().HermesInternal) {
107113
getCurrentHub().setTag("hermes", "true");
108114
}
115+
116+
return options;
117+
}
118+
119+
/**
120+
* Inits the Sentry React Native SDK without any wrapping
121+
*/
122+
export function init(options: ReactNativeOptions): void {
123+
_init(options);
124+
}
125+
126+
/**
127+
* Inits the Sentry React Native SDK with automatic instrumentation and wrapped features.
128+
*/
129+
export function initWith(
130+
RootComponent: React.ComponentType,
131+
passedOptions: ReactNativeWrapperOptions
132+
): React.FC {
133+
const options = _init(passedOptions);
134+
135+
const profilerProps = {
136+
...options.profilerProps,
137+
name: RootComponent.displayName ?? "Root",
138+
};
139+
140+
const RootApp: React.FC = (appProps) => {
141+
return (
142+
<ErrorBoundary {...options.errorBoundaryProps}>
143+
<TouchEventBoundary {...options.touchEventBoundaryProps}>
144+
<ReactNativeProfiler {...profilerProps}>
145+
<RootComponent {...appProps} />
146+
</ReactNativeProfiler>
147+
</TouchEventBoundary>
148+
</ErrorBoundary>
149+
);
150+
};
151+
152+
return RootApp;
109153
}
110154

111155
/**

src/js/tracing/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export {
1212
ReactNavigationRoute,
1313
ReactNavigationTransactionContext,
1414
} from "./types";
15+
export { ReactNativeProfiler } from "./reactnativeprofiler";
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { getCurrentHub, Profiler } from "@sentry/react";
2+
3+
import { ReactNativeTracing } from "./reactnativetracing";
4+
5+
/**
6+
* Custom profiler for the React Native app root.
7+
*/
8+
export class ReactNativeProfiler extends Profiler {
9+
/**
10+
* Get the app root mount time.
11+
*/
12+
public componentDidMount(): void {
13+
super.componentDidMount();
14+
15+
const tracingIntegration = getCurrentHub().getIntegration(
16+
ReactNativeTracing
17+
);
18+
19+
if (this._mountSpan && tracingIntegration) {
20+
if (typeof this._mountSpan.endTimestamp !== "undefined") {
21+
// The first root component mount is the app start finish.
22+
tracingIntegration.onAppStartFinish(this._mountSpan.endTimestamp);
23+
}
24+
}
25+
}
26+
}

src/js/tracing/reactnativetracing.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { StallTracking } from "../integrations";
2121
import { RoutingInstrumentationInstance } from "../tracing/routingInstrumentation";
2222
import { NATIVE } from "../wrapper";
2323
import { NativeFramesInstrumentation } from "./nativeframes";
24-
import { adjustTransactionDuration, getTimeOriginMilliseconds } from "./utils";
24+
import { adjustTransactionDuration } from "./utils";
2525

2626
export type BeforeNavigate = (
2727
context: TransactionContext
@@ -115,6 +115,7 @@ export class ReactNativeTracing implements Integration {
115115

116116
private _getCurrentHub?: () => Hub;
117117
private _awaitingAppStartData?: NativeAppStartResponse;
118+
private _appStartFinishTimestamp?: number;
118119

119120
public constructor(options: Partial<ReactNativeTracingOptions> = {}) {
120121
this.options = {
@@ -196,6 +197,13 @@ export class ReactNativeTracing implements Integration {
196197
this.nativeFramesInstrumentation?.onTransactionFinish(transaction);
197198
}
198199

200+
/**
201+
*
202+
*/
203+
public onAppStartFinish(endTimestamp: number): void {
204+
this._appStartFinishTimestamp = endTimestamp;
205+
}
206+
199207
/**
200208
* Instruments the app start measurements on the first route transaction.
201209
* Starts a route transaction if there isn't routing instrumentation.
@@ -235,18 +243,22 @@ export class ReactNativeTracing implements Integration {
235243
transaction: IdleTransaction,
236244
appStart: NativeAppStartResponse
237245
): void {
246+
if (!this._appStartFinishTimestamp) {
247+
logger.warn("App start was never finished.");
248+
return;
249+
}
250+
238251
const appStartTimeSeconds = appStart.appStartTime / 1000;
239-
const timeOriginSeconds = getTimeOriginMilliseconds() / 1000;
240252

241253
transaction.startChild({
242254
description: appStart.isColdStart ? "Cold App Start" : "Warm App Start",
243255
op: appStart.isColdStart ? "app.start.cold" : "app.start.warm",
244256
startTimestamp: appStartTimeSeconds,
245-
endTimestamp: timeOriginSeconds,
257+
endTimestamp: this._appStartFinishTimestamp,
246258
});
247259

248260
const appStartDurationMilliseconds =
249-
getTimeOriginMilliseconds() - appStart.appStartTime;
261+
this._appStartFinishTimestamp * 1000 - appStart.appStartTime;
250262

251263
transaction.setMeasurements(
252264
appStart.isColdStart

0 commit comments

Comments
 (0)