Skip to content

v9: Parent-child relationship for the PlatformExceptions and Cause #2803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
Apr 10, 2025
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
bd65dec
add source to exception cause, populate source from sentry exception …
denrase Mar 18, 2025
8488663
Merge branch 'main' into enha/rel-platfomr-cause
denrase Mar 18, 2025
b950043
add source for osError
denrase Mar 18, 2025
3486958
revert sentry exception cause factory changes
denrase Mar 18, 2025
fd73eaf
extract soruce from android platform exceptions
denrase Mar 18, 2025
07087aa
add ExceptionGroupEventProcessor
denrase Mar 18, 2025
2e189f2
format
denrase Mar 18, 2025
27ab973
reverse order according to rfc
denrase Mar 19, 2025
c888e40
iterate seperatley
denrase Mar 19, 2025
d317c84
update test
denrase Mar 19, 2025
9a23c4a
introduce parent/child relationship in exceptions, flatten exceptions…
denrase Mar 19, 2025
83081be
Merge branch 'main' into enha/rel-platfomr-cause
denrase Mar 27, 2025
576672c
mark methods as internal
denrase Mar 27, 2025
28a7e06
add cl entry
denrase Mar 27, 2025
d74c081
fix integration test
denrase Mar 27, 2025
4f6a74f
fix cl
denrase Mar 27, 2025
5e0236b
don’t try grouping if there are no children
denrase Mar 27, 2025
31c1a24
sentry client builds correct hierarchy for exception causes
denrase Mar 27, 2025
277bc21
reumove unused test
denrase Mar 27, 2025
a57fa2b
attach missing thread to root exception
denrase Mar 27, 2025
a3e6104
Merge branch 'main' into enha/rel-platfomr-cause
denrase Apr 2, 2025
23d5f24
move flatten to event processor
denrase Apr 2, 2025
972a9b7
Merge branch 'main' into enha/rel-platfomr-cause
denrase Apr 7, 2025
6fdbf5b
format
denrase Apr 7, 2025
4adb170
Merge branch 'main' into enha/rel-platfomr-cause
denrase Apr 7, 2025
a19c9aa
handle copyWith usage
denrase Apr 8, 2025
3a6af68
update exception grouping
denrase Apr 9, 2025
e26f4d4
Merge branch 'main' into enha/rel-platfomr-cause
denrase Apr 9, 2025
cd9e4ae
Update dart/lib/src/sentry_exception_factory.dart
denrase Apr 10, 2025
80e7f9e
Merge branch 'main' into enha/rel-platfomr-cause
buenaflor Apr 10, 2025
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Unreleased

### Behavioral changes

