Automatic Instrumentation

@sentry/react-native provides automatic performance instrumentation out of the box when tracing is enabled.

Set Up

To make the most out of our automatic instrumentation, you should:

Wrap Your Root Component

Wrap your root component with Sentry to access the most Performance features.

App.js
Copied
export default Sentry.wrap(App);

Enable Routing Instrumentation

We currently provide three routing instrumentations out of the box to instrument route changes for:

If you have a custom routing instrumentation or use a routing library we don't yet support, you can use the bare bones routing instrumentation or create your own by extending it.

React Navigation

This instrumentation supports React Navigation version 5 and above. If you use React Navigation version 4, see the instrumentation for React Navigation V4.

Note that this routing instrumentation will create a transaction on every route change including goBack navigations.

Copied
import * as Sentry from "@sentry/react-native";

// Construct a new instrumentation instance. This is needed to communicate between the integration and React
const routingInstrumentation = new Sentry.ReactNavigationInstrumentation();

Sentry.init({
  ...
  integrations: [
    new Sentry.ReactNativeTracing({
      // Pass instrumentation to be used as `routingInstrumentation`
      routingInstrumentation,
      // ...
    }),
  ],
})

// Functional Component Example
const App = () => {
  // Create a ref for the navigation container
  const navigation = React.useRef();

  return (
    // Connect the ref to the navigation container
    <NavigationContainer
      ref={navigation}
      onReady={() => {
        // Register the navigation container with the instrumentation
        routingInstrumentation.registerNavigationContainer(navigation);
      }}>
      ...
    </NavigationContainer>
  );
};

// Class Component Example
class App extends React.Component {
  // Create a ref for the navigation container
  navigation = React.createRef();

  render() {
    return (
      // Connect the ref to the navigation container
      <NavigationContainer
        ref={this.navigation}
        onReady={() => {
          // Register the navigation container with the instrumentation
          routingInstrumentation.registerNavigationContainer(navigation);
        }}>
        ...
      </NavigationContainer>
    )
  }
}
Configuration

You can configure the instrumentation by passing an options object to the constructor:

Copied
new Sentry.ReactNavigationInstrumentation({ ... });
routeChangeTimeoutMs

How long the instrumentation will wait for the route to mount after a change has been initiated before the transaction is discarded. Default: 1000ms

React Navigation V4

Note that this routing instrumentation will create a transaction on every route change including goBack navigations.

Copied
// Construct a new instrumentation instance. This is needed to communicate between the integration and React
const routingInstrumentation = new Sentry.ReactNavigationV4Instrumentation();

Sentry.init({
  ...
  integrations: [
    new Sentry.ReactNativeTracing({
      // Pass instrumentation to be used as `routingInstrumentation`
      routingInstrumentation,
      ...
    }),
  ],
})

// Functional Component Example
const App = () => {
  // Create a ref for the navigation container
  const appContainer = React.useRef();

  // Register the navigation container with the instrumentation
  React.useEffect(() => {
    routingInstrumentation.registerAppContainer(appContainer);
  }, []);

  return (
    // Connect the ref to the navigation container
    <AppContainer ref={appContainer} />
  );
};

// Class Component Example
class App extends React.Component {
  // Create a ref for the navigation container
  appContainer = React.createRef();

  componentDidMount() {
    // Register the navigation container with the instrumentation
    routingInstrumentation.registerAppContainer(this.appContainer);
  }

  render() {
    return (
      // Connect the ref to the navigation container
      <AppContainer ref={this.appContainer} />
    )
  }
}
Configuration

You can configure the instrumentation by passing an options object to the constructor:

Copied
new Sentry.ReactNavigationV4Instrumentation({ ... });
routeChangeTimeoutMs

How long the instrumentation will wait for the initial route to mount after a change has been initiated before the transaction is discarded. Default: 1000ms

React Native Navigation

This instrumentation supports react-native-navigation. You will need to pass the Navigation object imported from the library to initialize our routing instrumentation. It is recommended that you initialize our SDK and the routing instrumentation as early as possible in the lifecycle of your app; this would most likely be before the point where you initialize your screens.

index.js
Copied
import * as Sentry from "@sentry/react-native";
import { Navigation } from 'react-native-navigation';

Sentry.init({
  ...
  integrations: [
    new Sentry.ReactNativeTracing({
      // Pass instrumentation to be used as `routingInstrumentation`
      routingInstrumentation: new Sentry.ReactNativeNavigationInstrumentation(
        Navigation,
      )
      // ...
    }),
  ],
})
Configuration

You can configure the instrumentation by passing an options object to the constructor:

