Skip to content

Commit

Permalink
remove breadcrumbs from transaction in sentry client
Browse files Browse the repository at this point in the history
  • Loading branch information
denrase committed Mar 28, 2023
1 parent 0be962b commit ace80a3
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 24 deletions.
55 changes: 36 additions & 19 deletions dart/lib/src/sentry_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,7 @@ class SentryClient {
return _sentryId;
}

if (_options.platformChecker.platform.isAndroid &&
_options.enableScopeSync) {
/*
We do this to avoid duplicate breadcrumbs on Android as sentry-android applies the breadcrumbs
from the native scope onto every envelope sent through it. This scope will contain the breadcrumbs
sent through the scope sync feature. This causes duplicate breadcrumbs.
We then remove the breadcrumbs in all cases but if it is handled == false,
this is a signal that the app would crash and android would lose the breadcrumbs by the time the app is restarted to read
the envelope.
*/
preparedEvent = _eventWithRemovedBreadcrumbsIfHandled(preparedEvent);
}
preparedEvent = _eventWithoutBreadcrumbsIfNeeded(preparedEvent);

var attachments = List<SentryAttachment>.from(scope?.attachments ?? []);
var screenshot = hint.screenshot;
Expand Down Expand Up @@ -329,6 +318,9 @@ class SentryClient {
return _sentryId;
}

preparedTransaction =
_transactionWithoutBreadcrumbsIfNeeded(preparedTransaction);

final attachments = scope?.attachments
.where((element) => element.addToTransactions)
.toList();
Expand Down Expand Up @@ -462,18 +454,43 @@ class SentryClient {
_options.recorder.recordLostEvent(reason, category);
}

SentryEvent _eventWithRemovedBreadcrumbsIfHandled(SentryEvent event) {
SentryEvent _eventWithoutBreadcrumbsIfNeeded(SentryEvent event) {
if (_shouldRemoveBreadcrumbs(event)) {
return event.copyWith(breadcrumbs: []);
} else {
return event;
}
}

SentryTransaction _transactionWithoutBreadcrumbsIfNeeded(
SentryTransaction transaction) {
if (_shouldRemoveBreadcrumbs(transaction)) {
return transaction.copyWith(breadcrumbs: []);
} else {
return transaction;
}
}

/// We do this to avoid duplicate breadcrumbs on Android as sentry-android applies the breadcrumbs
/// from the native scope onto every envelope sent through it. This scope will contain the breadcrumbs
/// sent through the scope sync feature. This causes duplicate breadcrumbs.
/// We then remove the breadcrumbs in all cases but if it is handled == false,
/// this is a signal that the app would crash and android would lose the breadcrumbs by the time the app is restarted to read
/// the envelope.
bool _shouldRemoveBreadcrumbs(SentryEvent event) {
final isAndroid = _options.platformChecker.platform.isAndroid;
final enableScopeSync = _options.enableScopeSync;

if (!isAndroid || !enableScopeSync) {
return false;
}

final mechanisms =
(event.exceptions ?? []).map((e) => e.mechanism).whereType<Mechanism>();
final hasNoMechanism = mechanisms.isEmpty;
final hasOnlyHandledMechanism =
mechanisms.every((e) => (e.handled ?? true));

if (hasNoMechanism || hasOnlyHandledMechanism) {
return event.copyWith(breadcrumbs: []);
} else {
return event;
}
return hasNoMechanism || hasOnlyHandledMechanism;
}

Future<SentryId?> _attachClientReportsAndSend(SentryEnvelope envelope) {
Expand Down
33 changes: 28 additions & 5 deletions dart/test/sentry_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1154,7 +1154,28 @@ void main() {
fixture = Fixture();
});

test('Clears breadcrumbs on Android if mechanism.handled is true',
test('Clears breadcrumbs on Android for transaction', () async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker =
MockPlatformChecker(platform: MockPlatform.android());

final client = fixture.getSut();
final transaction = SentryTransaction(
fixture.tracer,
breadcrumbs: [
Breadcrumb(),
],
);
await client.captureTransaction(transaction);

final capturedEnvelope = (fixture.transport).envelopes.first;
final capturedTransaction =
await transactionFromEnvelope(capturedEnvelope);

expect((capturedTransaction['breadcrumbs'] ?? []).isEmpty, true);
});

test('Clears breadcrumbs on Android if mechanism.handled is true for event',
() async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker =
Expand All @@ -1181,7 +1202,7 @@ void main() {
expect((capturedEvent.breadcrumbs ?? []).isEmpty, true);
});

test('Clears breadcrumbs on Android if mechanism.handled is null',
test('Clears breadcrumbs on Android if mechanism.handled is null for event',
() async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker =
Expand All @@ -1205,7 +1226,8 @@ void main() {
expect((capturedEvent.breadcrumbs ?? []).isEmpty, true);
});

test('Clears breadcrumbs on Android if theres no mechanism', () async {
test('Clears breadcrumbs on Android if theres no mechanism for event',
() async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker =
MockPlatformChecker(platform: MockPlatform.android());
Expand All @@ -1227,7 +1249,8 @@ void main() {
expect((capturedEvent.breadcrumbs ?? []).isEmpty, true);
});

test('Does not clear breadcrumbs on Android if mechanism.handled is false',
test(
'Does not clear breadcrumbs on Android if mechanism.handled is false for event',
() async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker =
Expand Down Expand Up @@ -1255,7 +1278,7 @@ void main() {
});

test(
'Does not clear breadcrumbs on Android if any mechanism.handled is false',
'Does not clear breadcrumbs on Android if any mechanism.handled is false for event',
() async {
fixture.options.enableScopeSync = true;
fixture.options.platformChecker =
Expand Down
17 changes: 17 additions & 0 deletions flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,23 @@ class MainScaffold extends StatelessWidget {
child: Column(
children: [
const Center(child: Text('Trigger an action:\n')),
ElevatedButton(
onPressed: () async {
final transaction = Sentry.getSpan()?.startChild('foo') ??
Sentry.startTransaction(
'foo',
'op',
bindToScope: true,
);

final crumb = Breadcrumb(message: "bar");
Sentry.addBreadcrumb(crumb);

await Future.delayed(
const Duration(seconds: 2), transaction.finish);
},
child: const Text('Duplicate Transaction Breadcrumbs'),
),
ElevatedButton(
onPressed: () => sqfliteTest(),
child: const Text('sqflite'),
Expand Down

0 comments on commit ace80a3

Please sign in to comment.