Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Base the sdk name on the platform (`sentry.dart` for io & flutter, `sentry.dart.browser` in a browser context) #103
- Single changelog and readme for both packages #105
- expect a sdkName based on the test platform #105
- Added Scope and Breadcrumb ring buffer #109

# `package:sentry` changelog

Expand Down
2 changes: 2 additions & 0 deletions dart/lib/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
export 'src/client.dart';
export 'src/protocol.dart';
export 'src/version.dart';
export 'src/scope.dart';
export 'src/sentry_options.dart';
139 changes: 139 additions & 0 deletions dart/lib/src/scope.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import 'dart:collection';

import 'protocol.dart';
import 'sentry_options.dart';

/// Scope data to be sent with the event
class Scope {
/// How important this event is.
SeverityLevel _level;

SeverityLevel get level => _level;

set level(SeverityLevel level) {
_level = level;
}
Comment on lines +9 to +15
Copy link
Contributor Author

@marandaneto marandaneto Oct 14, 2020

Choose a reason for hiding this comment

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

@bruno-garcia maybe we should add getters and setters only for List/Maps so they cannot mutate the whole thing?
https://dart.dev/guides/language/effective-dart/usage#dont-wrap-a-field-in-a-getter-and-setter-unnecessarily

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense to me!


/// The name of the transaction which generated this event,
/// for example, the route name: `"/users/<username>/"`.
String _transaction;

String get transaction => _transaction;

set transaction(String transaction) {
_transaction = transaction;
}

/// Information about the current user.
User _user;

User get user => _user;

set user(User user) {
_user = user;
}

/// Used to deduplicate events by grouping ones with the same fingerprint
/// together.
///
/// Example:
///
/// // A completely custom fingerprint:
/// var custom = ['foo', 'bar', 'baz'];
List<String> _fingerprint;

List<String> get fingerprint =>
_fingerprint != null ? List.unmodifiable(_fingerprint) : null;

set fingerprint(List<String> fingerprint) {
_fingerprint = fingerprint;
}

/// List of breadcrumbs for this scope.
///
/// See also:
/// * https://docs.sentry.io/enriching-error-data/breadcrumbs/?platform=javascript
final Queue<Breadcrumb> _breadcrumbs = Queue();

/// Unmodifiable List of breadcrumbs
List<Breadcrumb> get breadcrumbs => List.unmodifiable(_breadcrumbs);

/// Name/value pairs that events can be searched by.
final Map<String, String> _tags = {};

Map<String, String> get tags => Map.unmodifiable(_tags);

/// Arbitrary name/value pairs attached to the scope.
///
/// Sentry.io docs do not talk about restrictions on the values, other than
/// they must be JSON-serializable.
final Map<String, dynamic> _extra = {};

Map<String, dynamic> get extra => Map.unmodifiable(_extra);

// TODO: EventProcessors, Contexts, BeforeBreadcrumbCallback, Breadcrumb Hint, clone

final SentryOptions _options;

Scope(this._options) : assert(_options != null, 'SentryOptions is required');

/// Adds a breadcrumb to the breadcrumbs queue
void addBreadcrumb(Breadcrumb breadcrumb) {
assert(breadcrumb != null, "Breadcrumb can't be null");

// bail out if maxBreadcrumbs is zero
if (_options.maxBreadcrumbs == 0) {
return;
}

// remove first item if list if full
if (_breadcrumbs.length >= _options.maxBreadcrumbs &&
_breadcrumbs.isNotEmpty) {
_breadcrumbs.removeFirst();
}

_breadcrumbs.add(breadcrumb);
}

/// Clear all the breadcrumbs
void clearBreadcrumbs() {
_breadcrumbs.clear();
}

/// Resets the Scope to its default state
void clear() {
clearBreadcrumbs();
_level = null;
_transaction = null;
_user = null;
_fingerprint = null;
_tags.clear();
_extra.clear();
}

/// Sets a tag to the Scope
void setTag(String key, String value) {
assert(key != null, "Key can't be null");
assert(value != null, "Key can't be null");

_tags[key] = value;
}

/// Removes a tag from the Scope
void removeTag(String key) {
_tags.remove(key);
}

/// Sets an extra to the Scope
void setExtra(String key, dynamic value) {
assert(key != null, "Key can't be null");
assert(value != null, "Value can't be null");

_extra[key] = value;
}

/// Removes an extra from the Scope
void removeExtra(String key) {
_extra.remove(key);
}
}
5 changes: 5 additions & 0 deletions dart/lib/src/sentry_options.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// Sentry SDK options
class SentryOptions {
/// This variable controls the total amount of breadcrumbs that should be captured Default is 100
int maxBreadcrumbs = 100;
}
2 changes: 1 addition & 1 deletion dart/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dependencies:
uuid: ^2.0.0

dev_dependencies:
mockito: ^4.1.2
mockito: ^4.1.1
Copy link
Contributor Author