Copied
new Sentry.ReactNativeNavigationInstrumentation({ ... });
routeChangeTimeoutMs

How long the instrumentation will wait for the route to mount after a change has been initiated, before the transaction is discarded. Default: 1000ms

Other Routing Libraries or Custom Routing Implementations

If you use another routing library that we don't yet support, or have a custom routing solution, you can use the basic RoutingInstrumentation we provide, or extend it to create your own instrumentation.

Every routing instrumentation revoles around one method:

onRouteWillChange (context: TransactionContext): Transaction | undefined

You need to ensure that this method is called before the route change occurs, and an IdleTransaction is created and set on the scope.

Bare Bones
Copied
// Construct a new instrumentation instance. This is needed to communicate between the integration and React
const routingInstrumentation = new Sentry.RoutingInstrumentation();

Sentry.init({
  ...
  integrations: [
    new Sentry.ReactNativeTracing({
      // Pass instrumentation to be used as `routingInstrumentation`
      routingInstrumentation,
      ...
    }),
  ],
})

const App = () => {
  <SomeNavigationLibrary
    onRouteWillChange={(newRoute) => {
        // Call this before the route changes
      routingInstrumentation.onRouteWillChange({
        name: newRoute.name,
        op: 'navigation'
      })
    }}
  />
};
Custom Instrumentation

To create a custom routing instrumentation, look at how the RoutingInstrumentation class is implemented. If you need an example of how to implement routing instrumentation, review the code of the existing official instrumentation such as the one for React Navigation V4

Copied
class CustomInstrumentation extends RoutingInstrumentation {
  constructor(navigator) {
    super();

    this.navigator.registerRouteChangeListener(this.routeListener.bind(this));
  }

  routeListener(newRoute) {
    // Again, ensure this is called BEFORE the route changes and BEFORE the route is mounted.
    this.onRouteWillChange({
      name: newRoute.name,
      op: "navigation",
    });
  }
}

Features

Sentry offers the following automatic instrumentation features.

App Start Instrumentation

The App Start Instrumentation provides insight into how long your application takes to launch. It tracks the length of time from the earliest native process initialization until the React Native root component mounts.

The SDK differentiates between a cold and a warm start, but doesn't track hot starts/resumes.

  • Cold start: App launched for the first time, after a reboot or update. The app is not in memory and no process exists.
  • Warm start: App launched at least once, is partially in memory, and no process exists.

Cold and warm start are Mobile Vitals, which you can learn about in the full documentation.

Slow and Frozen Frames

Unresponsive UI and animation hitches annoy users and degrade the user experience. Two measurements to track these types of experiences are slow frames and frozen frames. If you want your app to run smoothly, you should try to avoid both. The SDK adds these two measurements for the transactions you capture.

  • Slow Frames: Slow frames are captured by the SDK when the app takes more than 16.67 ms to render a frame. Typically, a phone or tablet renders 60 frames per second (fps), though the frame rate can be higher, especially as 120 fps displays become popular. At 60 fps, every frame has 16.67 ms to render; slower than that, and the app has slow frames.
  • Frozen Frames: Frozen frames are UI frames that take longer than 700 ms to render.

Slow and frozen frames are Mobile Vitals, which you can learn about in the full documentation.

AndroidX Support

Sentry uses the androidx.core library for detecting slow and frozen frames. This is necessary to produce accurate results across all Android OS versions.

We check for availability at runtime, so if you're not using androidx.core, you can remove it from Sentry's transitive dependencies.

Copied
api ('io.sentry:sentry-android:7.13.0') {
    exclude group: 'androidx.core', module: 'core'
}

Note that if you remove this transitive dependency, slow and frozen frames won't be reported.

Stall Tracking

A stall is when the JavaScript event loop takes longer than expected to complete. A stall in your JavaScript code will not just make your UI unresponsive, but also slow down the logic that is contained within JavaScript. This slows everything down, creating a bad experience for your users.

We track stalls that occur in your React Native app during a transaction and provide you with these values:

  • Longest Stall Time: The time, in milliseconds, of the longest event loop stall.
  • Total Stall Time: The total combined time, in milliseconds, of all stalls.
  • Stall Count: The total number of stalls that occurred during the transaction.

Fetch/XML Request Instrumentation

The tracing integration creates a child span for every XMLHttpRequest or fetch request on the Javascript layer that occurs while those transactions are open. Learn more about traces, transactions, and spans.

Configuration Options

To configure the automatic performance instrumentation, you will need to add the ReactNativeTracing integration yourself. We provide many options by default, so for the majority of apps you won't need to configure the integration yourself.

