Named span access

Use SDK plugins to track open spans and access them by name from different parts of your codebase.

When starting custom spans, a reference to the object representing that span is returned and can be used to set further attributes or end the measurement.

const span = BugsnagPerformance.startSpan("login")

However, sometimes you may wish to end the span in a separate component, requiring an awkward passing of the object between scopes. The @bugsnag/plugin-named-spans plugin keeps track of all open spans and allows you to obtain a reference to them using just their name from anywhere in your codebase.

const span: Span | null = BugsnagPerformance.getSpanControls(new NamedSpanQuery('login'))
if (span) {
  span.setAttribute('user.id', 12345)
  span.end()
}

As well as using this tracking within your JavaScript code base, the SDK allows you to access spans created in native Android and iOS code, and also allows your native code to access JavaScript spans. This cross-layer tracking is useful for managing performance data across different parts of your application. For example, ending a span or setting a span attribute in a different layer.

JavaScript span access

For access to JavaScript spans created from elsewhere in your JavaScript code:

Installation

Install the @bugsnag/plugin-named-spans package:

yarn add @bugsnag/plugin-named-spans
# or
npm install --save @bugsnag/plugin-named-spans

Configuration

To enable the plugin, include it in the configuration options when starting the SDK:

import { BugsnagNamedSpansPlugin } from '@bugsnag/plugin-named-spans'

BugsnagPerformance.start({
  apiKey: 'YOUR_API_KEY',
  plugins: [new BugsnagNamedSpansPlugin()]
})

Usage

The plugin maintains a map of open spans that can be queried via BugsnagPerformance.getSpanControls(). The NamedSpanQuery class allows you to specify the name of the JavaScript span you want to retrieve.

import { NamedSpanQuery } from '@bugsnag/plugin-named-spans'

const span: Span | null = BugsnagPerformance.getSpanControls(new NamedSpanQuery('login'))
if (span) {
  span.setAttribute('user.id', 12345)
  span.end()
}

If the span does not exist; has already ended; or is no longer valid, it will return null. If multiple spans with the same name are open, only the most recent one will be returned.

To avoid memory leaks caused by spans not being ended, spans older than 10 minutes are removed from tracked memory to allow them to be deallocated.

Cross-layer span access

For access to spans created in native Android and iOS code from your JavaScript code (and vice versa), follow the installation and configuration steps below to enable the @bugsnag/plugin-react-native-span-access plugin.

Cross-layer tracking requires that the Native integration is installed and configured in your app.

Installation

Install the @bugsnag/plugin-react-native-span-access package:

JavaScript

yarn add @bugsnag/plugin-react-native-span-access
# or
npm install --save @bugsnag/plugin-react-native-span-access

Android

Add the following dependencies to your Module Gradle Settings, usually found at <project_dir>/android/app/build.gradle or build.gradle.kts:

dependencies {
    // ...
    implementation project(':bugsnag_plugin-react-native-span-access')
}
dependencies {
    // ...
    implementation(project(":bugsnag_plugin-react-native-span-access"))
}

iOS

run pod install in your iOS project directory to ensure the plugin is linked correctly:

npx pod-install

Configuration

To allow the React Native Performance SDK to access and manipulate native spans, add BugsnagNativeSpansPlugin to the native SDK configuration:

Android

import com.bugsnag.reactnative.performance.nativespans.BugsnagNativeSpansPlugin;

PerformanceConfiguration config = PerformanceConfiguration.load(this);
config.addPlugin(new BugsnagNativeSpansPlugin());     // access to native spans in JS
config.addPlugin(new BugsnagJavascriptSpansPlugin()); // access to JS spans in native
BugsnagPerformance.start(config);
import com.bugsnag.reactnative.performance.nativespans.BugsnagNativeSpansPlugin

BugsnagPerformance.start(PerformanceConfiguration.load(this).apply {
  addPlugin(BugsnagNativeSpansPlugin())     // access to native spans in JS
  addPlugin(BugsnagJavascriptSpansPlugin()) // access to JS spans in native
})

