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

# Custom Instrumentation | Sentry for Flutter

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

This page covers both transaction mode (default, using transaction) and stream mode (using service spans). See [Streamed Spans](https://docs.sentry.io/platforms/dart/guides/flutter/tracing/streamed-spans.md) to learn more.

To instrument certain regions of your code, you can create a transaction (or a service span in stream mode) to capture them.

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

**Transaction Mode (Default)**

```dart
import 'package:sentry/sentry.dart';

final transaction = Sentry.startTransaction('processOrderBatch()', 'task');

try {
  processOrderBatch();
} catch (exception) {
  transaction.throwable = exception;
  transaction.status = SpanStatus.internalError();
} finally {
  await transaction.finish();
}
```

**Stream Mode**

```dart
import 'package:sentry/sentry.dart';

// Pass parentSpan: null to force a new service span.
// The status is set to error automatically if the callback throws.
await Sentry.startSpan(
  'processOrderBatch()',
  (span) async {
    span.setAttribute('sentry.op', SentryAttribute.string('task'));
    processOrderBatch();
  },
  parentSpan: null,
);
```

## [Add More Spans to the Transaction/Service Span](https://docs.sentry.io/platforms/dart/guides/flutter/tracing/instrumentation/custom-instrumentation.md#add-more-spans-to-the-transactionservice-span)

* Transaction mode: Transactions aren't bound to the scope, so they have to be passed manually as a method parameter to attach nested spans. Keep in mind that each individual span also needs to be manually finished, and spans are sent together with their parent transaction when the transaction is finished.
* Stream mode: Nested spans attach to the active span automatically — no manual passing needed. When creating a nested span, you can choose its name and `sentry.op` (see example below). Each span ends automatically when its callback completes, and spans are streamed as they finish.

**Transaction Mode (Default)**

```dart
import 'package:sentry/sentry.dart';

final transaction = Sentry.startTransaction('processOrderBatch()', 'task');

try {
  await processOrderBatch(transaction);
} catch (exception) {
  transaction.throwable = exception;
  transaction.status = SpanStatus.internalError();
} finally {
  await transaction.finish();
}

Future<void> processOrderBatch(ISentrySpan span) async {
  // span operation: task, span description: operation
  final innerSpan = span.startChild('task', description: 'operation');

  try {
    // omitted code
  } catch (exception) {
    innerSpan.throwable = exception;
    innerSpan.status = SpanStatus.notFound();
  } finally {
    await innerSpan.finish();
  }
}
```

**Stream Mode**

```dart
import 'package:sentry/sentry.dart';

await Sentry.startSpan(
  'processOrderBatch()',
  (span) async {
    span.setAttribute('sentry.op', SentryAttribute.string('task'));
    await processOrderBatch();
  },
  parentSpan: null,
);

// No need to pass the parent span — nested spans attach to the active span.
Future<void> processOrderBatch() async {
  await Sentry.startSpan(
    'operation',
    (innerSpan) async {
      innerSpan.setAttribute('sentry.op', SentryAttribute.string('task'));
      // omitted code
    },
  );
}
```

## [Retrieve a Transaction/Service Span](https://docs.sentry.io/platforms/dart/guides/flutter/tracing/instrumentation/custom-instrumentation.md#retrieve-a-transactionservice-span)

* Transaction mode: When you want to attach spans to an already ongoing transaction, use `Sentry.getSpan()`. It returns the running `SentryTransaction`, or `null` if there is none.
* Stream Mode: There's no public API to retrieve the active span. You don't need one: `Sentry.startSpan` attaches to the currently active span automatically, or starts a new service span if none is active.

**Transaction Mode (Default)**

```dart
import 'package:sentry/sentry.dart';

final span = Sentry.getSpan()?.startChild('task') ??
    Sentry.startTransaction('processOrderBatch()', 'task');

try {
  processOrderBatch();
} catch (exception) {
  span.throwable = exception;
  span.status = SpanStatus.internalError();
} finally {
  await span.finish();
}
```

**Stream Mode**

```dart
import 'package:sentry/sentry.dart';

// Attaches to the active span, or starts a new service span if none is active.
await Sentry.startSpan('task', (span) async {
  processOrderBatch();
});
```

## [Connect Errors with Spans](https://docs.sentry.io/platforms/dart/guides/flutter/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 **bound to the scope** is running are linked automatically.

* Transaction mode: Set `bindToScope: true`.
* Stream mode: The active span is bound automatically, so no flag is needed.

**Transaction Mode (Default)**

```dart
import 'package:sentry/sentry.dart';

final transaction = Sentry.startTransaction(
  'processOrderBatch()',
  'task',
  bindToScope: true,
);

try {
  processOrderBatch();
} catch (exception) {
  Sentry.captureException(exception);
} finally {
  await transaction.finish();
}
```

**Stream Mode**

```dart
import 'package:sentry/sentry.dart';

// The active span is bound automatically — no bindToScope needed.
await Sentry.startSpan('processOrderBatch()', (span) async {
  try {
    processOrderBatch();
  } catch (exception) {
    Sentry.captureException(exception);
  }
});
```

Exceptions may be thrown within spans that can finish before the exception gets reported to Sentry.

* Transaction mode: Link it by calling the `throwable` setter.
* Stream mode: An exception thrown inside the callback is recorded on the span and rethrown automatically.

**Transaction Mode (Default)**

```dart
import 'package:sentry/sentry.dart';

final transaction = Sentry.startTransaction('processOrderBatch()', 'task');

try {
  processOrderBatch();
} catch (exception) {
  transaction.throwable = exception;
  rethrow;
} finally {
  await transaction.finish();
}
```

**Stream Mode**

```dart
import 'package:sentry/sentry.dart';

// If the callback throws, the span is marked as errored and the error is rethrown.
await Sentry.startSpan('processOrderBatch()', (span) async {
  processOrderBatch();
});
```

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

You can add data attributes to your transactions and spans. This data is visible in the trace explorer in Sentry.

* Transaction mode: Use `setData` with `String`, `int`, `double` or `bool` values, as well as (non-mixed) arrays of these types.
* Stream mode: Use `setAttribute` or `setAttributes` with a typed [`SentryAttribute` factory](https://docs.sentry.io/platforms/dart/guides/flutter/tracing/streamed-spans.md#add-span-attributes).

### [For Transactions/Service Spans](https://docs.sentry.io/platforms/dart/guides/flutter/tracing/instrumentation/custom-instrumentation.md#for-transactionsservice-spans)

**Transaction Mode (Default)**

```dart
final transaction = Sentry.startTransaction('my-transaction', 'http.server');
transaction.setData('data_attribute_1', 'value1');
transaction.setData('data_attribute_2', 42);
transaction.setData('data_attribute_3', true);

transaction.setData('data_attribute_4', ['value1', 'value2']);
transaction.setData('data_attribute_5', [42, 43]);
transaction.setData('data_attribute_6', [true, false]);
```

**Stream Mode**

```dart
await Sentry.startSpan(
  'my-transaction',
  (span) async {
    span.setAttribute('sentry.op', SentryAttribute.string('http.server'));
    span.setAttribute('data_attribute_1', SentryAttribute.string('value1'));
    span.setAttribute('data_attribute_2', SentryAttribute.int(42));
    span.setAttribute('data_attribute_3', SentryAttribute.bool(true));

    span.setAttributes({
      'data_attribute_4': SentryAttribute.stringArray(['value1', 'value2']),
      'data_attribute_5': SentryAttribute.intArray([42, 43]),
      'data_attribute_6': SentryAttribute.boolArray([true, false])
    });
  },
  // force the creation of a service span
  parentSpan: null,
);
```

### [For Spans](https://docs.sentry.io/platforms/dart/guides/flutter/tracing/instrumentation/custom-instrumentation.md#for-spans)

**Transaction Mode (Default)**

```dart
final span = parent.startChild('http.client');
span.setData('data_attribute_1', 'value1');
span.setData('data_attribute_2', 42);
span.setData('data_attribute_3', true);

span.setData('data_attribute_4', ['value1', 'value2']);
span.setData('data_attribute_5', [42, 43]);
span.setData('data_attribute_6', [true, false]);
```

**Stream Mode**

```dart
await Sentry.startSpan('http.client', (span) async {
  span.setAttribute('data_attribute_1', SentryAttribute.string('value1'));
  span.setAttribute('data_attribute_2', SentryAttribute.int(42));
  span.setAttribute('data_attribute_3', SentryAttribute.bool(true));

  span.setAttributes({
    'data_attribute_4': SentryAttribute.stringArray(['value1', 'value2']),
    'data_attribute_5': SentryAttribute.intArray([42, 43]),
    'data_attribute_6': SentryAttribute.boolArray([true, false])
  });
});
```
