---
title: "Custom Instrumentation"
description: "Learn how to enable trace and span data-capturing on any action in your app."
url: https://docs.sentry.io/platforms/android/tracing/instrumentation/custom-instrumentation/
---

# Custom Instrumentation | Sentry for Android

To capture transactions and spans customized to your organization's needs, you must first [set up tracing.](https://docs.sentry.io/platforms/android/tracing.md)

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

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

**Java**

```java
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();
}
```

**Kotlin**

```kotlin
import io.sentry.Sentry
import io.sentry.SpanStatus

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

By default the transaction is not bound to the scope. Our auto instrumentation, for example those instrumenting database requests or HTTP calls, rely on a transaction to be present in order to attach new child spans to it. Please take a look at the [Create Transaction Bound to The Current Scope](https://docs.sentry.io/platforms/android/tracing/instrumentation/custom-instrumentation.md#create-transaction-bound-to-the-current-scope) section further down.

## [Add More Spans to the Transaction](https://docs.sentry.io/platforms/android/tracing/instrumentation/custom-instrumentation.md#add-more-spans-to-the-transaction)

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`.

**Java**

```java
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 parentSpan) {
  // span operation: task, span description: operation
  ISpan innerSpan = parentSpan.startChild("task", "operation");
  try {
    // omitted code
  } catch (FileNotFoundException e) {
    innerSpan.setThrowable(e);
    innerSpan.setStatus(SpanStatus.NOT_FOUND);
    throw e;
  } finally {
    innerSpan.finish();
  }
}
```

**kotlin**

```kotlin
import java.io.FileNotFoundException

import io.sentry.Sentry
import io.sentry.SpanStatus

val transaction = Sentry.startTransaction("processOrderBatch()", "task")
try {
  processOrderBatch(transaction)
} catch (e: Exception) {
  transaction.throwable = e
  transaction.status = SpanStatus.INTERNAL_ERROR
  throw e
} finally {
  transaction.finish()
}

fun processOrderBatch(parentSpan: ISpan) {
  // span operation: task, span description: operation
  val innerSpan = parentSpan.startChild("task", "operation")

  try {
    // omitted code
  } catch (e: FileNotFoundException) {
    innerSpan.throwable = e
    innerSpan.status = 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.

## [Create Transaction Bound to The Current Scope](https://docs.sentry.io/platforms/android/tracing/instrumentation/custom-instrumentation.md#create-transaction-bound-to-the-current-scope)

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`.

Our auto instrumentation, for example those instrumenting database requests or HTTP calls, require a transaction to be bound to the current scope in order to attach a new child span.

`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`.

**java**

```java
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 parentSpan = Sentry.getSpan(); // returns the "processOrderBatch()" transaction
  ISpan innerSpan = null;
  if (parentSpan != null) {
    innerSpan = parentSpan.startChild("task", "operation");
  }
  
  try {
    // omitted code
  } catch (FileNotFoundException e) {
    if (innerSpan != null) {
      innerSpan.setThrowable(e);
      innerSpan.setStatus(SpanStatus.NOT_FOUND);
    throw e;
  } finally {
    if (innerSpan != null) {
      innerSpan.finish();
    }
  }
}
```

**kotlin**

```kotlin
import java.io.FileNotFoundException

import io.sentry.Sentry
import io.sentry.SpanStatus
import io.sentry.TransactionOptions

val transaction = Sentry.startTransaction("processOrderBatch()", "task", TransactionOptions().apply { isBindToScope = true })
try {
  processOrderBatch()
} catch (e: Exception) {
  transaction.throwable = e
  transaction.status = SpanStatus.INTERNAL_ERROR
  throw e
} finally {
  transaction.finish()
}

fun processOrderBatch() {
  val parentSpan = Sentry.getSpan() // returns the "processOrderBatch()" transaction
  val innerSpan = parentSpan?.startChild("task", "operation")

  try {
    // omitted code
  } catch (e: FileNotFoundException) {
    innerSpan?.throwable = e
    innerSpan?.status = SpanStatus.NOT_FOUND
    throw e
  } finally {
    innerSpan?.finish()
  }
}
```

## [Connect Errors with Spans](https://docs.sentry.io/platforms/android/tracing/instrumentation/custom-instrumentation.md#connect-errors-with-spans)

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:

**Java**

```java
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();
}
```

**Kotlin**

```kotlin
import io.sentry.Sentry
import io.sentry.TransactionOptions;

val span = Sentry.startTransaction(item.getTransactionName(), "task", TransactionOptions().apply { isBindToScope = true })
try {
  processItem()
} catch (e: Exception) {
  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:

**Java**

```java
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();
}
```

**Kotlin**

```kotlin
import io.sentry.Sentry

