Custom Instrumentation
Learn how to capture performance data on any action in your app.
To capture transactions and spans customized to your organization's needs, you must first set up performance monitoring.
To add custom performance data to your application, you need to add custom instrumentation in the form of spans. Spans are a way to measure the time it takes for a specific action to occur. For example, you can create a span to measure the time it takes for a function to execute.
To get started, import the SDK.
import * as Sentry from '@sentry/react-native';
The below span APIs (startSpan
, startInactiveSpan
, and startSpanManual
) require SDK version 7.69.0
or higher. If you are using an older version of the SDK, you can use the explicit transaction APIs for custom instrumentation.
By default, spans you create are considered active, which means they are put on the Sentry scope. This allows child spans and Sentry errors to be associated with that span. This is the recommended way to create spans.
You can use the Sentry.startSpan
method to wrap a callback in a span to measure how long it will take. The span will automatically be finished when the callback finishes. This works with both synchronous and async callbacks, but automatically finishes async callbacks based on the promise that is returned from the callback. In cases where you need to manually finish the span (because you don't return anything), you can use Sentry.startSpanManual
, documented below this snippet.
const result = Sentry.startSpan({name: 'Important Function'}, () => {
return expensiveFunction();
});
const result2 = await Sentry.startSpan({name: 'Important Function'}, async () => {
const res = await Sentry.startSpan({name: 'Child Span'}, () => {
return expensiveAsyncFunction();
});
return updateRes(res);
});
const result3 = Sentry.startSpan({name: 'Important Function'}, span => {
// You can access the span to add attributes or set specific status.
// The span may be undefined if the span was not sampled or if performance monitoring is disabled.
span.setAttribute('foo', 'bar');
return expensiveFunction();
});
const result4 = Sentry.startSpan(
{
name: 'Important Function',
// You can also pass attributes directly to `startSpan`:
attributes: {
foo: 'bar',
count: 1,
},
},
() => {
return expensiveFunction();
}
);
In this example, the span named Important Function
will become the active span for the duration of the callback.
If you need to override when the span finishes, you can use Sentry.startSpanManual
. This is useful if you don't want to finish the span when the callback ends, or if you want to finish the span at a specific time.
// Start a span that tracks the duration of middleware
function middleware(_req, res, next) {
return Sentry.startSpanManual({name: 'middleware'}, span => {
res.once('finish', () => {
span.setHttpStatus(res.status);
// manually tell the span when to end
span.end();
});
return next();
});
}
To add spans that aren't active, you can create independent spans. This is useful for when you have work that is grouped together under a single parent span, but is independent from the current active span. However, in most cases you'll want to create and use the start span API from above.
const span1 = Sentry.startInactiveSpan({name: 'span1'});
someWork();
const span2 = Sentry.startInactiveSpan({name: 'span2'});
moreWork();
const span3 = Sentry.startInactiveSpan({name: 'span3'});
evenMoreWork();
span1.end();
span2.end();
span3.end();
Spans can have an operation associated with them, which help activate Sentry identify additional context about the span. For example database related spans have the db
span operation associated with them. The Sentry product offers additional controls, visualizations and filters for spans with known operations.
Sentry maintains a list of well known span operations and it is recommended that you use one of those operations if it is applicable to your span.
const result = Sentry.startSpan({ name: 'GET /users', op: 'http.client' }, () => {
return fetchUsers();
})
The root span (the span that is the parent of all other spans) is known as a transaction in Sentry. Transactions can be accessed and created separately if you need more control over your timing data or if you use a version of the SDK that doesn't support the top-level span APIs.
To instrument certain regions of your code, you can create spans to capture them.
This is valid for all JavaScript SDKs (both backend and frontend) and works independently of the Express
, Http
, and BrowserTracing
integrations.
Sentry.startSpan({name: 'test-transaction'}, rootSpan => {
Sentry.startSpan({name: 'child-span', op: 'functionX'}, childSpan => {
// do something in here
// childSpan will be nested inside of rootSpan
});
});
For example, if you want to create a transaction for a user interaction on your page:
// Let's say this function is invoked when a user clicks on the checkout button of your shop
function shopCheckout() {
return Sentry.startSpan({name: 'shopCheckout'}, () => {
// Assume this function makes a fetch call
const result = validateShoppingCartOnServer();
return Sentry.startSpan(
{
name: 'processing shopping cart result',
op: 'task',
attributes: {
// you can add additional attributes, if needed
},
},
() => {
// `startSpan` will automatically mark the span as errored, if an exception is thrown
// it will end the span when the callback is finished.
return processAndValidateShoppingCart(result);
}
);
});
}
This example will send a transaction shopCheckout
to Sentry. The span will contain a task
child span that measures how long processAndValidateShoppingCart
took. When the callback ends, the span will be finished and sent to Sentry.
You can also take advantage of promises when creating spans for async operations. Keep in mind, though, that a span must finish before the parent span finishes in order to be sent to Sentry.
If the callback passed to startSpan()
is an async function or returns a promise, startSpan
will automatically wait for the promise to resolve before finishing the span. If the promise rejects, the span will be marked as errored.
For example, when returning a promise:
function processItem(item) {
return Sentry.startSpan(
{
op: 'http',
name: 'GET /items/:item-id',
},
span => {
return new Promise((resolve, reject) => {
http.get(`/items/${item.id}`, response => {
response.on('data', () => {});
response.on('end', () => {
span.setTag('http.status_code', response.statusCode);
span.setAttribute('http.foobarsessionid', getFoobarSessionid(response));
resolve(response);
});
});
});
}
);
}
Or when using an async function:
function processItem(item) {
return Sentry.startSpan(
{
op: 'http',
name: 'GET /items/:item-id',
},
async span => {
const response = await fetch('/items/${item.id}');
const json = await response.json();
span.setTag('http.status_code', response.statusCode);
span.setAttribute('http.foobarsessionid', getFoobarSessionid(response));
}
);
}
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").