Copied
import * as Sentry from "@sentry/react-native";

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",

  integrations: [
    new Sentry.ReactNativeTracing({
      tracingOrigins: ["localhost", "my-site-url.com", /^\//],
      // ... other options
    }),
  ],
});

For all possible options, see ReactNativeTracingOptions.

beforeNavigate

You can use beforeNavigate to modify the transaction from routing instrumentation before the transaction is created. If you prefer not to send the transaction, set sampled = false.

Copied
Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
  integrations: [
    new Sentry.ReactNativeTracing({
      routingInstrumentation,
      beforeNavigate: context => {
        // Decide to not send a transaction by setting sampled = false
        if (context.data.route.name === "Do Not Send") {
          context.sampled = false;
        }

        // Modify the transaction context
        context.name = context.name.toUpperCase();
        context.tags = {
          ...context.tags,
          customTag: "value",
        };

        return context;
      },
    }),
  ],
});

If you use our routing instrumentation for React Navigation, the route data is set on the transaction context passed to beforeNavigate. This has the type:

Copied
type ReactNavigationTransactionContext = {
  // ...
  data: {
    route: {
      name: string;
      key: string;
      params: {
        [key: string]: any;
      };
      /** Will be true if this is not the first time this screen with this key has been seen before. */
      hasBeenSeen: boolean;
    };
    previousRoute: {
      name: string;
      key: string;
      params: {
        [key: string]: any;
      };
    } | null;
  };
};

If you use Typescript, you can type your beforeNavigate function with Sentry.ReactNavigationTransactionContext.

tracingOrigins

The default value of tracingOrigins is ['localhost', /^\//]. The React Native SDK will attach the sentry-trace header to all outgoing XHR/fetch requests whose destination contains a string in the list or matches a regex in the list. If your frontend is making requests to a different domain, you will need to add the domain there to propagate the sentry-trace header to the backend services, which is required to link transactions together as part of a single trace. The tracingOrigins option matches against the entire request URL, not just the domain. Using stricter regex to match certain parts of the URL ensures that requests do not unnecessarily have the sentry-trace header attached.

For example:

  • A frontend application is served from example.com
  • A backend service is served from api.example.com
  • The frontend application makes API calls to the backend
  • Therefore, the option needs to be configured like this: new Sentry.ReactNativeTracing({tracingOrigins: ['api.example.com']})
  • Now outgoing XHR/fetch requests to api.example.com will get the sentry-trace header attached
Copied
Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
  integrations: [
    new Sentry.ReactNativeTracing({
      tracingOrigins: ["localhost", "my-site-url.com"],
    }),
  ],

  // We recommend adjusting this value in production, or using tracesSampler
  // for finer control
  tracesSampleRate: 1.0,
});

You will need to configure your web server CORS to allow the sentry-trace header. The configuration might look like "Access-Control-Allow-Headers: sentry-trace", but the configuration depends on your setup. If you do not allow the sentry-trace header, the request might be blocked.

shouldCreateSpanForRequest

This function can be used to filter out unwanted spans, such as XHR's running health checks or something similar. By default, shouldCreateSpanForRequest already filters out everything except what was defined in tracingOrigins.

Copied
Sentry.init({
  // ...
  integrations: [
    new Sentry.ReactNativeTracing({
      shouldCreateSpanForRequest: url => {
        // Do not create spans for outgoing requests to a `/health/` endpoint
        return !url.match(/\/health\/?$/);
      },
    }),
  ],
});

Recipes

Currently, by default, the React Native SDK will only create child spans for fetch/XHR transactions out of the box. This means once you are done setting up your routing instrumentation, you will either see just a few fetch/XHR child spans or no children at all. To find out how to customize instrumentation your app, review our Custom Instrumentation.

React Profiler

We export the React Profiler from our React Native SDK as well, you can read more at React Profiler.

After you instrument your app's routing, if you wrap a component that renders on one of the routes with withProfiler, you will be able to track the component's lifecycle as a child span of the route transaction.

Copied
import * as Sentry from "@sentry/react-native";

// withProfiler HOC
const SomeComponent = () => {
  // ...
};

export default Sentry.withProfiler(SomeComponent);

Minified Names in Production

When bundling for production, React Native will minify class and function names to reduce the bundle size. This means that you won't get the full original component names in your Profiler spans and instead you will see minified names. Check out our troubleshooting guide for minified production bundles documentation to solve this.

Opt Out

If you want to use tracing without our automatic instrumentation, you can disable it by setting enableAutoPerformanceTracking in your Sentry options and removing the ReactNativeTracing integration, if you added it:

Copied
import * as Sentry from "@sentry/react-native";

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",

  enableAutoPerformanceTracking: false,
});