iOS

BugsnagPerformanceConfiguration *config = [BugsnagPerformanceConfiguration loadConfig];
[config addPlugin:[BugsnagNativeSpansPlugin new]];     // access to native spans in JS
[config addPlugin:[BugsnagJavascriptSpansPlugin new]]; // access to JS spans in native
[BugsnagPerformance startWithConfiguration:config];
let config = BugsnagPerformanceConfiguration.loadConfig()
config.add(BugsnagNativeSpansPlugin())     // access to native spans in JS
config.add(BugsnagJavascriptSpansPlugin()) // access to JS spans in native
BugsnagPerformance.start(configuration: config)

JavaScript

To enable the plugin, include it in the configuration options when attaching to the native SDK:

import BugsnagPerformance from '@bugsnag/react-native-performance'
import { BugsnagNativeSpansPlugin } from '@bugsnag/plugin-react-native-span-access'
import { BugsnagJavascriptSpansPlugin } 
  from '@bugsnag/plugin-react-native-javascript-span-access'

BugsnagPerformance.attach({
  plugins: [
    new BugsnagNativeSpansPlugin(),     // access to native spans in JS
    new BugsnagJavascriptSpansPlugin()  // access to JS spans in native
  ]
})

To avoid memory leaks caused by spans not being ended, spans older than 10 minutes are removed from tracked memory to allow them to be deallocated.

Accessing native spans from JavaScript

The plugin maintains a map of open native spans that can be queried by name via BugsnagPerformance.getSpanControls(). The NativeSpanQuery class allows you to specify the name of the span you want to retrieve and, if found, returns a NativeSpanControl object. This allows the span to be used in parent hierarchies and for supported operations to be executed on the native span from JavaScript code.

import BugsnagPerformance from '@bugsnag/react-native-performance'
import { NativeSpanQuery } from '@bugsnag/plugin-react-native-span-access'

// Query the SDK for a native span control
const nativeSpan: NativeSpanControl | null = 
  BugsnagPerformance.getSpanControls(new NativeSpanQuery('login'))

// Use the control:
if (nativeSpan) {
  // ... as a parent context for JS spans
  BugsnagPerformance.startSpan('js-login-view', { parentContext: nativeSpanControl })

  // ... to set attributes or end the native span
  const spanWasUpdated = await nativeSpanControl.updateSpan(spanMutator => {
    spanMutator.setAttribute('my.attribute', 'from JS')
    spanMutator.end()
  })
}

If the span does not exist, has already ended or is no longer valid, getSpanControls will return null. If multiple spans with the same name are open, only the most recent one will be returned.

updateSpan returns true if the changes were successfully applied. The spanMutator is only valid for the duration of the lambda call.

Accessing JavaScript spans from native code

To access a JavaScript span from native code, use BugsnagPerformance.getSpanControls with a named span query to retrieve a JavascriptSpanControl for the span. To use the remote span as a parent context for a native span, use the retrieveSpanContext method. You can also create an update transaction to set attributes or end the span, then commit the transaction to apply your changes asynchronously.

import com.bugsnag.reactnative.performance.nativespans.JavascriptSpanByName
import com.bugsnag.reactnative.performance.nativespans.JavascriptSpanControl

// Query the SDK for a JavaScript span control
val spanControl: JavascriptSpanControl? =
    BugsnagPerformance.getSpanControls(JavascriptSpanByName("login"))

