Performance Monitoring

Sentry allows you to monitor the performance of your application, showing you how latency in one service may affect another service, and letting you pinpoint exactly which parts of a given operation may be responsible. To do this, it captures distributed traces consisting of transactions and spans, which measure individual services and individual operations within those services, respectively. You can learn more about this model in our Distributed Tracing docs.

Once tracing is enabled, certain types of operations will be measured automatically, and you can also choose to manually measure any operation you like. To learn more, see Capturing Transactions Automatically and Capturing Transactions Manually.

Enabling Tracing

The first step is to install the tracing package, if you haven't done so already:

Copied
# Using yarn
yarn add @sentry/tracing

# Using npm
npm install @sentry/tracing

Once you've installed the package, there are two ways to enable tracing in your app:

  • Set a uniform sample rate for all transactions, by setting the tracesSampleRate option in your SDK config to a number between 0 and 1. (For example, to send 20% of transactions, set tracesSampleRate to 0.2.)
  • Control the sample rate dynamically, based on the transaction itself and the context in which it's captured, by providing a function to the tracesSampler config option.
Copied
// If you're using one of our integration packages, like `@sentry/react` or
// `@sentry/angular`, substitute its name for `@sentry/browser` here
import * as Sentry from "@sentry/browser";

// If taking advantage of automatic instrumentation (highly recommended)
import { Integrations as TracingIntegrations } from "@sentry/tracing";
// Or, if only doing manual tracing
// import * as _ from "@sentry/tracing"
// Note: You MUST import the package in some way for tracing to work

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

  // This enables automatic instrumentation (highly recommended), but is not
  // necessary for purely manual usage
  integrations: [new TracingIntegrations.BrowserTracing()],

  // To set a uniform sample rate
  tracesSampleRate: 0.2

  // Alternatively, to control sampling dynamically
  tracesSampler: samplingContext => { ... }
});

If either of these options is set, tracing will be enabled in your app. (They are meant to be mutually exclusive, but if you do set both, tracesSampler will take precedence.) You can learn more about how they work in Sampling Transactions.

Connecting Backend and Frontend Transactions

To connect backend and frontend transactions into a single coherent trace, Sentry uses a trace_id value which is propagated between frontend and backend. Depending on the circumstance, this id is transmitted either in a request header or in an HTML <meta> tag. Linking transactions in this way makes it possible for you to navigate between them in the Sentry UI, so you can better understand how the different parts of your system are affecting each other.

Page load

When enabling tracing in both frontend and backend and taking advantage of the automatic frontend instrumentation, you can connect the automatically-generated pageload transaction on the frontend with the request transaction that serves the page on the backend. Because JavaScript code running in a browser cannot read the response headers of the current page, the trace_id must be transmitted in the response itself, specifically in a <meta> tag in the <head> of the HTML sent from your backend.

Copied
<html>
  <head>
    <meta name="sentry-trace" content="{{ span.toTraceparent() }}" />
    <!-- ... -->
  </head>
</html>

The name attribute must be the string "sentry-trace" and the content attribute must be generated by your backend's Sentry SDK using span.toTraceparent() (or equivalent, depending on the backend platform). This guarantees that a new and unique value will be generated for each request.

The span reference is either the transaction that serves the HTML, or any of its child spans. It defines the parent of the pageload transaction.

