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.
For access to JavaScript spans created from elsewhere in your JavaScript code:
Install the @bugsnag/plugin-named-spans
package:
yarn add @bugsnag/plugin-named-spans
# or
npm install --save @bugsnag/plugin-named-spans
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()]
})
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.
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.
Install the @bugsnag/plugin-react-native-span-access
package:
yarn add @bugsnag/plugin-react-native-span-access
# or
npm install --save @bugsnag/plugin-react-native-span-access
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"))
}
run pod install
in your iOS project directory to ensure the plugin is linked correctly:
npx pod-install
To allow the React Native Performance SDK to access and manipulate native spans, add BugsnagNativeSpansPlugin
to the native SDK configuration:
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
})
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)
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.
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.
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
}];
}