Custom Instrumentation

To instrument certain regions of your code, you can create transactions to capture them.

The following example creates a transaction for a scope that contains an expensive operation (for example, processOrderBatch), and sends the result to Sentry:

Copied
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();
} catch (Exception e) {
  transaction.setThrowable(e);
  transaction.setStatus(SpanStatus.INTERNAL_ERROR);
  throw e;
} finally {
  transaction.finish();
}

Add More Spans to the Transaction

The next example contains the implementation of the hypothetical processItem function called from the code snippet in the previous section. Our SDK can determine if there is currently an open transaction and add all newly created spans as child operations to that transaction. Keep in mind that each individual span also needs to be manually finished; otherwise, spans will not show up in the transaction.

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.

You can choose the value of operation and description.

Copied
import java.io.FileNotFoundException;

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

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

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.

Retrieve a Transaction

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 io.sentry.ISpan;
import io.sentry.Sentry;
import io.sentry.SpanStatus;

ISpan span = Sentry.getSpan();
if (span == null) {
  span = Sentry.startTransaction("transaction-name", "task");
} else {
  span = span.startChild("task");
}

try {
  // omitted code
} catch (Exception e) {
  span.setThrowable(e);
  span.setStatus(SpanStatus.INTERNAL_ERROR);
  throw e;
} finally {
  span.finish();
}

Connect Errors with Spans

Sentry errors can be linked with transactions and spans.

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

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

ITransaction span = Sentry.startTransaction(item.getTransactionName(), "task");
Sentry.configureScope(scope -> scope.setTransaction(span));
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();
}

Set Transaction to Current Scope

To avoid passing ITransaction objects to method calls when creating child spans, you can bind the transaction to the current scope using Sentry#configureScope and Scope#setTransaction methods:

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

void executeTask() {
  ITransaction transaction = Sentry.startTransaction("transaction-name", "task");
  Sentry.configureScope(scope -> scope.setTransaction(transaction));

  executeSubTask();

  transaction.finish();
}

private void executeSubTask() {
  ISpan span = Sentry.getSpan();
  if (span == null) {
    span = Sentry.startTransaction("transaction-name", "sub-task");
  } else {
    span = span.startChild("operation");
  }
  // ...
  span.finish();
}

Distributed Tracing

Traces can bridge across multiple software services. Each span in a trace can be represented as a sentry-trace header, containing the trace id, span id, and sampling details. This sentry-trace header can be passed to downstream services so that they can create spans that are a continuation of the trace started in the originating service.

To obtain a trace header from the span, use ISpan#toSentryTrace() method. Then pass it to the downstream service. If the communication happens over HTTP, it is recommended that you set the value to the sentry-trace (also available as a constant SentryTraceHeader.SENTRY_TRACE_HEADER) HTTP header.

To create a span as a continuation of the trace retrieved from the upstream service, pass the sentry-trace header value to the transaction context:

Copied
import io.sentry.ITransaction;
import io.sentry.Sentry;
import io.sentry.SentryTraceHeader;
import io.sentry.TransactionContext;
import io.sentry.exception.InvalidSentryTraceHeaderException;

String sentryTrace = request.getHeader(SentryTraceHeader.SENTRY_TRACE_HEADER);
ITransaction transaction = null;
try {
  transaction = Sentry.startTransaction(TransactionContext.fromSentryTrace("name", "op", new SentryTraceHeader(sentryTrace)));
} catch (InvalidSentryTraceHeaderException e) {
  // handle invalid trace header
}
You can edit this page on GitHub.