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

# Custom Instrumentation 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 stream mode (the default, using service spans) and transaction mode (using transactions). See [Streamed Spans](https://docs.sentry.io/platforms/dart/guides/flutter/tracing/streamed-spans.md) to learn more.

To use the Transaction Mode tab, set `options.traceLifecycle = SentryTraceLifecycle.static` during SDK initialization.

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:

**Stream Mode (Default)**

```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,
);
```

**Transaction Mode**

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

## [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.

**Stream Mode (Default)**

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

**Transaction Mode**

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

## [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.

**Stream Mode (Default)**

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

**Transaction Mode**

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

## [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.

**Stream Mode (Default)**

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

**Transaction Mode**

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

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.

**Stream Mode (Default)**

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

**Transaction Mode**

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

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

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

## [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)

**Stream Mode (Default)**

```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,
);
```

**Transaction Mode**

```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]);
```

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

**Stream Mode (Default)**

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

**Transaction Mode**

```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]);
```
