Custom Instrumentation for Flutter
Learn how to capture performance data on any action in your app using the Sentry Flutter SDK.
To capture transactions and spans customized to your organization's needs, you must first set up tracing.
This page covers both transaction mode (default, using transaction) and stream mode (using service spans). See Streamed Spans 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:
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();
}
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();
}
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: 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.
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();
}
}
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();
}
}
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: When you want to attach spans to an already ongoing transaction, use
Sentry.getSpan(). It returns the runningSentryTransaction, ornullif there is none. - Stream Mode: There's no public API to retrieve the active span. You don't need one:
Sentry.startSpanattaches to the currently active span automatically, or starts a new service span if none is active.
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();
}
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();
}
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();
});
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.
import 'package:sentry/sentry.dart';
final transaction = Sentry.startTransaction(
'processOrderBatch()',
'task',
bindToScope: true,
);
try {
processOrderBatch();
} catch (exception) {
Sentry.captureException(exception);
} finally {
await transaction.finish();
}
import 'package:sentry/sentry.dart';
final transaction = Sentry.startTransaction(
'processOrderBatch()',
'task',
bindToScope: true,
);
try {
processOrderBatch();
} catch (exception) {
Sentry.captureException(exception);
} finally {
await transaction.finish();
}
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
throwablesetter. - Stream mode: An exception thrown inside the callback is recorded on the span and rethrown automatically.
import 'package:sentry/sentry.dart';
final transaction = Sentry.startTransaction('processOrderBatch()', 'task');
try {
processOrderBatch();
} catch (exception) {
transaction.throwable = exception;
rethrow;
} finally {
await transaction.finish();
}
import 'package:sentry/sentry.dart';
final transaction = Sentry.startTransaction('processOrderBatch()', 'task');
try {
processOrderBatch();
} catch (exception) {
transaction.throwable = exception;
rethrow;
} finally {
await transaction.finish();
}
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();
});
You can add data attributes to your transactions and spans. This data is visible in the trace explorer in Sentry.
- Transaction mode: Use
setDatawithString,int,doubleorboolvalues, as well as (non-mixed) arrays of these types. - Stream mode: Use
setAttributeorsetAttributeswith a typedSentryAttributefactory.
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]);
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]);
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,
);
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]);
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]);
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])
});
});
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").