Custom Instrumentation

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.

Copied
import * as Sentry from "@sentry/nextjs";

Start Span

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.

Copied
const result = Sentry.startSpan({ name: "Important Function" }, () => {
  return expensiveFunction();
});

const result = await Sentry.startSpan(
  { name: "Important Function" },
  async () => {
    const res = Sentry.startSpan({ name: "Child Span" }, () => {
      return expensiveFunction();
    });

    return updateRes(res);
  }
);

const result = Sentry.startSpan({ name: "Important Function" }, (span) => {
  // You can access the span to add data or set specific status.
  // The span may be undefined if the span was not sampled or if performance monitoring is disabled.
  span?.setData("foo", "bar");
  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 for creating parallel spans that are not related to each other.

Copied
// Start a span that tracks the duration of middleware
function middleware(_req, res, next) {
  return Sentry.startSpanManual({ name: "middleware" }, (span, finish) => {
    res.once("finish", () => {
      span?.setHttpStatus(res.status);
      finish();
    });
    return next();
  });
}

Start Inactive Spans

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.

Copied
const span1 = Sentry.startInactiveSpan({ name: "span1" });

someWork();

const span2 = Sentry.startInactiveSpan({ name: "span2" });

moreWork();

const span3 = Sentry.startInactiveSpan({ name: "span3" });

evenMoreWork();

span1?.finish();
span2?.finish();
span3?.finish();

Adding Span operations

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.

Copied
const result = Sentry.startSpan({ name: 'GET /users', op: 'http.client' }, () => {
  return fetchUsers();
})

Start Transaction

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 transactions to capture them.

This is valid for all JavaScript SDKs (both backend and frontend) and works independently of the Express, Http, and BrowserTracing integrations.

Copied
const transaction = Sentry.startTransaction({ name: "test-transaction" });
const span = transaction.startChild({ op: "functionX" }); // This function returns a Span
// functionCallX
span.finish(); // Remember that only finished spans will be sent with the transaction
transaction.finish(); // Finishing the transaction will send it to Sentry

For example, if you want to create a transaction for a user interaction on your page:

Copied
// Let's say this function is invoked when a user clicks on the checkout button of your shop
function shopCheckout() {
  // This will create a new Transaction for you
  const transaction = Sentry.startTransaction({ name: "shopCheckout" });
  // Set transaction on scope to associate with errors and get included span instrumentation
  // If there's currently an unfinished transaction, it may be dropped
  Sentry.getCurrentScope().setSpan(transaction));

  // Assume this function makes a fetch call
  const result = validateShoppingCartOnServer();

  const span = transaction.startChild({
    data: {
      result,
    },
    op: "task",
    description: `processing shopping cart result`,
  });
  try {
    processAndValidateShoppingCart(result);
    span.setStatus("ok");
  } catch (err) {
    span.setStatus("unknown_error");
    throw err;
  } finally {
    span.finish();
    transaction.finish();
  }
}

This example will send a transaction shopCheckout to Sentry. The transaction will contain a task span that measures how long processAndValidateShoppingCart took. Finally, the call to transaction.finish() will finish the transaction and send it 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 transaction.finish() is called to be included in the transaction.

For example:

Copied
function processItem(item, transaction) {
  const span = transaction.startChild({
    op: "http",
    description: `GET /items/:item-id`,
  });

  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.setData("http.foobarsessionid", getFoobarSessionid(response));
        span.finish();
        resolve(response);
      });
    });
  });
}
Help improve this content
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").