Once the data is included in the <meta> tag, our BrowserTracing integration will pick it up automatically and link it to the transaction generated on pageload. (Note that it will not get linked to automatically-generated navigation transactions, that is, those which don't require a full page reload. Each of those will be the result of a different request transaction on the backend, and therefore should have a unique trace_id.)

Once a page is loaded, any requests it makes (and any requests your backend makes as a result) are linked through a request header.

As is the case with the <meta> tag discussed above, the header's name is sentry-trace and its value is obtained by calling span.toTraceparent() (or the equivalent), where span is either the relevant transaction or any of its children.

All of Sentry's tracing-related integrations (BrowserTracing, Http, and Express) either generate or pick up and propagate this header automatically as appropriate, for all transactions and spans which they generate. You can also attach and read the header yourself, in any case in which you've manually created either a transaction or a span and it makes sense to do so.

Retrieving an Active Transaction

In cases where you want to attach Spans to an already ongoing transaction you can use Sentry.getCurrentHub().getScope().getTransaction(). This function will return a Transaction object when there is a running transaction on the scope, otherwise it returns undefined. If you are using our BrowserTracing integration, by default we attach the transaction to the Scope, so you could do something like this:

Copied
function myJsFunction() {
  const transaction = Sentry.getCurrentHub()
    .getScope()
    .getTransaction();
  if (transaction) {
    let span = transaction.startChild({
      op: "encode",
      description: "parseAvatarImages",
    });
    // Do something
    span.finish();
  }
}

Adding Query Information and Parameters to Spans

Currently, every tag has a maximum character limit of 200 characters. Tags over the 200 character limit will become truncated, losing potentially important information. To retain this data, you can split data over several tags instead.

For example, a 200+ character tagged request:

https://empowerplant.io/api/0/projects/ep/setup_form/?user_id=314159265358979323846264338327&tracking_id=EasyAsABC123OrSimpleAsDoReMi&product_name=PlantToHumanTranslator&product_id=161803398874989484820458683436563811772030917980576

The 200+ character request above will become truncated to:

https://empowerplant.io/api/0/projects/ep/setup_form/?user_id=314159265358979323846264338327&tracking_id=EasyAsABC123OrSimpleAsDoReMi&product_name=PlantToHumanTranslator&product_id=1618033988749894848

Instead, using span.set_tag and span.set_data preserves the details of this query using structured metadata. This could be done over baseUrl, endpoint, and parameters:

Copied
const baseUrl = "https://empowerplant.io";
const endpoint = "/api/0/projects/ep/setup_form";
const parameters = {
  user_id: 314159265358979323846264338327,
  tracking_id: "EasyAsABC123OrSimpleAsDoReMi",
  product_name: PlantToHumanTranslator,
  product_id: 161803398874989484820458683436563811772030917980576,
};

const span = transaction.startChild({
  op: "request",
  description: "setup form",
});

span.setTag("baseUrl", baseUrl);
span.setTag("endpoint", endpoint);
span.setData("parameters", parameters);
// you may also find some parameters to be valuable as tags
span.setData("user_id", parameters.user_id);
http.get(`${base_url}/${endpoint}/`, (data = parameters));

Grouping Transactions

When Sentry captures transactions, they are assigned a transaction name. This name is generally auto-generated by the Sentry SDK based on the framework integrations you are using. If you can't leverage the automatic transaction generation (or want to customize how transaction names are generated) you can use a global event processor that is registered when you initialize the SDK with your configuration.

An example of doing this in a node.js application:

Copied
import { addGlobalEventProcessor } from "@sentry/node";

addGlobalEventProcessor(event => {
  // if event is a transaction event
  if (event.type === "transaction") {
    event.transaction = sanitizeTransactionName(event.transaction);
  }
  return event;
});

For browser JavaScript applications using the BrowserTracing integration, the beforeNavigate option can be used to better group navigation/pageload transactions together based on URL.

Copied
import * as Sentry from "@sentry/browser";
import { Integrations } from "@sentry/tracing";

Sentry.init({
  // ...
  integrations: [
    new Integrations.BrowserTracing({
      beforeNavigate: context => {
        return {
          ...context,
          // You could use your UI's routing library to find the matching
          // route template here. We don't have one right now, so do some basic
          // parameter replacements.
          name: location.pathname
            .replace(/\d+/g, "<digits>")
            .replace(/[a-f0-9]{32}/g, "<hash>"),
        };
      },
    }),
  ],
});
You can edit this page on GitHub.