// Use the control:
if (spanControl != null) {
  // ... as a parent context for native spans
  spanControl.retrieveSpanContext { remoteContext ->
    if (remoteContext != null) {
      BugsnagPerformance.startSpan("child-span", SpanOptions.within(remoteContext))
    }
  }
  // ... or to set attributes or end the native span
  val transaction = spanControl.createUpdateTransaction()
  transaction
      .setAttribute("my.attribute", "from native")
      .end()
      // Commit the transaction to apply changes
      .commit { updateResult ->
          // Optional handling for whether the update succeeded
          // updateResult is true if the span was updated, false otherwise
      }
}
import com.bugsnag.reactnative.performance.nativespans.JavascriptSpanByName;
import com.bugsnag.reactnative.performance.nativespans.JavascriptSpanControl;
import com.bugsnag.reactnative.performance.nativespans.JavascriptSpanTransaction;
import com.bugsnag.reactnative.performance.nativespans.OnRemoteSpanUpdatedCallback;
import com.bugsnag.reactnative.performance.nativespans.OnSpanContextRetrievedCallback;

// Query the SDK for a JavaScript span control
JavascriptSpanControl spanControl =
    BugsnagPerformance.getSpanControls(new JavascriptSpanByName("login"));

// Use the control:
if (spanControl != null) {
  // ... as a parent context for native spans
  spanControl.retrieveSpanContext(new OnSpanContextRetrievedCallback() {
    @Override
    public void onSpanContextRetrieved(SpanContext remoteContext) {
      if (remoteContext != null) {
        BugsnagPerformance.startSpan("child-span", SpanOptions.createWithin(remoteContext));
      }
    }
  });
  // ... or to set attributes or end the native span
  JavascriptSpanTransaction transaction = spanControl.createUpdateTransaction();
  transaction
    .setAttribute("my.attribute", "from native")
    .end()
    // Commit the transaction to apply changes
    .commit(new OnRemoteSpanUpdatedCallback() {
      @Override
      public void onRemoteSpanUpdated(boolean updateResult) {
          // Optional handling for whether the update succeeded
          // updateResult is true if the span was updated, false otherwise
      }
    });
}

// Query the SDK for a JavaScript span control
guard let spanControl = BugsnagPerformance.getSpanControls(
  with: BugsnagJavascriptSpanQuery(name:"MyJavascriptSpan"))
  as? BugsnagJavascriptSpanControl else { return }

// Use the control:
// ... as a parent context for native spans
spanControl.retrieveSpanContext { remoteContext in
  guard let remoteContext = remoteContext else { return }
  BugsnagPerformance.startSpan(
    name: "child-span", 
    options: BugsnagPerformanceSpanOptions.setParentContext(remoteContext))
}
// ... or to set attributes or end the native span
let updateTransaction = spanControl.createUpdateTransaction()
updateTransaction.setAttribute("my.attribute", withValue: "from native")
updateTransaction.end()
// Commit the transaction to apply changes
updateTransaction.commit { result in
  // Optional handling for whether the update succeeded
  // result is true if the span was updated, false otherwise
}
// Query the SDK for a JavaScript span control
BugsnagJavascriptSpanQuery *spanQuery = [BugsnagJavascriptSpanQuery queryWithName:@"login"];
BugsnagJavascriptSpanControl *spanControl = [BugsnagPerformance getSpanControlsWithQuery:spanQuery];

// Use the control:
if (spanControl != nil) {
  // ... as a parent context for native spans
  [spanControl retrieveSpanContext:^(BugsnagPerformanceSpanContext * _Nullable remoteContext) {
      if (remoteContext != nil) {
          [BugsnagPerformance startSpanWithName:@"child-span"
                                                options:[BugsnagPerformanceSpanOptions setParentContext:remoteContext]];
      }
  }];
  // ... or to set attributes or end the native span
  BugsnagJavascriptSpanTransaction *transaction = [spanControl createUpdateTransaction];
  [transaction setAttribute:@"my.attribute" withValue:@"from native"];
  [transaction end];
  // Commit the transaction to apply changes
  [transaction commit:^(BOOL result) {
    // Optional handling for whether the update succeeded
    // result is true if the span was updated, false otherwise
  }];
}

Both Android and iOS SDKs have plugins to track native spans by name. See the Android and iOS guides for more information.