---
title: "Custom Instrumentation"
description: "Learn how to capture performance data on any action in your app."
url: https://docs.sentry.io/platforms/rust/guides/axum/tracing/instrumentation/custom-instrumentation/
---

# Custom Instrumentation | Sentry for axum

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

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

```rust
// Transaction can be started by providing the name and the operation
let tx_ctx = sentry::TransactionContext::new(
    "transaction name",
    "transaction operation",
);
let transaction = sentry::start_transaction(tx_ctx);

// Transactions can have child spans, and those spans can have child spans as well.
let span = transaction.start_child("span operation", "span description");

// ...
// Perform your operations
// ...

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 in your application:

```rust
// Let's say this method is called in a background thread when a user clicks on the checkout button of your shop
fn perform_checkout() {
    // This will create a new Transaction for you
    let tx_ctx = sentry::TransactionContext::new(
        "checkout",
        "perform-checkout",
    );
    let transaction = sentry::start_transaction(tx_ctx);

    // Validate the cart
    let validation_span = transaction.start_child(
        "validation",
        "validating shopping cart",
    );

    validate_shopping_cart(); //Some long process, maybe a sync http request.

    validation_span.finish();

    // Process the order
    let process_span = transaction.start_child(
        "process",
        "processing shopping cart",
    );

    process_shopping_cart(); //Another time consuming process.

    process_span.finish();

    transaction.finish();
}
```

This example will send a transaction named `checkout` to Sentry. The transaction will contain a `validation` span that measures how long `validate_shopping_cart()` took and a `process` span that measures `process_shopping_cart()`. Finally, the call to `transaction.finish()` will finish the transaction and send it to Sentry.

## [Retrieve a Transaction or Span](https://docs.sentry.io/platforms/rust/guides/axum/tracing/instrumentation/custom-instrumentation.md#retrieve-a-transaction-or-span)

In cases where you want to attach spans to an already ongoing transaction, you can use `Scope::get_span`. This method will return a `TransactionOrSpan` in case there is a running transaction or span; otherwise it returns `None`.

Transactions or spans need to be manually attached to the scope using `Scope::set_span`.

```rust
// Retrieve the currently running span
let parent_span = sentry::configure_scope(|scope| scope.get_span());

let span: sentry::TransactionOrSpan = match &parent_span {
    Some(parent) => parent.start_child("subtask", "description").into(),
    None => {
        let ctx = sentry::TransactionContext::new("task", "op");
        sentry::start_transaction(ctx).into()
    }
};

// Set the currently running span
sentry::configure_scope(|scope| scope.set_span(Some(span)));
```

## [Transactions for Async Tasks](https://docs.sentry.io/platforms/rust/guides/axum/tracing/instrumentation/custom-instrumentation.md#transactions-for-async-tasks)

Special care must be taken when spawning operations as independent threads or asynchronous tasks.

In situations where the computation can run longer than the calling context, you should always start a new transaction. The SDK offers the `TransactionContext::continue_from_span` method to make that more convenient.

```rust
let parent_span = sentry::configure_scope(|scope| scope.get_span());
// Create a new transaction as an independent continuation
let ctx = sentry::TransactionContext::continue_from_span(
    "A long-running spawned task",
    "spawned_task",
    parent_span,
);

let task = async move {
  let transaction = sentry::start_transaction(ctx);
  // Set the newly created transaction as the *current span*
  sentry::configure_scope(|scope| scope.set_span(Some(transaction.clone().into())));

  // Do the actual computation
  // ...

  // Finish the span and send it to sentry
  transaction.finish();
  // The task gets a
}.bind_hub(Hub::new_from_top(Hub::current()));

task::spawn(task);
```

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

Sentry errors can be linked with transactions and spans.

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

```rust
let tx_ctx = sentry::TransactionContext::new(
    "checkout",
    "perform-checkout",
);
let transaction = sentry::start_transaction(tx_ctx);

// Bind the transaction / span to the scope:
sentry::configure_scope(|scope| scope.set_span(Some(transaction.into())));

// The error is linked to the transaction / span:
let err = "NaN".parse::<usize>().unwrap_err();
sentry::capture_error(&err);

transaction.finish();
```

## [Adding Span & Transaction Data Attributes](https://docs.sentry.io/platforms/rust/guides/axum/tracing/instrumentation/custom-instrumentation.md#adding-span--transaction-data-attributes)

You can capture data attributes along with your spans and transactions. You can set data attributes when starting a span or transaction:

```rust
use serde_json::json;

// Create a transaction and assign data attributes...
let tx_ctx = sentry::TransactionContext::new("Example Transaction", "http.server");
let transaction = sentry::start_transaction(tx_ctx);
transaction.set_data("attribute_1", "hello".into());
transaction.set_data("attribute_2", 42.into());

// ... or create a span and assign data attributes
let span = transaction.start_child("http.client", "Example span");
span.set_data("is_ok", true.into());
span.set_data("other_data", json!({ "an": "object" }));
```

Or you can add data attributes to an existing span:

```rust
let span = Hub::current().configure_scope(|scope| scope.get_span());
if let Some(span) = span {
    span.set_data("hello", "world".into());
}
```
