From 43109b3db5bca151348383751352f078b67c2278 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Tue, 13 Oct 2020 13:56:11 +0200 Subject: [PATCH 1/4] feat: add Scope class --- dart/lib/sentry.dart | 2 + dart/lib/src/scope.dart | 80 +++++++++++ dart/lib/src/sentry_options.dart | 5 + dart/pubspec.yaml | 2 +- dart/test/scope_test.dart | 238 +++++++++++++++++++++++++++++++ 5 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 dart/lib/src/scope.dart create mode 100644 dart/lib/src/sentry_options.dart create mode 100644 dart/test/scope_test.dart diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index dcaa278d42..d81860c038 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -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'; diff --git a/dart/lib/src/scope.dart b/dart/lib/src/scope.dart new file mode 100644 index 0000000000..b7a9e7a7ee --- /dev/null +++ b/dart/lib/src/scope.dart @@ -0,0 +1,80 @@ +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; + + /// The name of the transaction which generated this event, + /// for example, the route name: `"/users//"`. + String transaction; + + /// Information about the current 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 fingerprint; + + /// List of breadcrumbs for this scope. + /// + /// See also: + /// * https://docs.sentry.io/enriching-error-data/breadcrumbs/?platform=javascript + final Queue _breadcrumbs = Queue(); + + /// Unmodifiable List of breadcrumbs + List get breadcrumbs => List.unmodifiable(_breadcrumbs); + + /// Name/value pairs that events can be searched by. + final Map 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 extra = {}; + + // TODO: eventProcessors, Contexts, BeforeBreadcrumbCallback, Breadcrumb hint, clome + + 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"); + + if (_breadcrumbs.length >= _options.maxBreadcrumbs && + _breadcrumbs.isNotEmpty) { + _breadcrumbs.removeFirst(); + } + if (_options.maxBreadcrumbs == 0) { + return; + } + _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(); + } +} diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart new file mode 100644 index 0000000000..cde1d36fe5 --- /dev/null +++ b/dart/lib/src/sentry_options.dart @@ -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; +} diff --git a/dart/pubspec.yaml b/dart/pubspec.yaml index e688917675..34e82cf850 100644 --- a/dart/pubspec.yaml +++ b/dart/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: uuid: ^2.0.0 dev_dependencies: - mockito: ^4.1.2 + mockito: ^4.1.1 pedantic: ^1.9.2 test: ^1.15.4 yaml: ^2.2.1 diff --git a/dart/test/scope_test.dart b/dart/test/scope_test.dart new file mode 100644 index 0000000000..c80f210351 --- /dev/null +++ b/dart/test/scope_test.dart @@ -0,0 +1,238 @@ +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.tags['test'] = 'test'; + + expect( + sut.tags['test'], + 'test', + ); + }); + + test('removes tag', () { + final sut = fixture.getSut(); + + sut.tags['test'] = 'test'; + sut.tags.remove('test'); + + expect( + sut.tags['test'], + null, + ); + }); + + test('sets extra', () { + final sut = fixture.getSut(); + + sut.extra['test'] = 'test'; + + expect( + sut.extra['test'], + 'test', + ); + }); + + test('removes extra', () { + final sut = fixture.getSut(); + + sut.extra['test'] = 'test'; + sut.extra.remove('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.tags['test'] = 'test'; + sut.extra['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); + } +} From 03405a04ef256cb61de54ed9aa359811ef989f57 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Tue, 13 Oct 2020 14:34:10 +0200 Subject: [PATCH 2/4] add comments --- dart/lib/src/scope.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/dart/lib/src/scope.dart b/dart/lib/src/scope.dart index b7a9e7a7ee..f0b3956ce7 100644 --- a/dart/lib/src/scope.dart +++ b/dart/lib/src/scope.dart @@ -42,7 +42,7 @@ class Scope { /// they must be JSON-serializable. final Map extra = {}; - // TODO: eventProcessors, Contexts, BeforeBreadcrumbCallback, Breadcrumb hint, clome + // TODO: eventProcessors, Contexts, BeforeBreadcrumbCallback, Breadcrumb hint, clone final SentryOptions _options; @@ -52,13 +52,17 @@ class Scope { 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(); } - if (_options.maxBreadcrumbs == 0) { - return; - } + _breadcrumbs.add(breadcrumb); } From b971db2f2750e98d231f72ed2d884f2441f30194 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 14 Oct 2020 13:14:38 +0200 Subject: [PATCH 3/4] added getters and setters --- dart/lib/src/scope.dart | 81 +++++++++++++++++++---- dart/test/scope_test.dart | 133 ++++++++++---------------------------- 2 files changed, 103 insertions(+), 111 deletions(-) diff --git a/dart/lib/src/scope.dart b/dart/lib/src/scope.dart index f0b3956ce7..63e1c85ec8 100644 --- a/dart/lib/src/scope.dart +++ b/dart/lib/src/scope.dart @@ -6,14 +6,32 @@ import 'sentry_options.dart'; /// Scope data to be sent with the event class Scope { /// How important this event is. - SeverityLevel level; + SeverityLevel _level; + + SeverityLevel get level => _level; + + set level(SeverityLevel level) { + _level = level; + } /// The name of the transaction which generated this event, /// for example, the route name: `"/users//"`. - String transaction; + String _transaction; + + String get transaction => _transaction; + + set transaction(String transaction) { + _transaction = transaction; + } /// Information about the current user. - User 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. @@ -22,7 +40,14 @@ class Scope { /// /// // A completely custom fingerprint: /// var custom = ['foo', 'bar', 'baz']; - List fingerprint; + List _fingerprint; + + List get fingerprint => + _fingerprint != null ? List.unmodifiable(_fingerprint) : null; + + set fingerprint(List fingerprint) { + _fingerprint = fingerprint; + } /// List of breadcrumbs for this scope. /// @@ -34,15 +59,19 @@ class Scope { List get breadcrumbs => List.unmodifiable(_breadcrumbs); /// Name/value pairs that events can be searched by. - final Map tags = {}; + final Map _tags = {}; + + Map 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 extra = {}; + final Map _extra = {}; + + Map get extra => Map.unmodifiable(_extra); - // TODO: eventProcessors, Contexts, BeforeBreadcrumbCallback, Breadcrumb hint, clone + // TODO: EventProcessors, Contexts, BeforeBreadcrumbCallback, Breadcrumb Hint, clone final SentryOptions _options; @@ -74,11 +103,37 @@ class Scope { /// Resets the Scope to its default state void clear() { clearBreadcrumbs(); - level = null; - transaction = null; - user = null; - fingerprint = null; - tags.clear(); - extra.clear(); + _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); } } diff --git a/dart/test/scope_test.dart b/dart/test/scope_test.dart index c80f210351..2139b3d772 100644 --- a/dart/test/scope_test.dart +++ b/dart/test/scope_test.dart @@ -9,10 +9,7 @@ void main() { sut.level = SeverityLevel.debug; - expect( - sut.level, - SeverityLevel.debug, - ); + expect(sut.level, SeverityLevel.debug); }); test('sets transaction', () { @@ -20,10 +17,7 @@ void main() { sut.transaction = 'test'; - expect( - sut.transaction, - 'test', - ); + expect(sut.transaction, 'test'); }); test('sets $User', () { @@ -32,10 +26,7 @@ void main() { final user = User(id: 'test'); sut.user = user; - expect( - sut.user, - user, - ); + expect(sut.user, user); }); test('sets fingerprint', () { @@ -44,10 +35,7 @@ void main() { final fingerprints = ['test']; sut.fingerprint = fingerprints; - expect( - sut.fingerprint, - fingerprints, - ); + expect(sut.fingerprint, fingerprints); }); test('adds $Breadcrumb', () { @@ -56,10 +44,7 @@ void main() { final breadcrumb = Breadcrumb('test log', DateTime.utc(2019)); sut.addBreadcrumb(breadcrumb); - expect( - sut.breadcrumbs.last, - breadcrumb, - ); + expect(sut.breadcrumbs.last, breadcrumb); }); test('respects max $Breadcrumb', () { @@ -73,10 +58,7 @@ void main() { sut.addBreadcrumb(breadcrumb2); sut.addBreadcrumb(breadcrumb3); - expect( - sut.breadcrumbs.length, - maxBreadcrumbs, - ); + expect(sut.breadcrumbs.length, maxBreadcrumbs); }); test('rotates $Breadcrumb', () { @@ -89,15 +71,9 @@ void main() { sut.addBreadcrumb(breadcrumb2); sut.addBreadcrumb(breadcrumb3); - expect( - sut.breadcrumbs.first, - breadcrumb2, - ); + expect(sut.breadcrumbs.first, breadcrumb2); - expect( - sut.breadcrumbs.last, - breadcrumb3, - ); + expect(sut.breadcrumbs.last, breadcrumb3); }); test('empty $Breadcrumb list', () { @@ -107,10 +83,7 @@ void main() { final breadcrumb1 = Breadcrumb('test log', DateTime.utc(2019)); sut.addBreadcrumb(breadcrumb1); - expect( - sut.breadcrumbs.length, - maxBreadcrumbs, - ); + expect(sut.breadcrumbs.length, maxBreadcrumbs); }); test('clears $Breadcrumb list', () { @@ -120,56 +93,41 @@ void main() { sut.addBreadcrumb(breadcrumb1); sut.clear(); - expect( - sut.breadcrumbs.length, - 0, - ); + expect(sut.breadcrumbs.length, 0); }); test('sets tag', () { final sut = fixture.getSut(); - sut.tags['test'] = 'test'; + sut.setTag('test', 'test'); - expect( - sut.tags['test'], - 'test', - ); + expect(sut.tags['test'], 'test'); }); test('removes tag', () { final sut = fixture.getSut(); - sut.tags['test'] = 'test'; - sut.tags.remove('test'); + sut.setTag('test', 'test'); + sut.removeTag('test'); - expect( - sut.tags['test'], - null, - ); + expect(sut.tags['test'], null); }); test('sets extra', () { final sut = fixture.getSut(); - sut.extra['test'] = 'test'; + sut.setExtra('test', 'test'); - expect( - sut.extra['test'], - 'test', - ); + expect(sut.extra['test'], 'test'); }); test('removes extra', () { final sut = fixture.getSut(); - sut.extra['test'] = 'test'; - sut.extra.remove('test'); + sut.setExtra('test', 'test'); + sut.removeExtra('test'); - expect( - sut.extra['test'], - null, - ); + expect(sut.extra['test'], null); }); test('clears $Scope', () { @@ -187,45 +145,24 @@ void main() { final fingerprints = ['test']; sut.fingerprint = fingerprints; - sut.tags['test'] = 'test'; - sut.extra['test'] = 'test'; + 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, - ); + 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); }); } From 6459069ad58926558137713e2b52378db6b7ec06 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 14 Oct 2020 13:19:22 +0200 Subject: [PATCH 4/4] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf86df0b03..b9692e13ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - new Dart code file structure #96 - 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 +- Added Scope and Breadcrumb ring buffer #109 # `package:sentry` changelog