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 instrument certain regions of your code, you can create transactions to capture them.

Every Spring bean method execution can be turned into a transaction or a span.

To enable this feature, you must include spring-aop and aspectj in your application:

Copied
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

Then, import SentryTracingConfiguration in one of your @Configuration classes:

Copied
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import io.sentry.spring.tracing.SentryTracingConfiguration;

@Import(SentryTracingConfiguration.class)
class SentryConfig {
}

Methods executed outside of Spring MVC request processing can be turned into transactions by annotating them with @SentryTransaction annotation:

Copied
import org.springframework.stereotype.Component;
import org.springframework.scheduling.annotation.Scheduled;
import io.sentry.spring.tracing.SentryTransaction;

@Component
class ScheduledJob {

  @Scheduled(...)
  @SentryTransaction(operation = "task")
  void execute() {
    ...
  }
}

@SentryTransaction can be configured with custom name. If not defined, name will be resolved from the class, and the method name.

@SentryTransaction can be also placed on a class or an interface - it turns every method execution from the annotated type into a transaction.

Advanced Spring AOP users can redefine transaction pointcut by providing a custom org.springframework.aop.Pointcut bean with name sentryTransactionPointcut.

To create a span around a method execution, annotate method with @SentrySpan annotation:

Copied
import org.springframework.stereotype.Component;
import io.sentry.spring.tracing.SentrySpan;

@Component
class PersonService {

  @SentrySpan
  Person findById(Long id) {
    ...
  }
}

@SentrySpan can be configured with custom description property. If not defined, operation will be resolved from the class, and the method name.

@SentrySpan can be also placed on a class or an interface - it turns every method execution from the annotated type into a span.

Advanced Spring AOP users can redefine around which methods spans and transactions are created by creating custom advice, pointcut and advisor beans instead of importing SentryTracingConfiguration class.

By default, transactions are not bound to the scope. Transaction has to be passed manually as a method parameter to enable attaching nested spans. When creating nested span, you can choose the value of operation and description.

Copied
import java.io.FileNotFoundException;

import io.sentry.ISpan;
import io.sentry.ITransaction;
import io.sentry.Sentry;
import io.sentry.SpanStatus;

// A good name for the transaction is key, to help identify what this is about
ITransaction transaction = Sentry.startTransaction("processOrderBatch()", "task");
try {
  processOrderBatch(transaction);
} catch (Exception e) {
  transaction.setThrowable(e);
  transaction.setStatus(SpanStatus.INTERNAL_ERROR);
  throw e;
} finally {
  transaction.finish();
}

void processOrderBatch(ISpan span) {
  if (span == null) {
    span = Sentry.startTransaction("processOrderBatch()", "task");
  }
  // span operation: task, span description: operation
  ISpan innerSpan = span.startChild("task", "operation");
  try {
    // omitted code
  } catch (FileNotFoundException e) {
    innerSpan.setThrowable(e);
    innerSpan.setStatus(SpanStatus.NOT_FOUND);
    throw e;
  } finally {
    innerSpan.finish();
  }
}

Keep in mind that each individual span also needs to be manually finished; otherwise, spans will not show up in the transaction.

Spans are sent together with their parent transaction when the transaction is finished. Make sure to call finish() on transaction once all the child spans have finished.

Our SDK can bind a transaction to the scope making it accessible to every method running within this scope by calling Sentry#startTransaction method with bindToScope parameter to true.

bindToScope additionally ensures that your new transaction replaces any one that may be already started. This is useful if you want custom instrumentation to co-exist with auto-instrumented transactions.

In cases where you want to attach Spans to an already ongoing Transaction you can use Sentry#getSpan. This method will return a SentryTransaction in case there is a running Transaction or a Span in case there is already a running Span, otherwise it returns null.

Copied
import java.io.FileNotFoundException;

import io.sentry.ISpan;
import io.sentry.ITransaction;
import io.sentry.Sentry;
import io.sentry.SpanStatus;
import io.sentry.TransactionOptions;

// A good name for the transaction is key, to help identify what this is about
TransactionOptions txOptions = new TransactionOptions();
txOptions.setBindToScope(true);
ITransaction transaction = Sentry.startTransaction("processOrderBatch()", "task", txOptions);
try {
  processOrderBatch();
} catch (Exception e) {
  transaction.setThrowable(e);
  transaction.setStatus(SpanStatus.INTERNAL_ERROR);
  throw e;
} finally {
  transaction.finish();
}

void processOrderBatch() {
  ISpan span = Sentry.getSpan();
  if (span == null) {
    span = Sentry.startTransaction("processOrderBatch()", "task");
  }
  ISpan innerSpan = span.startChild("task", "operation");
  try {
    // omitted code
  } catch (FileNotFoundException e) {
    innerSpan.setThrowable(e);
    innerSpan.setStatus(SpanStatus.NOT_FOUND);
    throw e;
  } finally {
    innerSpan.finish();
  }
}

Sentry errors can be linked with transactions and spans.

Errors reported to Sentry while transaction or span bound to the scope is running are linked automatically:

Copied
import io.sentry.Sentry;
import io.sentry.ISpan;
import io.sentry.ITransaction;
import io.sentry.TransactionOptions;

TransactionOptions txOptions = new TransactionOptions();
txOptions.setBindToScope(true);
ITransaction span = Sentry.startTransaction(item.getTransactionName(), "task", txOptions);
try {
  processItem();
} catch (Exception e) {
  Sentry.captureException(e);
} finally {
  span.finish();
}

Exceptions may be thrown within spans that can finish before exception gets reported to Sentry. To attach span information to this exception, you must link it by calling setThrowable method:

Copied
import io.sentry.Sentry;
import io.sentry.ISpan;

ISpan span = Sentry.getSpan();
if (span == null) {
  span = Sentry.startTransaction(item.getTransactionName(), "task");
}
try {
  processItem();
} catch (Exception e) {
  span.setThrowable(e);
  throw e;
} finally {
  span.finish();
}
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").