Instrument React Native Applications with OpenTelemetry

The Honeycomb OpenTelemetry React Native SDK is Honeycomb’s distribution of OpenTelemetry for React Native applications. It includes instrumentation for things like detecting slow event loops or unhandled exceptions, and provides a component to simplify navigation instrumentation.

This page covers basic usage of the SDK. If you just want to see some code, check out the example on GitHub.

Installing the SDK 

Before you can use Honeycomb’s OpenTelemetry React Native SDK, you need to install it and configure platform-specific dependencies.

  1. Configure Metro to recognize package.json exports. To do this, create a new file, metro.config.js, in your application’s root directory. Enable package.json exports in your Metro configuration.

    config.resolver.unstable_enablePackageExports = true;
    

    Here’s an example Metro configuration:

    const path = require('path');
    const { getDefaultConfig } = require('@react-native/metro-config');
    const { getConfig } = require('react-native-builder-bob/metro-config');
    const pkg = require('../package.json');
    
    const root = path.resolve(__dirname, '..');
    
    const config = getConfig(getDefaultConfig(__dirname), {
      root,
      pkg,
      project: __dirname,
    });
    
    // Required to use @opentelemetry package.json "exports" field
    config.resolver.unstable_enablePackageExports = true;
    
    module.exports = config;
    
    Tip
    To learn more about Metro configuration, visit Configuring Metro.
  2. Install Honeycomb’s OpenTelemetry React Native SDK in your application’s root directory using your preferred package manager.

    Install with yarn:

    yarn add @honeycombio/opentelemetry-react-native
    

    Install with npm:

    npm install @honeycombio/opentelemetry-react-native
    
  3. Install the necessary dependencies for each mobile platform your application supports.

    Android dependencies:

    Add the following dependencies to your application’s build.gradle.

    dependencies {
        //...
        implementation "io.honeycomb.android:honeycomb-opentelemetry-android:0.0.16"
        implementation "io.opentelemetry.android:android-agent:0.11.0-alpha"
    }
    

    If your application’s minSDK version is lower than 26, add core library desugaring to your android/app/build.gradle.

    android {
        compileOptions {
            // Enable support for the new language APIs
            coreLibraryDesugaringEnabled true
        }
        dependencies {
            coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.5"
        }
    }
    

    iOS dependencies:

    Add the use_frameworks! option to your application’s Podfile.

    platform :ios, min_ios_version_supported
    prepare_react_native_project!
    use_frameworks!
    

    From the ios directory, run pod install to install the required dependencies.

Initializing 

Initialize the SDK at the start of your React Native application. This ensures that events such as startup time and early view loads are captured.

import { HoneycombReactNativeSDK } from '@honeycombio/opentelemetry-react-native';
import { DiagLogLevel } from '@opentelemetry/api';

const sdk = new HoneycombReactNativeSDK({
  // Uncomment the line below to send to EU instance. Defaults to US.
  // endpoint: "https://api.eu1.honeycomb.io:443",
  apiKey: "YOUR-API-KEY",
  serviceName: "YOUR-SERVICE-NAME",
  logLevel: DiagLogLevel.DEBUG,
});

sdk.start();

For Android, initialize the SDK at the start of the onCreate() method in your main Application class.

// MainApplication.kt
override fun onCreate() {
    val options = HoneycombOpentelemetryReactNativeModule.optionsBuilder(this)
        // Uncomment the line below to send to EU instance. Defaults to US.
        // .setApiEndpoint("https://api.eu1.honeycomb.io:443")
        .setApiKey("YOUR-API-KEY")
        .setServiceName("YOUR-SERVICE-NAME")

    HoneycombOpentelemetryReactNativeModule.configure(this, options)
    super.onCreate()
}

For iOS, initialize the SDK at the start of the application() method in your AppDelegate class.