- Parent-child relationship for the PlatformExceptions and Cause ([#2803](https://github.com/getsentry/sentry-dart/pull/2803))
- Improves and changes exception grouping

## 9.0.0-alpha.2

### Features
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import '../../event_processor.dart';
import '../../protocol.dart';
import '../../hint.dart';

/// Group exceptions into a flat list with references to hierarchy.
class ExceptionGroupEventProcessor implements EventProcessor {
@override
SentryEvent? apply(SentryEvent event, Hint hint) {
final sentryExceptions = event.exceptions ?? [];
if (sentryExceptions.isEmpty) {
return event;
}
final firstException = sentryExceptions.first;

if (sentryExceptions.length > 1 || firstException.exceptions == null) {
// If already a list or no child exceptions, no grouping possible/needed.
return event;
} else {
final grouped = firstException.flatten().reversed.toList(growable: false);
return event.copyWith(exceptions: grouped);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,24 @@
SocketException exception,
SentryEvent event,
) {
final address = exception.address;
final osError = exception.osError;
SentryException? osException;
List<SentryException>? exceptions;
if (osError != null) {
// OSError is the underlying error
// https://api.dart.dev/stable/dart-io/SocketException/osError.html
// https://api.dart.dev/stable/dart-io/OSError-class.html
osException = _sentryExceptionFromOsError(osError);
osException.exceptions = event.exceptions;
exceptions = [osException];
} else {
exceptions = event.exceptions;

Check warning on line 59 in dart/lib/src/event_processor/exception/io_exception_event_processor.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/event_processor/exception/io_exception_event_processor.dart#L59

Added line #L59 was not covered by tests
}

final address = exception.address;
if (address == null) {
return event.copyWith(
exceptions: [
// OSError is the underlying error
// https://api.dart.dev/stable/dart-io/SocketException/osError.html
// https://api.dart.dev/stable/dart-io/OSError-class.html
if (osError != null) _sentryExceptionfromOsError(osError),
...?event.exceptions,
],
exceptions: exceptions,
);
}
SentryRequest? request;
Expand All @@ -76,13 +83,7 @@

return event.copyWith(
request: event.request ?? request,
exceptions: [
// OSError is the underlying error
// https://api.dart.dev/stable/dart-io/SocketException/osError.html
// https://api.dart.dev/stable/dart-io/OSError-class.html
if (osError != null) _sentryExceptionfromOsError(osError),
...?event.exceptions,
],
exceptions: exceptions,
);
}

Expand All @@ -92,19 +93,27 @@
SentryEvent event,
) {
final osError = exception.osError;

SentryException? osException;
List<SentryException>? exceptions;
if (osError != null) {
// OSError is the underlying error
// https://api.dart.dev/stable/dart-io/SocketException/osError.html
// https://api.dart.dev/stable/dart-io/OSError-class.html
osException = _sentryExceptionFromOsError(osError);
osException.exceptions = event.exceptions;
exceptions = [osException];
} else {
exceptions = event.exceptions;

Check warning on line 107 in dart/lib/src/event_processor/exception/io_exception_event_processor.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/event_processor/exception/io_exception_event_processor.dart#L107

Added line #L107 was not covered by tests
}

return event.copyWith(
exceptions: [
// OSError is the underlying error
// https://api.dart.dev/stable/dart-io/FileSystemException/osError.html
// https://api.dart.dev/stable/dart-io/OSError-class.html
if (osError != null) _sentryExceptionfromOsError(osError),
...?event.exceptions,
],
exceptions: exceptions,
);
}
}

SentryException _sentryExceptionfromOsError(OSError osError) {
SentryException _sentryExceptionFromOsError(OSError osError) {
return SentryException(
type: osError.runtimeType.toString(),
value: osError.toString(),
Expand All @@ -115,6 +124,7 @@
meta: {
'errno': {'number': osError.errorCode},
},
source: 'osError',
),
);
}
3 changes: 2 additions & 1 deletion dart/lib/src/exception_cause.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/// Holds inner exception and stackTrace combinations contained in other exceptions
class ExceptionCause {
ExceptionCause(this.exception, this.stackTrace);
ExceptionCause(this.exception, this.stackTrace, {this.source});

dynamic exception;
dynamic stackTrace;
String? source;
}
52 changes: 50 additions & 2 deletions dart/lib/src/protocol/sentry_exception.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import '../protocol.dart';
import 'access_aware_map.dart';

/// The Exception Interface specifies an exception or error that occurred in a program.
@immutable
class SentryException {
/// Required. The type of exception
final String? type;
Expand All @@ -29,7 +28,9 @@ class SentryException {
@internal
final Map<String, dynamic>? unknown;

const SentryException({
List<SentryException>? _exceptions;

SentryException({
required this.type,
required this.value,
this.module,
Expand Down Expand Up @@ -92,4 +93,51 @@ class SentryException {
throwable: throwable ?? this.throwable,
unknown: unknown,
);

@internal
List<SentryException>? get exceptions =>
_exceptions != null ? List.unmodifiable(_exceptions!) : null;

@internal
set exceptions(List<SentryException>? value) {
_exceptions = value;
}

@internal
void addException(SentryException exception) {
_exceptions ??= [];
_exceptions!.add(exception);
}

@internal
List<SentryException> flatten({int? parentId, int id = 0}) {
final exceptions = this.exceptions ?? [];

var mechanism = this.mechanism ?? Mechanism(type: "generic");
mechanism = mechanism.copyWith(
type: id > 0 ? "chained" : null,
parentId: parentId,
exceptionId: id,
isExceptionGroup: exceptions.length > 1 ? true : null,
);

final exception = copyWith(
mechanism: mechanism,
);

var all = <SentryException>[];
all.add(exception);

if (exceptions.isNotEmpty) {
final parentId = id;
for (var exception in exceptions) {
id++;
final flattenedExceptions =
exception.flatten(parentId: parentId, id: id);
id = flattenedExceptions.lastOrNull?.mechanism?.exceptionId ?? id;
all.addAll(flattenedExceptions);
}
}
return all.toList(growable: false);
}
}
6 changes: 4 additions & 2 deletions dart/lib/src/recursive_exception_cause_extractor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ class RecursiveExceptionCauseExtractor {
final circularityDetector = <dynamic>{};

var currentException = exception;
ExceptionCause? currentExceptionCause =
ExceptionCause(exception, stackTrace);
ExceptionCause? currentExceptionCause = ExceptionCause(
exception,
stackTrace,
);

while (currentException != null &&
currentExceptionCause != null &&
Expand Down
4 changes: 4 additions & 0 deletions dart/lib/src/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'environment/environment_variables.dart';
import 'event_processor/deduplication_event_processor.dart';
import 'event_processor/enricher/enricher_event_processor.dart';
import 'event_processor/exception/exception_event_processor.dart';
import 'event_processor/exception/exception_group_event_processor.dart';
import 'hint.dart';
import 'hub.dart';
import 'hub_adapter.dart';
Expand Down Expand Up @@ -110,6 +111,9 @@ class Sentry {
options.addEventProcessor(DeduplicationEventProcessor(options));

options.prependExceptionTypeIdentifier(DartExceptionTypeIdentifier());

// Added last to ensure all error events have correct parent/child relationships
options.addEventProcessor(ExceptionGroupEventProcessor());
}

/// This method reads available environment variables and uses them
Expand Down
30 changes: 23 additions & 7 deletions dart/lib/src/sentry_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -237,18 +237,27 @@ class SentryClient {
final isolateId = isolateName?.hashCode;

if (event.throwableMechanism != null) {
final extractedExceptions = _exceptionFactory.extractor
final extractedExceptionCauses = _exceptionFactory.extractor
.flatten(event.throwableMechanism, stackTrace);

final sentryExceptions = <SentryException>[];
SentryException? rootException;
SentryException? currentException;
final sentryThreads = <SentryThread>[];

for (final extractedException in extractedExceptions) {
for (final extractedExceptionCause in extractedExceptionCauses) {
var sentryException = _exceptionFactory.getSentryException(
extractedException.exception,
stackTrace: extractedException.stackTrace,
extractedExceptionCause.exception,
stackTrace: extractedExceptionCause.stackTrace,
removeSentryFrames: hint.get(TypeCheckHint.currentStackTrace),
);
if (extractedExceptionCause.source != null) {
var mechanism =
sentryException.mechanism ?? Mechanism(type: "generic");
mechanism = mechanism.copyWith(
source: extractedExceptionCause.source,
);
sentryException = sentryException.copyWith(mechanism: mechanism);
}

SentryThread? sentryThread;

Expand All @@ -264,14 +273,21 @@ class SentryClient {
);
}

sentryExceptions.add(sentryException);
rootException ??= sentryException;
currentException?.addException(sentryException);
currentException = sentryException;

if (sentryThread != null) {
sentryThreads.add(sentryThread);
}
}

final exceptions = [...?event.exceptions];
if (rootException != null) {
exceptions.add(rootException);
}
return event.copyWith(
exceptions: [...?event.exceptions, ...sentryExceptions],
exceptions: exceptions,
threads: [
...?event.threads,
...sentryThreads,
Expand Down
1 change: 1 addition & 0 deletions dart/lib/src/sentry_exception_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class SentryExceptionFactory {
}) {
var throwable = exception;
Mechanism? mechanism;

bool? snapshot;
if (exception is ThrowableMechanism) {
throwable = exception.throwable;
Expand Down
Loading
Loading