diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f7ce06059..f04c01fb11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ ### Features - Add support for span level measurements. ([#2214](https://github.com/getsentry/sentry-dart/pull/2214)) +- Add `ignoreTransactions` and `ignoreErrors` to options ([#2207](https://github.com/getsentry/sentry-dart/pull/2207)) + ```dart + await SentryFlutter.init( + (options) { + options.dsn = 'https://examplePublicKey@o0.ingest.sentry.io/0'; + options.ignoreErrors = ["my-error", "^error-.*\$"]; + options.ignoreTransactions = ["my-transaction", "^transaction-.*\$"]; + ... + }, + appRunner: () => runApp(MyApp()), + ); + ``` ## 8.6.0 diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index c319677832..c4ebac3db5 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -84,6 +84,16 @@ class SentryClient { dynamic stackTrace, Hint? hint, }) async { + if (_isIgnoredError(event)) { + _options.logger( + SentryLevel.debug, + 'Error was ignored as specified in the ignoredErrors options.', + ); + _options.recorder + .recordLostEvent(DiscardReason.ignored, _getCategory(event)); + return _emptySentryId; + } + if (_options.containsIgnoredExceptionForType(event.throwable)) { _options.logger( SentryLevel.debug, @@ -180,6 +190,15 @@ class SentryClient { return id ?? SentryId.empty(); } + bool _isIgnoredError(SentryEvent event) { + if (event.message == null || _options.ignoreErrors.isEmpty) { + return false; + } + + var message = event.message!.formatted; + return _isMatchingRegexPattern(message, _options.ignoreErrors); + } + SentryEvent _prepareEvent(SentryEvent event, {dynamic stackTrace}) { event = event.copyWith( serverName: event.serverName ?? _options.serverName, @@ -351,6 +370,17 @@ class SentryClient { return _emptySentryId; } + if (_isIgnoredTransaction(preparedTransaction)) { + _options.logger( + SentryLevel.debug, + 'Transaction was ignored as specified in the ignoredTransactions options.', + ); + + _options.recorder.recordLostEvent( + DiscardReason.ignored, _getCategory(preparedTransaction)); + return _emptySentryId; + } + preparedTransaction = await _runBeforeSend(preparedTransaction, hint) as SentryTransaction?; @@ -379,6 +409,15 @@ class SentryClient { return id ?? SentryId.empty(); } + bool _isIgnoredTransaction(SentryTransaction transaction) { + if (_options.ignoreTransactions.isEmpty) { + return false; + } + + var name = transaction.tracer.name; + return _isMatchingRegexPattern(name, _options.ignoreTransactions); + } + /// Reports the [envelope] to Sentry.io. Future captureEnvelope(SentryEnvelope envelope) { return _attachClientReportsAndSend(envelope); @@ -554,4 +593,11 @@ class SentryClient { SentryId.empty(), ); } + + bool _isMatchingRegexPattern(String value, List regexPattern, + {bool caseSensitive = false}) { + final combinedRegexPattern = regexPattern.join('|'); + final regExp = RegExp(combinedRegexPattern, caseSensitive: caseSensitive); + return regExp.hasMatch(value); + } } diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 636534fb5d..26cda852c4 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -184,6 +184,14 @@ class SentryOptions { /// sent. Events are picked randomly. Default is null (disabled) double? sampleRate; + /// The ignoreErrors tells the SDK which errors should be not sent to the sentry server. + /// If an null or an empty list is used, the SDK will send all transactions. + List ignoreErrors = []; + + /// The ignoreTransactions tells the SDK which transactions should be not sent to the sentry server. + /// If null or an empty list is used, the SDK will send all transactions. + List ignoreTransactions = []; + final List _inAppExcludes = []; /// A list of string prefixes of packages names that do not belong to the app, but rather third-party diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart index 5a2945a6ec..772699b14d 100644 --- a/dart/test/sentry_client_test.dart +++ b/dart/test/sentry_client_test.dart @@ -1033,6 +1033,130 @@ void main() { }); }); + group('SentryClient ignored errors', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + fixture.options.ignoreErrors = ["my-error", "^error-.*\$"]; + }); + + test('drop event if error message fully matches ignoreErrors value', + () async { + final event = SentryEvent(message: SentryMessage("my-error")); + + final client = fixture.getSut(); + await client.captureEvent(event); + + expect((fixture.transport).called(0), true); + }); + + test('drop event if error message partially matches ignoreErrors value', + () async { + final event = SentryEvent(message: SentryMessage("this is my-error-foo")); + + final client = fixture.getSut(); + await client.captureEvent(event); + + expect((fixture.transport).called(0), true); + }); + + test( + 'drop event if error message partially matches ignoreErrors regex value', + () async { + final event = SentryEvent(message: SentryMessage("error-test message")); + + final client = fixture.getSut(); + await client.captureEvent(event); + + expect((fixture.transport).called(0), true); + }); + + test('send event if error message does not match ignoreErrors value', + () async { + final event = SentryEvent(message: SentryMessage("warning")); + + final client = fixture.getSut(); + await client.captureEvent(event); + + expect((fixture.transport).called(1), true); + }); + + test('send event if no values are set for ignoreErrors', () async { + fixture.options.ignoreErrors = []; + final event = SentryEvent(message: SentryMessage("this is a test event")); + + final client = fixture.getSut(); + await client.captureEvent(event); + + expect((fixture.transport).called(1), true); + }); + }); + + group('SentryClient ignored transactions', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + fixture.options.ignoreTransactions = [ + "my-transaction", + "^transaction-.*\$" + ]; + }); + + test('drop transaction if name fully matches ignoreTransaction value', + () async { + final client = fixture.getSut(); + final fakeTransaction = fixture.fakeTransaction(); + fakeTransaction.tracer.name = "my-transaction"; + await client.captureTransaction(fakeTransaction); + + expect((fixture.transport).called(0), true); + }); + + test('drop transaction if name partially matches ignoreTransaction value', + () async { + final client = fixture.getSut(); + final fakeTransaction = fixture.fakeTransaction(); + fakeTransaction.tracer.name = "this is a transaction-test"; + await client.captureTransaction(fakeTransaction); + + expect((fixture.transport).called(0), true); + }); + + test( + 'drop transaction if name partially matches ignoreTransaction regex value', + () async { + final client = fixture.getSut(); + final fakeTransaction = fixture.fakeTransaction(); + fakeTransaction.tracer.name = "transaction-test message"; + await client.captureTransaction(fakeTransaction); + + expect((fixture.transport).called(0), true); + }); + + test('send transaction if name does not match ignoreTransaction value', + () async { + final client = fixture.getSut(); + final fakeTransaction = fixture.fakeTransaction(); + fakeTransaction.tracer.name = "capture"; + await client.captureTransaction(fakeTransaction); + + expect((fixture.transport).called(1), true); + }); + + test('send transaction if no values are set for ignoreTransaction', + () async { + fixture.options.ignoreTransactions = []; + final client = fixture.getSut(); + final fakeTransaction = fixture.fakeTransaction(); + fakeTransaction.tracer.name = "this is a test transaction"; + await client.captureTransaction(fakeTransaction); + + expect((fixture.transport).called(1), true); + }); + }); + group('SentryClient ignored exceptions', () { late Fixture fixture;