// AppDelegate.swift
override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
    let options = HoneycombReactNative.optionsBuilder()
        // Uncomment the line below to send to EU instance. Defaults to US.
        // .setAPIEndpoint("https://api.eu1.honeycomb.io:443")
        .setAPIKey("YOUR-API-KEY")
        .setServiceName("YOUR-SERVICE-NAME")

    HoneycombReactNative.configure(options)
}

Configuring 

The Honeycomb React Native SDK can be configured using many of the configuration options from the Honeycomb Web SDK.

import { HoneycombReactNativeSDK } from '@honeycombio/opentelemetry-react-native';
import { DiagLogLevel } from '@opentelemetry/api';
import { ExampleSpanProcessor } from "@/services/ExampleSpanProcessor";

const sdk = new HoneycombReactNativeSDK({
  apiKey: "YOUR-API-KEY",
  // Uncomment the line below to send to EU instance. Defaults to US.
  // endpoint: "https://api.eu1.honeycomb.io:443",
  serviceName: "YOUR-SERVICE-NAME",
  resourceAttributes: {
    "app.environment": "development",
  },
  logLevel: DiagLogLevel.DEBUG,
  
  fetchInstrumentationConfig: {
    propagateTraceHeaderCorsUrls: [
      // Regex to match your backend URLs.
      // Update to the domains you wish to include.
      /.+/g,
    ]
  },

  slowEventLoopInstrumentationConfig: {
    enabled: true,
  },

  spanProcessors: [
    new ExampleSpanProcessor();
  ]
});

sdk.start();

Adding resource attributes 

Resource attributes are available on every span your instrumentation emits. Adding custom, application-specific attributes makes it easier to correlate your data to important business information.

You can set resource attributes using the resourceAttributes configuration option.

import { HoneycombReactNativeSDK } from '@honeycombio/opentelemetry-react-native';

const sdk = new HoneycombReactNativeSDK({
  apiKey: "YOUR-API-KEY",
  // Uncomment the line below to send to EU instance. Defaults to US.
  // endpoint: "https://api.eu1.honeycomb.io:443",
  serviceName: "YOUR-SERVICE-NAME",
  resourceAttributes: {
    "app.team": "team name",
  },
});

sdk.start();

Enabling sampling 

The SDK includes optional deterministic head sampling. The sample rate is 1 by default, meaning every trace is exported.

The iOS example below sets a sampleRate of 40, meaning 1 in 40 traces will be exported. This sample rate will only apply to JavaScript/TypeScript traces.

import { HoneycombReactNativeSDK } from '@honeycombio/opentelemetry-react-native';

const sdk = new HoneycombReactNativeSDK({
  apiKey: "YOUR-API-KEY",
  // Uncomment the line below to send to EU instance. Defaults to US.
  // endpoint: "https://api.eu1.honeycomb.io:443",
  serviceName: "YOUR-SERVICE-NAME",
  sampleRate: 40,
});

sdk.start();

Android 

You can configure a sample rate for Android traces with .setSampleRate():

// MainApplication.kt
override fun onCreate() {
    val options = HoneycombOpentelemetryReactNativeModule.optionsBuilder(this)
        // Uncomment the line below to send to EU instance. Defaults to US.
        // .setApiEndpoint("https://api.eu1.honeycomb.io:443")
        .setApiKey("YOUR-API-KEY")
        .setServiceName("YOUR-SERVICE-NAME")
        .setSampleRate(40)

    HoneycombOpentelemetryReactNativeModule.configure(this, options)
    super.onCreate()
}

iOS 

You can configure a sample rate for iOS traces with .sampleRate():

// AppDelegate.swift
override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
    let options = HoneycombReactNative.optionsBuilder()
        // Uncomment the line below to send to EU instance. Defaults to US.
        // .setAPIEndpoint("https://api.eu1.honeycomb.io:443")
        .setAPIKey("YOUR-API-KEY")
        .setServiceName("YOUR-SERVICE-NAME")
        .sampleRate(40)

    HoneycombReactNative.configure(options)
}

