Skip to content
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

Exception aggregate mechanism #1866

Merged
merged 12 commits into from
Apr 18, 2024
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
- Now the device context from Android is available in `BeforeSendCallback`
- Set ip_address to {{auto}} by default, even if sendDefaultPII is disabled ([#1665](https://github.com/getsentry/sentry-dart/pull/1665))
- Instead use the "Prevent Storing of IP Addresses" option in the "Security & Privacy" project settings on sentry.io

### Features

- Add support for exception aggregates ([#1866](https://github.com/getsentry/sentry-dart/pull/1866))

Check failure on line 23 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / danger / danger

The changelog entry seems to be part of an already released section `## 8.0.0`. Consider moving the entry to the `## Unreleased` section, please.

### Fixes

Expand Down
52 changes: 52 additions & 0 deletions dart/lib/src/protocol/mechanism.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,38 @@ class Mechanism {
/// This may be because they are created at a central place (like a crash handler), and are all called the same: Error, Segfault etc. When the flag is set, Sentry will then try to use other information (top in-app frame function) rather than exception type and value in the UI for the primary event display. This flag should be set for all "segfaults" for instance as every single error group would look very similar otherwise.
final bool? synthetic;

/// An optional boolean value, set `true` when the exception is the exception
/// group type specific to the platform or language.
/// The default is false when omitted.
/// For example, exceptions of type (PlatformException)[https://api.flutter.dev/flutter/services/PlatformException-class.html]
ueman marked this conversation as resolved.
Show resolved Hide resolved
/// have set it to `true`, others are set to `false`.
final bool? isExceptionGroup;

/// An optional string value describing the source of the exception.
///
/// The SDK should populate this with the name of the property or attribute of
/// the parent exception that this exception was acquired from.
/// In the case of an array, it should include the zero-based array index as
/// well.
final String? source;

/// An optional numeric value providing an ID for the exception relative to
/// this specific event.
///
/// The SDK should assign simple incrementing integers to each exception in
/// the tree, starting with 0 for the root of the tree.
/// In other words, when flattened into the list provided in the exception
/// values on the event, the last exception in the list should have ID 0,
/// the previous one should have ID 1, the next previous should have ID 2, etc.
final int? exceptionId;

/// An optional numeric value pointing at the [exceptionId] that is the parent
/// of this exception.
///
/// The SDK should assign this to all exceptions except the root exception
/// (the last to be listed in the exception values).
final int? parentId;

Mechanism({
required this.type,
this.description,
Expand All @@ -52,6 +84,10 @@ class Mechanism {
this.synthetic,
Map<String, dynamic>? meta,
Map<String, dynamic>? data,
this.isExceptionGroup,
this.source,
this.exceptionId,
this.parentId,
}) : _meta = meta != null ? Map.from(meta) : null,
_data = data != null ? Map.from(data) : null;

Expand All @@ -63,6 +99,10 @@ class Mechanism {
Map<String, dynamic>? meta,
Map<String, dynamic>? data,
bool? synthetic,
bool? isExceptionGroup,
String? source,
int? exceptionId,
int? parentId,
}) =>
Mechanism(
type: type ?? this.type,
Expand All @@ -72,6 +112,10 @@ class Mechanism {
meta: meta ?? this.meta,
data: data ?? this.data,
synthetic: synthetic ?? this.synthetic,
isExceptionGroup: isExceptionGroup ?? this.isExceptionGroup,
source: source ?? this.source,
exceptionId: exceptionId ?? this.exceptionId,
parentId: parentId ?? this.parentId,
);

/// Deserializes a [Mechanism] from JSON [Map].
Expand All @@ -94,6 +138,10 @@ class Mechanism {
meta: meta,
data: data,
synthetic: json['synthetic'],
isExceptionGroup: json['is_exception_group'],
source: json['source'],
exceptionId: json['exception_id'],
parentId: json['parent_id'],
);
}

Expand All @@ -107,6 +155,10 @@ class Mechanism {
if (_meta?.isNotEmpty ?? false) 'meta': _meta,
if (_data?.isNotEmpty ?? false) 'data': _data,
if (synthetic != null) 'synthetic': synthetic,
if (isExceptionGroup != null) 'is_exception_group': isExceptionGroup,
if (source != null) 'source': source,
if (exceptionId != null) 'exception_id': exceptionId,
if (parentId != null) 'parent_id': parentId,
};
}
}
17 changes: 17 additions & 0 deletions dart/test/protocol/mechanism_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ void main() {
synthetic: true,
meta: {'key': 'value'},
data: {'keyb': 'valueb'},
isExceptionGroup: false,
exceptionId: 0,
parentId: 0,
source: 'source',
);

final mechanismJson = <String, dynamic>{
Expand All @@ -21,6 +25,10 @@ void main() {
'meta': {'key': 'value'},
'data': {'keyb': 'valueb'},
'synthetic': true,
'is_exception_group': false,
'source': 'source',
'exception_id': 0,
'parent_id': 0,
};

group('json', () {
Expand Down Expand Up @@ -51,6 +59,7 @@ void main() {

expect(data.toJson(), copy.toJson());
});

test('copyWith takes new values', () {
final data = mechanism;

Expand All @@ -62,6 +71,10 @@ void main() {
synthetic: false,
meta: {'key1': 'value1'},
data: {'keyb1': 'valueb1'},
exceptionId: 1,
parentId: 1,
isExceptionGroup: false,
source: 'foo',
);

expect('type1', copy.type);
Expand All @@ -71,6 +84,10 @@ void main() {
expect(false, copy.synthetic);
expect({'key1': 'value1'}, copy.meta);
expect({'keyb1': 'valueb1'}, copy.data);
expect(1, copy.exceptionId);
expect(1, copy.parentId);
expect(false, copy.isExceptionGroup);
expect('foo', copy.source);
});
});
}
Loading