From 1abaf254ea8df4a1ba80a5963393107d68405c84 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 13 Nov 2023 11:08:23 +0100 Subject: [PATCH 1/2] Add `markUnhandledAsFatal` option --- .../src/run_zoned_guarded_integration.dart | 3 ++- dart/lib/src/sentry_isolate.dart | 5 +++-- dart/lib/src/sentry_options.dart | 7 ++++++- .../run_zoned_guarded_integration_test.dart | 17 +++++++++++++++ dart/test/sentry_client_test.dart | 6 ++---- dart/test/sentry_isolate_test.dart | 21 ++++++++++++++++--- .../flutter_error_integration.dart | 4 +++- flutter/lib/src/sentry_flutter_options.dart | 1 + .../flutter_error_integration_test.dart | 14 +++++++++++++ 9 files changed, 66 insertions(+), 12 deletions(-) diff --git a/dart/lib/src/run_zoned_guarded_integration.dart b/dart/lib/src/run_zoned_guarded_integration.dart index 623a9a5f54..2defea53a2 100644 --- a/dart/lib/src/run_zoned_guarded_integration.dart +++ b/dart/lib/src/run_zoned_guarded_integration.dart @@ -50,7 +50,8 @@ class RunZonedGuardedIntegration extends Integration { final event = SentryEvent( throwable: throwableMechanism, - level: SentryLevel.fatal, + level: + options.markUnhandledAsFatal ? SentryLevel.fatal : SentryLevel.error, timestamp: hub.options.clock(), ); diff --git a/dart/lib/src/sentry_isolate.dart b/dart/lib/src/sentry_isolate.dart index 8a6b71d48f..e055c9ceca 100644 --- a/dart/lib/src/sentry_isolate.dart +++ b/dart/lib/src/sentry_isolate.dart @@ -72,10 +72,11 @@ class SentryIsolate { // Isolate errors don't crash the app, but is not handled by the user. final mechanism = Mechanism(type: 'isolateError', handled: false); final throwableMechanism = ThrowableMechanism(mechanism, throwable); - final event = SentryEvent( throwable: throwableMechanism, - level: SentryLevel.fatal, + level: hub.options.markUnhandledAsFatal + ? SentryLevel.fatal + : SentryLevel.error, timestamp: hub.options.clock(), ); diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 37f7dc01e9..c14c22266f 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -4,7 +4,7 @@ import 'dart:developer'; import 'package:meta/meta.dart'; import 'package:http/http.dart'; -import '../sentry.dart'; +import '../sentry_io.dart'; import 'client_reports/client_report_recorder.dart'; import 'client_reports/noop_client_report_recorder.dart'; import 'sentry_exception_factory.dart'; @@ -358,6 +358,11 @@ class SentryOptions { @internal bool devMode = false; + /// Unhandled exceptions that the SDK automatically reports, for example in + /// [SentryIsolate], have `level` [SentryLevel.fatal] set per default. + /// Settings this to `false` will set the `level` to [SentryLevel.error]. + bool markUnhandledAsFatal = true; + SentryOptions({this.dsn, PlatformChecker? checker}) { if (checker != null) { platformChecker = checker; diff --git a/dart/test/run_zoned_guarded_integration_test.dart b/dart/test/run_zoned_guarded_integration_test.dart index 7e8045445d..28f23ee5de 100644 --- a/dart/test/run_zoned_guarded_integration_test.dart +++ b/dart/test/run_zoned_guarded_integration_test.dart @@ -54,6 +54,23 @@ void main() { expect(onErrorCalled, true); }); + + test('sets level to error instead of fatal', () async { + final exception = StateError('error'); + final stackTrace = StackTrace.current; + + final hub = Hub(fixture.options); + final client = MockSentryClient(); + hub.bindClient(client); + + final sut = fixture.getSut(runner: () async {}); + + fixture.options.markUnhandledAsFatal = false; + await sut.captureError(hub, fixture.options, exception, stackTrace); + + final capturedEvent = client.captureEventCalls.last.event; + expect(capturedEvent.level, SentryLevel.error); + }); }); } diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart index 522bfcae73..acf71276d5 100644 --- a/dart/test/sentry_client_test.dart +++ b/dart/test/sentry_client_test.dart @@ -847,8 +847,7 @@ void main() { expect(capturedEvent.user?.ipAddress, '{{auto}}'); }); - test('event has a user with IP address', - () async { + test('event has a user with IP address', () async { final client = fixture.getSut(sendDefaultPii: true); await client.captureEvent(fakeEvent); @@ -864,8 +863,7 @@ void main() { expect(capturedEvent.user?.email, fakeEvent.user!.email); }); - test('event has a user without IP address', - () async { + test('event has a user without IP address', () async { final client = fixture.getSut(sendDefaultPii: true); final event = fakeEvent.copyWith(user: fakeUser); diff --git a/dart/test/sentry_isolate_test.dart b/dart/test/sentry_isolate_test.dart index 1b1939ac2f..2fb7b469c5 100644 --- a/dart/test/sentry_isolate_test.dart +++ b/dart/test/sentry_isolate_test.dart @@ -1,9 +1,7 @@ @TestOn('vm') -import 'package:sentry/src/hub.dart'; -import 'package:sentry/src/protocol/span_status.dart'; +import 'package:sentry/sentry.dart'; import 'package:sentry/src/sentry_isolate.dart'; -import 'package:sentry/src/sentry_options.dart'; import 'package:test/test.dart'; import 'mocks.dart'; @@ -48,6 +46,23 @@ void main() { await span?.finish(); }); + + test('sets level to error instead of fatal', () async { + final exception = StateError('error'); + final stackTrace = StackTrace.current.toString(); + + final hub = Hub(fixture.options); + final client = MockSentryClient(); + hub.bindClient(client); + + fixture.options.markUnhandledAsFatal = false; + + await SentryIsolate.handleIsolateError( + hub, [exception.toString(), stackTrace]); + + final capturedEvent = client.captureEventCalls.last.event; + expect(capturedEvent.level, SentryLevel.error); + }); }); } diff --git a/flutter/lib/src/integrations/flutter_error_integration.dart b/flutter/lib/src/integrations/flutter_error_integration.dart index 03f8436bf9..0ab767e3b2 100644 --- a/flutter/lib/src/integrations/flutter_error_integration.dart +++ b/flutter/lib/src/integrations/flutter_error_integration.dart @@ -60,7 +60,9 @@ class FlutterErrorIntegration implements Integration { var event = SentryEvent( throwable: throwableMechanism, - level: SentryLevel.fatal, + level: options.markUnhandledAsFatal + ? SentryLevel.fatal + : SentryLevel.error, contexts: flutterErrorDetails.isNotEmpty ? (Contexts()..['flutter_error_details'] = flutterErrorDetails) : null, diff --git a/flutter/lib/src/sentry_flutter_options.dart b/flutter/lib/src/sentry_flutter_options.dart index a1d7755c3c..8865cf70ec 100644 --- a/flutter/lib/src/sentry_flutter_options.dart +++ b/flutter/lib/src/sentry_flutter_options.dart @@ -1,6 +1,7 @@ import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; import 'package:flutter/widgets.dart'; +import 'package:sentry/sentry_io.dart'; import 'binding_wrapper.dart'; import 'renderer/renderer.dart'; diff --git a/flutter/test/integrations/flutter_error_integration_test.dart b/flutter/test/integrations/flutter_error_integration_test.dart index 43df8de278..72466e9e6b 100644 --- a/flutter/test/integrations/flutter_error_integration_test.dart +++ b/flutter/test/integrations/flutter_error_integration_test.dart @@ -246,6 +246,20 @@ void main() { await span?.finish(); }); + + test('captures error with level error', () async { + final exception = StateError('error'); + + fixture.options.markUnhandledAsFatal = false; + + _reportError(exception: exception); + + final event = verify( + await fixture.hub.captureEvent(captureAny, hint: anyNamed('hint')), + ).captured.first as SentryEvent; + + expect(event.level, SentryLevel.error); + }); }); } From 67999133f575ef4c91a49dc07d44203684e6da9f Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Mon, 13 Nov 2023 11:26:31 +0100 Subject: [PATCH 2/2] add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 323f9c563e..3d3f3a5fb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 8.0.0 +### Features + +- Add `markUnhandledAsFatal` to `SentryOptions` ([#1720](https://github.com/getsentry/sentry-dart/pull/1720)) + ### Breaking Changes - Mark exceptions not handled by the user as `handled: false` ([#1535](https://github.com/getsentry/sentry-dart/pull/1535))