Sending to OpenTelemetry Collector 

In production, we recommend running an OpenTelemetry Collector. Your application sends telemetry to your Collector instead of directly to Honeycomb. Your Collector then forwards the telemetry data to Honeycomb, keeping your API key stored securely in the Collector’s configuration.

Configure your Collector’s URL by setting the endpoint option when initializing the Honeycomb React Native SDK:

import { HoneycombReactNativeSDK } from '@honeycombio/opentelemetry-react-native';

const sdk = new HoneycombReactNativeSDK({
  endpoint: "http(s)://YOUR-COLLECTOR-URL",
  serviceName: "YOUR-SERVICE-NAME",
});

sdk.start();

This example works for all platforms and shows the core configuration needed to connect your application to a Collector.

Android 

For Android, use .setApiEndpoint():

// MainApplication.kt
override fun onCreate() {
    val options = HoneycombOpentelemetryReactNativeModule.optionsBuilder(this)
        .setApiEndpoint("http(s)://YOUR-COLLECTOR-URL")
        .setServiceName("YOUR-SERVICE-NAME")

    HoneycombOpentelemetryReactNativeModule.configure(this, options)
    super.onCreate()
}

iOS 

For iOS, use .setAPIEndpoint():

// AppDelegate.swift
override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
    let options = HoneycombReactNative.optionsBuilder()
        .setAPIEndpoint("http(s)://YOUR-COLLECTOR-URL")
        .setServiceName("YOUR-SERVICE-NAME")

    HoneycombReactNative.configure(options)
}

Sending to Honeycomb 

To send telemetry data directly to Honeycomb, set the apiKey option with your Ingest API Key.

import { HoneycombReactNativeSDK } from '@honeycombio/opentelemetry-react-native';

const sdk = new HoneycombReactNativeSDK({
  apiKey: "YOUR-API-KEY",
  // Uncomment the line below to send to EU instance. Defaults to US.
  // endpoint: "https://api.eu1.honeycomb.io:443",
  serviceName: "YOUR-SERVICE-NAME",
});

sdk.start();

This example works for all platforms and shows the core configuration needed to send data directly to Honeycomb.

Android 

For Android, use .setApiKey():

// MainApplication.kt
override fun onCreate() {
    val options = HoneycombOpentelemetryReactNativeModule.optionsBuilder(this)
        // Uncomment the line below to send to EU instance. Defaults to US.
        // .setApiEndpoint("https://api.eu1.honeycomb.io:443")
        .setApiKey("YOUR-API-KEY")
        .setServiceName("YOUR-SERVICE-NAME")

    HoneycombOpentelemetryReactNativeModule.configure(this, options)
    super.onCreate()
}

iOS 

For iOS, use .setAPIKey():

// AppDelegate.swift
override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
    let options = HoneycombReactNative.optionsBuilder()
        // Uncomment the line below to send to EU instance. Defaults to US.
        // .setAPIEndpoint("https://api.eu1.honeycomb.io:443")
        .setAPIKey("YOUR-API-KEY")
        .setServiceName("YOUR-SERVICE-NAME")

    HoneycombReactNative.configure(options)
    //...
}

Adding automatic instrumentation 

The Honeycomb OpenTelemetry React Native SDK includes auto-instrumentation options for:

  • Application startup time, measured from when the native SDKs start to when the JavaScript SDK finishes initializing
  • Errors or uncaught exceptions
  • Fetch instrumentation, using the opentelemetry-instrumentation-fetch package
  • Slow event loop detection

Automatic instrumentation is enabled by default. You can enable or disable individual auto-instrumentation libraries in your configuration.

import { HoneycombReactNativeSDK } from '@honeycombio/opentelemetry-react-native';
import { DiagLogLevel } from '@opentelemetry/api';