val span = Sentry.getSpan()?: Sentry.startTransaction(item.getTransactionName(), "task")
try {
  processItem()
} catch (e: Exception) {
  span.throwable = e
  throw e
} finally {
  span.finish()
}
```

## [Improving Data on Transactions and Spans](https://docs.sentry.io/platforms/android/tracing/instrumentation/custom-instrumentation.md#improving-data-on-transactions-and-spans)

### [Adding Data Attributes to Transactions](https://docs.sentry.io/platforms/android/tracing/instrumentation/custom-instrumentation.md#adding-data-attributes-to-transactions)

You can add data attributes to your transactions. This data is visible in the trace explorer in Sentry. Data attributes can be of type `String`, `Number` or `Boolean`, as well as (non-mixed) arrays of these types:

**java**

```java
ITransaction transaction = Sentry.startTransaction("processOrderBatch()", "task");
transaction.setData("my-data-attribute-1", "value1");
transaction.setData("my-data-attribute-2", 42);
transaction.setData("my-data-attribute-3", true);

transaction.setData("my-data-attribute-4", Arrays.asList("value1", "value2", "value3"));
transaction.setData("my-data-attribute-5", Arrays.asList(42, 43, 44));
transaction.setData("my-data-attribute-6", Arrays.asList(true, false, true));
```

**kotlin**

```kotlin
ITransaction transaction = Sentry.startTransaction("processOrderBatch()", "task");
transaction.setData("my-data-attribute-1", "value1")
transaction.setData("my-data-attribute-2", 42)
transaction.setData("my-data-attribute-3", true)

transaction.setData("my-data-attribute-4", listOf("value1", "value2", "value3"))
transaction.setData("my-data-attribute-5", listOf(42, 43, 44))
transaction.setData("my-data-attribute-6", listOf(true, false, true))
```

### [Adding Data Attributes to Spans](https://docs.sentry.io/platforms/android/tracing/instrumentation/custom-instrumentation.md#adding-data-attributes-to-spans)

You can add data attributes to your spans. This data is visible in the trace explorer in Sentry. Data attributes can be of type `String`, `Number` or `Boolean`, as well as (non-mixed) arrays of these types:

**java**

```java
ISpan span = parent.startChild("task", "operation");
span.setData("my-data-attribute-1", "value1");
span.setData("my-data-attribute-2", 42);
span.setData("my-data-attribute-3", true);

span.setData("my-data-attribute-4", Arrays.asList("value1", "value2", "value3"));
span.setData("my-data-attribute-5", Arrays.asList(42, 43, 44));
span.setData("my-data-attribute-6", Arrays.asList(true, false, true));
```

**kotlin**

```kotlin
ISpan span = parent.startChild("task", "operation");
span.setData("my-data-attribute-1", "value1")
span.setData("my-data-attribute-2", 42)
span.setData("my-data-attribute-3", true)

span.setData("my-data-attribute-4", listOf("value1", "value2", "value3"))
span.setData("my-data-attribute-5", listOf(42, 43, 44))
span.setData("my-data-attribute-6", listOf(true, false, true))
```

### [Adding Attributes to all Spans and Transactions](https://docs.sentry.io/platforms/android/tracing/instrumentation/custom-instrumentation.md#adding-attributes-to-all-spans-and-transactions)

To add an attribute to all spans, use the `beforeSendTransaction` callback:

**java**

```java
Sentry.init(options -> {
    options.setBeforeSendTransaction((transaction, hint) -> {
        
        // set the attribute on the root span
        if (transaction.getContexts().getTrace() == null) {
            SpanContext spanContext = new SpanContext("op");
            transaction.getContexts().setTrace(spanContext);
        }
        transaction.getContexts().getTrace().setData("myAttribute", "myValue");

        // and on all child spans
        transaction.getSpans().forEach(span -> {
            if (span.getData() == null) {
                span.setData(new HashMap<>());
            }
            span.getData().put("myAttribute", "myValue");
        });

        return transaction;
    });
});
```

**kotlin**

```kotlin
Sentry.init { options ->
    options.setBeforeSendTransaction { transaction, hint ->
    
        // set the attribute on the root span
        if (transaction.contexts.trace == null) {
            transaction.contexts.setTrace(SpanContext("op"))
        }
        transaction.contexts.trace?.setData("myAttribute", "myValue")

        // and on all child spans
        transaction.spans.forEach { span ->
            if (span.data == null) {
                span.data = HashMap()
            }
            span.data?.set("myAttribute", "myValue")
        }

        transaction
    }
}
```