Choose a reason for hiding this comment

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

because of null-safety, this is not working right thru the IDE, downgrading version made it to work

pedantic: ^1.9.2
test: ^1.15.4
yaml: ^2.2.1
Expand Down
175 changes: 175 additions & 0 deletions dart/test/scope_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import 'package:sentry/sentry.dart';
import 'package:test/test.dart';

void main() {
final fixture = Fixture();

test('sets $SeverityLevel', () {
final sut = fixture.getSut();

sut.level = SeverityLevel.debug;

expect(sut.level, SeverityLevel.debug);
});

test('sets transaction', () {
final sut = fixture.getSut();

sut.transaction = 'test';

expect(sut.transaction, 'test');
});

test('sets $User', () {
final sut = fixture.getSut();

final user = User(id: 'test');
sut.user = user;

expect(sut.user, user);
});

test('sets fingerprint', () {
final sut = fixture.getSut();

final fingerprints = ['test'];
sut.fingerprint = fingerprints;

expect(sut.fingerprint, fingerprints);
});

test('adds $Breadcrumb', () {
final sut = fixture.getSut();

final breadcrumb = Breadcrumb('test log', DateTime.utc(2019));
sut.addBreadcrumb(breadcrumb);

expect(sut.breadcrumbs.last, breadcrumb);
});

test('respects max $Breadcrumb', () {
final maxBreadcrumbs = 2;
final sut = fixture.getSut(maxBreadcrumbs: maxBreadcrumbs);

final breadcrumb1 = Breadcrumb('test log', DateTime.utc(2019));
final breadcrumb2 = Breadcrumb('test log', DateTime.utc(2019));
final breadcrumb3 = Breadcrumb('test log', DateTime.utc(2019));
sut.addBreadcrumb(breadcrumb1);
sut.addBreadcrumb(breadcrumb2);
sut.addBreadcrumb(breadcrumb3);

expect(sut.breadcrumbs.length, maxBreadcrumbs);
});

test('rotates $Breadcrumb', () {
final sut = fixture.getSut(maxBreadcrumbs: 2);

final breadcrumb1 = Breadcrumb('test log', DateTime.utc(2019));
final breadcrumb2 = Breadcrumb('test log', DateTime.utc(2019));
final breadcrumb3 = Breadcrumb('test log', DateTime.utc(2019));
sut.addBreadcrumb(breadcrumb1);
sut.addBreadcrumb(breadcrumb2);
sut.addBreadcrumb(breadcrumb3);

expect(sut.breadcrumbs.first, breadcrumb2);

expect(sut.breadcrumbs.last, breadcrumb3);
});

test('empty $Breadcrumb list', () {
final maxBreadcrumbs = 0;
final sut = fixture.getSut(maxBreadcrumbs: maxBreadcrumbs);

final breadcrumb1 = Breadcrumb('test log', DateTime.utc(2019));
sut.addBreadcrumb(breadcrumb1);

expect(sut.breadcrumbs.length, maxBreadcrumbs);
});

test('clears $Breadcrumb list', () {
final sut = fixture.getSut();

final breadcrumb1 = Breadcrumb('test log', DateTime.utc(2019));
sut.addBreadcrumb(breadcrumb1);
sut.clear();

expect(sut.breadcrumbs.length, 0);
});

test('sets tag', () {
final sut = fixture.getSut();

sut.setTag('test', 'test');

expect(sut.tags['test'], 'test');
});

test('removes tag', () {
final sut = fixture.getSut();

sut.setTag('test', 'test');
sut.removeTag('test');

expect(sut.tags['test'], null);
});

test('sets extra', () {
final sut = fixture.getSut();

sut.setExtra('test', 'test');

expect(sut.extra['test'], 'test');
});

test('removes extra', () {
final sut = fixture.getSut();

sut.setExtra('test', 'test');
sut.removeExtra('test');

expect(sut.extra['test'], null);
});

test('clears $Scope', () {
final sut = fixture.getSut();

final breadcrumb1 = Breadcrumb('test log', DateTime.utc(2019));
sut.addBreadcrumb(breadcrumb1);

sut.level = SeverityLevel.debug;
sut.transaction = 'test';

final user = User(id: 'test');
sut.user = user;

final fingerprints = ['test'];
sut.fingerprint = fingerprints;

sut.setTag('test', 'test');
sut.setExtra('test', 'test');

sut.clear();

expect(sut.breadcrumbs.length, 0);

expect(sut.level, null);

expect(sut.transaction, null);

expect(sut.user, null);

expect(sut.fingerprint, null);

expect(sut.tags.length, 0);

expect(sut.extra.length, 0);
});
}

class Fixture {
Scope getSut({int maxBreadcrumbs = 100}) {
final options = SentryOptions();
options.maxBreadcrumbs = maxBreadcrumbs;
return Scope(options);
}
}