Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
- Bump Cocoa SDK from v8.29.0 to v8.30.0 ([#2132](https://github.com/getsentry/sentry-dart/pull/2132))
- [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8300)
- [diff](https://github.com/getsentry/sentry-cocoa/compare/8.29.0...8.30.0)
- Bump Android SDK from v7.10.0 to v7.11.0 ([#2144](https://github.com/getsentry/sentry-dart/pull/2144))
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#7110)
- [diff](https://github.com/getsentry/sentry-java/compare/7.10.0...7.11.0)

## 8.3.0

Expand Down
17 changes: 16 additions & 1 deletion dart/lib/src/sentry_envelope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@ import 'sentry_user_feedback.dart';

/// Class representation of `Envelope` file.
class SentryEnvelope {
SentryEnvelope(this.header, this.items);
SentryEnvelope(this.header, this.items,
{this.containsUnhandledException = false});

/// Header describing envelope content.
final SentryEnvelopeHeader header;

/// All items contained in the envelope.
final List<SentryEnvelopeItem> items;

/// Whether the envelope contains an unhandled exception.
/// This is used to determine if the native SDK should start a new session.
final bool containsUnhandledException;

/// Create a [SentryEnvelope] containing one [SentryEnvelopeItem] which holds the [SentryEvent] data.
factory SentryEnvelope.fromEvent(
SentryEvent event,
Expand All @@ -29,6 +34,15 @@ class SentryEnvelope {
SentryTraceContextHeader? traceContext,
List<SentryAttachment>? attachments,
}) {
bool containsUnhandledException = false;

if (event.exceptions != null && event.exceptions!.isNotEmpty) {
// Check all exceptions for any unhandled ones
containsUnhandledException = event.exceptions!.any((exception) {
return exception.mechanism?.handled == false;
});
}

return SentryEnvelope(
SentryEnvelopeHeader(
event.eventId,
Expand All @@ -41,6 +55,7 @@ class SentryEnvelope {
if (attachments != null)
...attachments.map((e) => SentryEnvelopeItem.fromAttachment(e))
],
containsUnhandledException: containsUnhandledException,
);
}

Expand Down
3 changes: 3 additions & 0 deletions dart/test/mocks/mock_envelope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ class MockEnvelope implements SentryEnvelope {

@override
List<SentryEnvelopeItem> items = [];

@override
bool get containsUnhandledException => false;
}
2 changes: 1 addition & 1 deletion flutter/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ android {
}

dependencies {
api 'io.sentry:sentry-android:7.10.0'
api 'io.sentry:sentry-android:7.11.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

// Required -- JUnit 4 framework
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,9 @@ class SentryFlutterPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
val args = call.arguments() as List<Any>? ?: listOf()
if (args.isNotEmpty()) {
val event = args.first() as ByteArray?
if (event != null && event.isNotEmpty()) {
val id = InternalSentrySdk.captureEnvelope(event)
val containsUnhandledException = args[1] as Boolean
if (event != null && event.isNotEmpty() && containsUnhandledException != null) {
val id = InternalSentrySdk.captureEnvelope(event, containsUnhandledException)
if (id != null) {
result.success("")
} else {
Expand Down
3 changes: 2 additions & 1 deletion flutter/lib/src/file_system_transport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class FileSystemTransport implements Transport {
await envelope.envelopeStream(_options).forEach(envelopeData.addAll);
try {
// TODO avoid copy
await _native.captureEnvelope(Uint8List.fromList(envelopeData));
await _native.captureEnvelope(Uint8List.fromList(envelopeData),
envelope.containsUnhandledException);
} catch (exception, stackTrace) {
_options.logger(
SentryLevel.error,
Expand Down
3 changes: 2 additions & 1 deletion flutter/lib/src/native/sentry_native_binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ abstract class SentryNativeBinding {

Future<NativeAppStart?> fetchNativeAppStart();

Future<void> captureEnvelope(Uint8List envelopeData);
Future<void> captureEnvelope(
Uint8List envelopeData, bool containsUnhandledException);

Future<void> beginNativeFrames();

Expand Down
7 changes: 5 additions & 2 deletions flutter/lib/src/native/sentry_native_channel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,11 @@ class SentryNativeChannel
}

@override
Future<void> captureEnvelope(Uint8List envelopeData) =>
_channel.invokeMethod('captureEnvelope', [envelopeData]);
Future<void> captureEnvelope(
Uint8List envelopeData, bool containsUnhandledException) {
return _channel.invokeMethod(
'captureEnvelope', [envelopeData, containsUnhandledException]);
}

@override
Future<Map<String, dynamic>?> loadContexts() =>
Expand Down
62 changes: 58 additions & 4 deletions flutter/test/file_system_transport_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
library flutter_test;

import 'dart:convert';

// backcompatibility for Flutter < 3.3
// ignore: unnecessary_import
import 'dart:typed_data';
Expand Down Expand Up @@ -39,7 +40,7 @@ void main() {
});

test('$FileSystemTransport returns emptyId if channel throws', () async {
when(fixture.binding.captureEnvelope(any)).thenThrow(Exception());
when(fixture.binding.captureEnvelope(any, false)).thenThrow(Exception());

final transport = fixture.getSut();
final event = SentryEvent();
Expand All @@ -56,6 +57,58 @@ void main() {
expect(SentryId.empty(), sentryId);
});

test(
'sets unhandled exception flag in captureEnvelope to true for unhandled exception',
() async {
final transport = fixture.getSut();

final unhandledException = SentryException(
mechanism: Mechanism(type: 'UnhandledException', handled: false),
threadId: 99,
type: 'Exception',
value: 'Unhandled exception',
);
final event = SentryEvent(exceptions: [unhandledException]);
final sdkVersion =
SdkVersion(name: 'fixture-sdkName', version: 'fixture-sdkVersion');
final envelope = SentryEnvelope.fromEvent(
event,
sdkVersion,
dsn: fixture.options.dsn,
);

await transport.send(envelope);

verify(fixture.binding.captureEnvelope(captureAny, true)).captured.single
as Uint8List;
});

test(
'sets unhandled exception flag in captureEnvelope to false for handled exception',
() async {
final transport = fixture.getSut();

final unhandledException = SentryException(
mechanism: Mechanism(type: 'UnhandledException', handled: true),
threadId: 99,
type: 'Exception',
value: 'Unhandled exception',
);
final event = SentryEvent(exceptions: [unhandledException]);
final sdkVersion =
SdkVersion(name: 'fixture-sdkName', version: 'fixture-sdkVersion');
final envelope = SentryEnvelope.fromEvent(
event,
sdkVersion,
dsn: fixture.options.dsn,
);

await transport.send(envelope);

verify(fixture.binding.captureEnvelope(captureAny, false)).captured.single
as Uint8List;
});

test('$FileSystemTransport asserts the event', () async {
final transport = fixture.getSut();

Expand All @@ -70,9 +123,10 @@ void main() {
);
await transport.send(envelope);

final envelopeData = verify(fixture.binding.captureEnvelope(captureAny))
.captured
.single as Uint8List;
final envelopeData =
verify(fixture.binding.captureEnvelope(captureAny, false))
Copy link
Contributor

@krystofwoldrich krystofwoldrich Jul 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

M: @buenaflor Can you add one new test, which check that the containsUnhandledException=true is read from the event and passed to the native call?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added additional test cases

.captured
.single as Uint8List;
final envelopeString = utf8.decode(envelopeData);
final lines = envelopeString.split('\n');
final envelopeHeader = lines.first;
Expand Down
10 changes: 8 additions & 2 deletions flutter/test/mocks.mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1116,11 +1116,17 @@ class MockSentryNativeBinding extends _i1.Mock
) as _i8.Future<_i15.NativeAppStart?>);

@override
_i8.Future<void> captureEnvelope(_i16.Uint8List? envelopeData) =>
_i8.Future<void> captureEnvelope(
_i16.Uint8List? envelopeData,
bool? containsUnhandledException,
) =>
(super.noSuchMethod(
Invocation.method(
#captureEnvelope,
[envelopeData],
[
envelopeData,
containsUnhandledException,
],
),
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
Expand Down
2 changes: 1 addition & 1 deletion flutter/test/sentry_native_channel_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ void main() {
(invocation) async =>
{captured = invocation.positionalArguments[1][0] as Uint8List});

await sut.captureEnvelope(data);
await sut.captureEnvelope(data, false);

expect(captured, data);
});
Expand Down