const sdk = new HoneycombReactNativeSDK({
  // Uncomment the line below to send to EU instance. Defaults to US.
  // endpoint: "https://api.eu1.honeycomb.io:443",
  apiKey: "YOUR-API-KEY",
  serviceName: "YOUR-SERVICE-NAME",
  logLevel: DiagLogLevel.DEBUG,

  // auto-instrumentation options
  reactNativeStartupInstrumentationConfig: {
    enabled: true,
  },

  uncaughtExceptionInstrumentationConfig: {
    enabled: true,
  },

  fetchInstrumentationConfig: {
    enabled: true,
  },

  slowEventLoopInstrumentationConfig: {
    enabled: true,
  },
});

sdk.start();

Adding custom instrumentation 

Automatic instrumentation is a fast way to instrument your code, but you get more insight into your application by adding custom, otherwise known as manual, instrumentation.

Adding attributes to an active span 

You can retrieve the currently active span in a trace and add attributes to it. This lets you add more context to traces and gives you more ways to group or filter traces in your queries.

import { trace } from '@opentelemetry/api';

function handleUser(user: User) {
  let currentActiveSpan = trace.getActiveSpan();
  currentActiveSpan.setAttribute('user.id', user.getId());
}

Acquiring a tracer 

For manual tracing, you need to get a tracer:

import { trace } from '@opentelemetry/api';

const tracer = trace.getTracer('example-tracer', '0.1.0');

Creating spans 

Create custom spans to get a clear view of the critical parts in your application.

import { trace } from '@opentelemetry/api';

function trackWork() {
  const tracer = trace.getTracer('example-tracer', '0.1.0');
  const span = tracer.startActiveSpan('do work');

  console.log('performing work');
  
  span.end();
}

Adding instrumentation to navigation 

You can instrument React NativeRouter or Expo Router navigation in your application using the <NavigationInstrumentation> component.

React NativeRouter 

Add a <NavigationInstrumentation> component as a child of your <NavigationContainer> component. Pass the container ref to your navigation container and your navigation instrumentation components.

import { NavigationInstrumentation } from '@honeycombio/opentelemetry-react-native';
import { useNavigationContainerRef, NavigationContainer } from '@react-navigation/native';


export default function App() {
  const navigationRef = useNavigationContainerRef();

  return (
    <NavigationContainer ref={navigationRef}>
      <NavigationInstrumentation ref={navigationRef}>
        {/* Navigation/UI code*/}
      </NavigationInstrumentation>
    </NavigationContainer>
  );
}

Expo Router 

Wrap your navigation code with a <NavigationInstrumentation> component and pass the container ref to the <NavigationInstrumentation> component.

import { NavigationInstrumentation } from '@honeycombio/opentelemetry-react-native';
import { useNavigationContainerRef } from 'expo-router';


export default function App() {
  const navigationRef = useNavigationContainerRef();

  return (
    <NavigationInstrumentation ref={navigationRef}>
      {/* Navigation/UI code*/}
    </NavigationInstrumentation>
  );
}

Troubleshooting 

Running into issues? Here are some common problems and ways to fix them.

To explore common issues when sending data, visit Common Issues with Sending Data in Honeycomb.

File not found error on iOS 

If you see an error like this when running your application on iOS:

'HoneycombOpentelemetryReactNative/HoneycombOpentelemetryReactNative-Swift.h' file not found when trying to run for iOS

It usually means your application’s Podfile is missing the use_frameworks! line.

To resolve this error:

  1. Add use_frameworks! immediately below prepare_react_native_project! in your Podfile:

    prepare_react_native_project!
    use_frameworks!
    
  2. Install the iOS dependencies by navigating to your ios directory and running pod install:

    cd ios
    pod install
    

Not receiving native telemetry data 

Unlike JavaScript code, native code does not hot reload on changes. So if you updated your AppDelegate.swift or MainApplication.kt files and still not getting native telemetry you’ll need to rebuild your application.

Stop metro, the simulator, and restart the build. If this still doesn’t work try uninstalling the app and reinstalling it.