Skip to content

Commit a37a793

Browse files
authored
Load Device Contexts from Sentry Java (#1616)
1 parent 052a368 commit a37a793

File tree

8 files changed

+38
-267
lines changed

8 files changed

+38
-267
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
- Do not leak extensions of external classes ([#1576](https://github.com/getsentry/sentry-dart/pull/1576))
1313
- Make `hint` non-nullable in `BeforeSendCallback`, `BeforeBreadcrumbCall` and `EventProcessor` ([#1574](https://github.com/getsentry/sentry-dart/pull/1574))
1414
- This will affect your callbacks, making this a breaking change.
15-
15+
- Load Device Contexts from Sentry Java ([#1616](https://github.com/getsentry/sentry-dart/pull/1616))
16+
- Now the device context from Android is available in `BeforeSendCallback`
17+
1618
## Unreleased
1719

1820
### Fixes

dart/lib/src/sentry_client.dart

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,6 @@ class SentryClient {
108108
return _sentryId;
109109
}
110110

111-
preparedEvent = _eventWithoutBreadcrumbsIfNeeded(preparedEvent);
112-
113111
var attachments = List<SentryAttachment>.from(scope?.attachments ?? []);
114112
attachments.addAll(hint.attachments);
115113
var screenshot = hint.screenshot;
@@ -322,8 +320,6 @@ class SentryClient {
322320
return _sentryId;
323321
}
324322

325-
preparedTransaction = _eventWithoutBreadcrumbsIfNeeded(preparedTransaction);
326-
327323
final attachments = scope?.attachments
328324
.where((element) => element.addToTransactions)
329325
.toList();
@@ -457,40 +453,6 @@ class SentryClient {
457453
_options.recorder.recordLostEvent(reason, category);
458454
}
459455

460-
T _eventWithoutBreadcrumbsIfNeeded<T extends SentryEvent>(T event) {
461-
if (_shouldRemoveBreadcrumbs(event)) {
462-
return event.copyWith(breadcrumbs: []) as T;
463-
} else {
464-
return event;
465-
}
466-
}
467-
468-
/// We do this to avoid duplicate breadcrumbs on Android as sentry-android applies the breadcrumbs
469-
/// from the native scope onto every envelope sent through it. This scope will contain the breadcrumbs
470-
/// sent through the scope sync feature. This causes duplicate breadcrumbs.
471-
/// We then remove the breadcrumbs in all cases but if it is handled == false,
472-
/// this is a signal that the app would crash and android would lose the breadcrumbs by the time the app is restarted to read
473-
/// the envelope.
474-
bool _shouldRemoveBreadcrumbs(SentryEvent event) {
475-
if (_options.platformChecker.isWeb) {
476-
return false;
477-
}
478-
479-
final isAndroid = _options.platformChecker.platform.isAndroid;
480-
final enableScopeSync = _options.enableScopeSync;
481-
482-
if (!isAndroid || !enableScopeSync) {
483-
return false;
484-
}
485-
486-
final mechanisms =
487-
(event.exceptions ?? []).map((e) => e.mechanism).whereType<Mechanism>();
488-
final hasNoMechanism = mechanisms.isEmpty;
489-
final hasOnlyHandledMechanism =
490-
mechanisms.every((e) => (e.handled ?? true));
491-
return hasNoMechanism || hasOnlyHandledMechanism;
492-
}
493-
494456
Future<SentryId?> _attachClientReportsAndSend(SentryEnvelope envelope) {
495457
final clientReport = _options.recorder.flush();
496458
envelope.addClientReport(clientReport);

dart/test/sentry_client_test.dart

Lines changed: 0 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,201 +1153,6 @@ void main() {
11531153
});
11541154
});
11551155

1156-
group('Breadcrumbs', () {
1157-
late Fixture fixture;
1158-
1159-
setUp(() {
1160-
fixture = Fixture();
1161-
});
1162-
1163-
test('Clears breadcrumbs on Android for transaction', () async {
1164-
fixture.options.enableScopeSync = true;
1165-
fixture.options.platformChecker =
1166-
MockPlatformChecker(platform: MockPlatform.android());
1167-
1168-
final client = fixture.getSut();
1169-
final transaction = SentryTransaction(
1170-
fixture.tracer,
1171-
breadcrumbs: [
1172-
Breadcrumb(),
1173-
],
1174-
);
1175-
await client.captureTransaction(transaction);
1176-
1177-
final capturedEnvelope = (fixture.transport).envelopes.first;
1178-
final capturedTransaction =
1179-
await transactionFromEnvelope(capturedEnvelope);
1180-
1181-
expect((capturedTransaction['breadcrumbs'] ?? []).isEmpty, true);
1182-
});
1183-
1184-
test('Clears breadcrumbs on Android if mechanism.handled is true for event',
1185-
() async {
1186-
fixture.options.enableScopeSync = true;
1187-
fixture.options.platformChecker =
1188-
MockPlatformChecker(platform: MockPlatform.android());
1189-
1190-
final client = fixture.getSut();
1191-
final event = SentryEvent(exceptions: [
1192-
SentryException(
1193-
type: "type",
1194-
value: "value",
1195-
mechanism: Mechanism(
1196-
type: 'type',
1197-
handled: true,
1198-
),
1199-
)
1200-
], breadcrumbs: [
1201-
Breadcrumb()
1202-
]);
1203-
await client.captureEvent(event);
1204-
1205-
final capturedEnvelope = (fixture.transport).envelopes.first;
1206-
final capturedEvent = await eventFromEnvelope(capturedEnvelope);
1207-
1208-
expect((capturedEvent.breadcrumbs ?? []).isEmpty, true);
1209-
});
1210-
1211-
test('Clears breadcrumbs on Android if mechanism.handled is null for event',
1212-
() async {
1213-
fixture.options.enableScopeSync = true;
1214-
fixture.options.platformChecker =
1215-
MockPlatformChecker(platform: MockPlatform.android());
1216-
1217-
final client = fixture.getSut();
1218-
final event = SentryEvent(exceptions: [
1219-
SentryException(
1220-
type: "type",
1221-
value: "value",
1222-
mechanism: Mechanism(type: 'type'),
1223-
)
1224-
], breadcrumbs: [
1225-
Breadcrumb()
1226-
]);
1227-
await client.captureEvent(event);
1228-
1229-
final capturedEnvelope = (fixture.transport).envelopes.first;
1230-
final capturedEvent = await eventFromEnvelope(capturedEnvelope);
1231-
1232-
expect((capturedEvent.breadcrumbs ?? []).isEmpty, true);
1233-
});
1234-
1235-
test('Clears breadcrumbs on Android if theres no mechanism for event',
1236-
() async {
1237-
fixture.options.enableScopeSync = true;
1238-
fixture.options.platformChecker =
1239-
MockPlatformChecker(platform: MockPlatform.android());
1240-
1241-
final client = fixture.getSut();
1242-
final event = SentryEvent(exceptions: [
1243-
SentryException(
1244-
type: "type",
1245-
value: "value",
1246-
)
1247-
], breadcrumbs: [
1248-
Breadcrumb()
1249-
]);
1250-
await client.captureEvent(event);
1251-
1252-
final capturedEnvelope = (fixture.transport).envelopes.first;
1253-
final capturedEvent = await eventFromEnvelope(capturedEnvelope);
1254-
1255-
expect((capturedEvent.breadcrumbs ?? []).isEmpty, true);
1256-
});
1257-
1258-
test(
1259-
'Does not clear breadcrumbs on Android if mechanism.handled is false for event',
1260-
() async {
1261-
fixture.options.enableScopeSync = true;
1262-
fixture.options.platformChecker =
1263-
MockPlatformChecker(platform: MockPlatform.android());
1264-
1265-
final client = fixture.getSut();
1266-
final event = SentryEvent(exceptions: [
1267-
SentryException(
1268-
type: "type",
1269-
value: "value",
1270-
mechanism: Mechanism(
1271-
type: 'type',
1272-
handled: false,
1273-
),
1274-
)
1275-
], breadcrumbs: [
1276-
Breadcrumb()
1277-
]);
1278-
await client.captureEvent(event);
1279-
1280-
final capturedEnvelope = (fixture.transport).envelopes.first;
1281-
final capturedEvent = await eventFromEnvelope(capturedEnvelope);
1282-
1283-
expect((capturedEvent.breadcrumbs ?? []).isNotEmpty, true);
1284-
});
1285-
1286-
test(
1287-
'Does not clear breadcrumbs on Android if any mechanism.handled is false for event',
1288-
() async {
1289-
fixture.options.enableScopeSync = true;
1290-
fixture.options.platformChecker =
1291-
MockPlatformChecker(platform: MockPlatform.android());
1292-
1293-
final client = fixture.getSut();
1294-
final event = SentryEvent(exceptions: [
1295-
SentryException(
1296-
type: "type",
1297-
value: "value",
1298-
mechanism: Mechanism(
1299-
type: 'type',
1300-
handled: true,
1301-
),
1302-
),
1303-
SentryException(
1304-
type: "type",
1305-
value: "value",
1306-
mechanism: Mechanism(
1307-
type: 'type',
1308-
handled: false,
1309-
),
1310-
)
1311-
], breadcrumbs: [
1312-
Breadcrumb()
1313-
]);
1314-
await client.captureEvent(event);
1315-
1316-
final capturedEnvelope = (fixture.transport).envelopes.first;
1317-
final capturedEvent = await eventFromEnvelope(capturedEnvelope);
1318-
1319-
expect((capturedEvent.breadcrumbs ?? []).isNotEmpty, true);
1320-
});
1321-
1322-
test('web breadcrumbs exist on web Android devices', () async {
1323-
fixture.options.enableScopeSync = true;
1324-
fixture.options.platformChecker = MockPlatformChecker(
1325-
platform: MockPlatform.android(),
1326-
isWebValue: true,
1327-
);
1328-
1329-
final client = fixture.getSut();
1330-
final event = SentryEvent(exceptions: [
1331-
SentryException(
1332-
type: "type",
1333-
value: "value",
1334-
mechanism: Mechanism(
1335-
type: 'type',
1336-
handled: true,
1337-
),
1338-
),
1339-
], breadcrumbs: [
1340-
Breadcrumb(),
1341-
]);
1342-
await client.captureEvent(event);
1343-
1344-
final capturedEnvelope = (fixture.transport).envelopes.first;
1345-
final capturedEvent = await eventFromEnvelope(capturedEnvelope);
1346-
1347-
expect((capturedEvent.breadcrumbs ?? []).isNotEmpty, true);
1348-
});
1349-
});
1350-
13511156
group('ClientReportRecorder', () {
13521157
late Fixture fixture;
13531158

flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import io.sentry.SentryOptions
2121
import io.sentry.android.core.ActivityFramesTracker
2222
import io.sentry.android.core.AppStartState
2323
import io.sentry.android.core.BuildConfig.VERSION_NAME
24+
import io.sentry.android.core.InternalSentrySdk
2425
import io.sentry.android.core.LoadClass
2526
import io.sentry.android.core.SentryAndroid
2627
import io.sentry.android.core.SentryAndroidOptions
@@ -65,6 +66,7 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
6566
"removeExtra" -> removeExtra(call.argument("key"), result)
6667
"setTag" -> setTag(call.argument("key"), call.argument("value"), result)
6768
"removeTag" -> removeTag(call.argument("key"), result)
69+
"loadContexts" -> loadContexts(result)
6870
else -> result.notImplemented()
6971
}
7072
}
@@ -94,18 +96,6 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
9496
// Stub
9597
}
9698

97-
private fun writeEnvelope(envelope: ByteArray): Boolean {
98-
val options = HubAdapter.getInstance().options
99-
if (options.outboxPath.isNullOrEmpty()) {
100-
return false
101-
}
102-
103-
val file = File(options.outboxPath, UUID.randomUUID().toString())
104-
file.writeBytes(envelope)
105-
106-
return true
107-
}
108-
10999
private fun initNativeSdk(call: MethodCall, result: Result) {
110100
if (!this::context.isInitialized) {
111101
result.error("1", "Context is null", null)
@@ -356,20 +346,19 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
356346
result.error("1", "The Sentry Android SDK is disabled", null)
357347
return
358348
}
359-
360-
val args = call.arguments() as List<Any>? ?: listOf<Any>()
349+
val args = call.arguments() as List<Any>? ?: listOf()
361350
if (args.isNotEmpty()) {
362351
val event = args.first() as ByteArray?
363-
364352
if (event != null && event.isNotEmpty()) {
365-
if (!writeEnvelope(event)) {
366-
result.error("2", "SentryOptions or outboxPath are null or empty", null)
353+
val id = InternalSentrySdk.captureEnvelope(event)
354+
if (id != null) {
355+
result.success("")
356+
} else {
357+
result.error("2", "Failed to capture envelope", null)
367358
}
368-
result.success("")
369359
return
370360
}
371361
}
372-
373362
result.error("3", "Envelope is null or empty", null)
374363
}
375364

@@ -454,6 +443,21 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
454443
}
455444
}
456445
}
446+
447+
private fun loadContexts(result: Result) {
448+
val options = HubAdapter.getInstance().options
449+
if (options !is SentryAndroidOptions) {
450+
result.success(null)
451+
return
452+
}
453+
val currentScope = InternalSentrySdk.getCurrentScope()
454+
val serializedScope = InternalSentrySdk.serializeScope(
455+
context,
456+
options,
457+
currentScope
458+
)
459+
result.success(serializedScope)
460+
}
457461
}
458462

459463
// Call the `completion` closure if cast to map value with `key` and type `T` is successful.

flutter/lib/src/integrations/load_contexts_integration.dart

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,16 @@ import 'package:flutter/services.dart';
44
import 'package:sentry/sentry.dart';
55
import '../sentry_flutter_options.dart';
66

7-
/// Load Device's Contexts from the iOS SDK.
7+
/// Load Device's Contexts from the iOS & Android SDKs.
88
///
9-
/// This integration calls the iOS SDK via Message channel to load the
10-
/// Device's contexts before sending the event back to the iOS SDK via
9+
/// This integration calls the iOS & Android SDKs via Message channel to load
10+
/// the Device's contexts before sending the event back to the SDK via
1111
/// Message channel (already enriched with all the information).
1212
///
1313
/// The Device's contexts are:
1414
/// App, Device and OS.
1515
///
16-
/// ps. This integration won't be run on Android because the Device's Contexts
17-
/// is set on Android when the event is sent to the Android SDK via
18-
/// the Message channel.
19-
/// We intend to unify this behaviour in the future.
20-
///
21-
/// This integration is only executed on iOS & MacOS Apps.
16+
/// This integration is only executed on iOS, macOS & Android Apps.
2217
class LoadContextsIntegration extends Integration<SentryFlutterOptions> {
2318
final MethodChannel _channel;
2419

@@ -194,8 +189,8 @@ class _LoadContextsIntegrationEventProcessor implements EventProcessor {
194189
event = event.copyWith(sdk: sdk);
195190
}
196191

197-
// on iOS, captureEnvelope does not call the beforeSend callback,
198-
// hence we need to add these tags here.
192+
// captureEnvelope does not call the beforeSend callback, hence we need to
193+
// add these tags here.
199194
if (event.sdk?.name == 'sentry.dart.flutter') {
200195
final tags = event.tags ?? {};
201196
tags['event.origin'] = 'flutter';

0 commit comments

Comments
 (0)