Skip to content

Commit 2b7d7f8

Browse files
committed
Merge branch 'main' into feat/replay-resizing
2 parents 4080ee5 + b6bb5b4 commit 2b7d7f8

11 files changed

Lines changed: 392 additions & 203 deletions

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,28 @@
55
### Features
66

77
- Replay: device orientation change support & improve video size fit on Android ([#2462](https://github.com/getsentry/sentry-dart/pull/2462))
8+
- Support custom `Sentry.runZoneGuarded` zone creation ([#2088](https://github.com/getsentry/sentry-dart/pull/2088))
9+
- Sentry will not create a custom zone anymore if it is started within a custom one.
10+
- This fixes Zone miss-match errors when trying to initialize WidgetsBinding before Sentry on Flutter Web
11+
- `Sentry.runZonedGuarded` creates a zone and also captures exceptions & breadcrumbs automatically.
12+
```dart
13+
Sentry.runZonedGuarded(() {
14+
WidgetsBinding.ensureInitialized();
15+
16+
// Errors before init will not be handled by Sentry
17+
18+
SentryFlutter.init(
19+
(options) {
20+
...
21+
},
22+
appRunner: () => runApp(MyApp()),
23+
);
24+
} (error, stackTrace) {
25+
// Automatically sends errors to Sentry, no need to do any
26+
// captureException calls on your part.
27+
// On top of that, you can do your own custom stuff in this callback.
28+
});
29+
```
830

931
## 8.11.0-beta.2
1032

@@ -56,6 +78,7 @@
5678
```
5779
- Replace deprecated `BeforeScreenshotCallback` with new `BeforeCaptureCallback`.
5880

81+
5982
### Fixes
6083

6184
- Catch errors thrown during `handleBeginFrame` and `handleDrawFrame` ([#2446](https://github.com/getsentry/sentry-dart/pull/2446))

dart/lib/src/platform_checker.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
import 'dart:async';
12
import 'platform/platform.dart';
23

3-
/// Helper to check in which enviroment the library is running.
4-
/// The envirment checks (release/debug/profile) are mutually exclusive.
4+
/// Helper to check in which environment the library is running.
5+
/// The environment checks (release/debug/profile) are mutually exclusive.
56
class PlatformChecker {
67
static const _jsUtil = 'dart.library.js_util';
78

89
PlatformChecker({
910
this.platform = instance,
1011
bool? isWeb,
11-
}) : isWeb = isWeb ?? _isWebWithWasmSupport();
12+
bool? isRootZone,
13+
}) : isWeb = isWeb ?? _isWebWithWasmSupport(),
14+
isRootZone = isRootZone ?? Zone.current == Zone.root;
1215

1316
/// Check if running in release/production environment
1417
bool isReleaseMode() {
@@ -26,6 +29,7 @@ class PlatformChecker {
2629
}
2730

2831
final bool isWeb;
32+
final bool isRootZone;
2933

3034
String get compileMode {
3135
return isReleaseMode()

dart/lib/src/run_zoned_guarded_integration.dart

Lines changed: 9 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import 'dart:async';
22

3-
import 'package:meta/meta.dart';
4-
5-
import 'hub.dart';
6-
import 'integration.dart';
7-
import 'protocol.dart';
8-
import 'sentry_options.dart';
9-
import 'throwable_mechanism.dart';
3+
import 'sentry_run_zoned_guarded.dart';
4+
import '../sentry.dart';
105

116
/// Called inside of `runZonedGuarded`
127
typedef RunZonedGuardedRunner = Future<void> Function();
@@ -26,107 +21,17 @@ class RunZonedGuardedIntegration extends Integration<SentryOptions> {
2621
final RunZonedGuardedRunner _runner;
2722
final RunZonedGuardedOnError? _onError;
2823

29-
/// Needed to check if we somehow caused a `print()` recursion
30-
bool _isPrinting = false;
31-
32-
@visibleForTesting
33-
Future<void> captureError(
34-
Hub hub,
35-
SentryOptions options,
36-
Object exception,
37-
StackTrace stackTrace,
38-
) async {
39-
options.logger(
40-
SentryLevel.error,
41-
'Uncaught zone error',
42-
logger: 'sentry.runZonedGuarded',
43-
exception: exception,
44-
stackTrace: stackTrace,
45-
);
46-
47-
// runZonedGuarded doesn't crash the app, but is not handled by the user.
48-
final mechanism = Mechanism(type: 'runZonedGuarded', handled: false);
49-
final throwableMechanism = ThrowableMechanism(mechanism, exception);
50-
51-
final event = SentryEvent(
52-
throwable: throwableMechanism,
53-
level: options.markAutomaticallyCollectedErrorsAsFatal
54-
? SentryLevel.fatal
55-
: SentryLevel.error,
56-
timestamp: hub.options.clock(),
57-
);
58-
59-
// marks the span status if none to `internal_error` in case there's an
60-
// unhandled error
61-
hub.configureScope(
62-
(scope) => scope.span?.status ??= const SpanStatus.internalError(),
63-
);
64-
65-
await hub.captureEvent(event, stackTrace: stackTrace);
66-
}
67-
6824
@override
6925
Future<void> call(Hub hub, SentryOptions options) {
7026
final completer = Completer<void>();
7127

72-
runZonedGuarded(
73-
() async {
74-
try {
75-
await _runner();
76-
} finally {
77-
completer.complete();
78-
}
79-
},
80-
(exception, stackTrace) async {
81-
await captureError(hub, options, exception, stackTrace);
82-
final onError = _onError;
83-
if (onError != null) {
84-
await onError(exception, stackTrace);
85-
}
86-
},
87-
zoneSpecification: ZoneSpecification(
88-
print: (self, parent, zone, line) {
89-
if (!options.enablePrintBreadcrumbs || !hub.isEnabled) {
90-
// early bail out, in order to better guard against the recursion
91-
// as described below.
92-
parent.print(zone, line);
93-
return;
94-
}
95-
96-
if (_isPrinting) {
97-
// We somehow landed in a recursion.
98-
// This happens for example if:
99-
// - hub.addBreadcrumb() called print() itself
100-
// - This happens for example if hub.isEnabled == false and
101-
// options.logger == dartLogger
102-
//
103-
// Anyway, in order to not cause a stack overflow due to recursion
104-
// we drop any further print() call while adding a breadcrumb.
105-
parent.print(
106-
zone,
107-
'Recursion during print() call. '
108-
'Abort adding print() call as Breadcrumb.',
109-
);
110-
return;
111-
}
112-
113-
_isPrinting = true;
114-
115-
try {
116-
hub.addBreadcrumb(
117-
Breadcrumb.console(
118-
message: line,
119-
level: SentryLevel.debug,
120-
),
121-
);
122-
123-
parent.print(zone, line);
124-
} finally {
125-
_isPrinting = false;
126-
}
127-
},
128-
),
129-
);
28+
SentryRunZonedGuarded.sentryRunZonedGuarded(hub, () async {
29+
try {
30+
await _runner();
31+
} finally {
32+
completer.complete();
33+
}
34+
}, _onError);
13035

13136
options.sdk.addIntegration('runZonedGuardedIntegration');
13237

dart/lib/src/sentry.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import 'noop_isolate_error_integration.dart'
2121
import 'protocol.dart';
2222
import 'sentry_client.dart';
2323
import 'sentry_options.dart';
24+
import 'sentry_run_zoned_guarded.dart';
2425
import 'sentry_user_feedback.dart';
2526
import 'tracing.dart';
2627
import 'sentry_attachment/sentry_attachment.dart';
@@ -365,4 +366,45 @@ class Sentry {
365366

366367
@internal
367368
static Hub get currentHub => _hub;
369+
370+
/// Creates a new error handling zone with Sentry integration using [runZonedGuarded].
371+
///
372+
/// This method provides automatic error reporting and breadcrumb tracking while
373+
/// allowing you to define a custom error handling zone. It wraps Dart's native
374+
/// [runZonedGuarded] function with Sentry-specific functionality.
375+
///
376+
/// This function automatically records calls to `print()` as Breadcrumbs and
377+
/// can be configured using [SentryOptions.enablePrintBreadcrumbs].
378+
///
379+
/// ```dart
380+
/// Sentry.runZonedGuarded(() {
381+
/// WidgetsBinding.ensureInitialized();
382+
///
383+
/// // Errors before init will not be handled by Sentry
384+
///
385+
/// SentryFlutter.init(
386+
/// (options) {
387+
/// ...
388+
/// },
389+
/// appRunner: () => runApp(MyApp()),
390+
/// );
391+
/// } (error, stackTrace) {
392+
/// // Automatically sends errors to Sentry, no need to do any
393+
/// // captureException calls on your part.
394+
/// // On top of that, you can do your own custom stuff in this callback.
395+
/// });
396+
/// ```
397+
static runZonedGuarded<R>(
398+
R Function() body,
399+
void Function(Object error, StackTrace stack)? onError, {
400+
Map<Object?, Object?>? zoneValues,
401+
ZoneSpecification? zoneSpecification,
402+
}) =>
403+
SentryRunZonedGuarded.sentryRunZonedGuarded(
404+
_hub,
405+
body,
406+
onError,
407+
zoneValues: zoneValues,
408+
zoneSpecification: zoneSpecification,
409+
);
368410
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import 'dart:async';
2+
3+
import 'package:meta/meta.dart';
4+
5+
import '../sentry.dart';
6+
7+
@internal
8+
class SentryRunZonedGuarded {
9+
/// Needed to check if we somehow caused a `print()` recursion
10+
static var _isPrinting = false;
11+
12+
static R? sentryRunZonedGuarded<R>(
13+
Hub hub,
14+
R Function() body,
15+
void Function(Object error, StackTrace stack)? onError, {
16+
Map<Object?, Object?>? zoneValues,
17+
ZoneSpecification? zoneSpecification,
18+
}) {
19+
final sentryOnError = (exception, stackTrace) async {
20+
final options = hub.options;
21+
await _captureError(hub, options, exception, stackTrace);
22+
23+
if (onError != null) {
24+
onError(exception, stackTrace);
25+
}
26+
};
27+
28+
final userPrint = zoneSpecification?.print;
29+
30+
final sentryZoneSpecification = ZoneSpecification.from(
31+
zoneSpecification ?? ZoneSpecification(),
32+
print: (self, parent, zone, line) async {
33+
final options = hub.options;
34+
35+
if (userPrint != null) {
36+
userPrint(self, parent, zone, line);
37+
}
38+
39+
if (!options.enablePrintBreadcrumbs || !hub.isEnabled) {
40+
// early bail out, in order to better guard against the recursion
41+
// as described below.
42+
parent.print(zone, line);
43+
return;
44+
}
45+
if (_isPrinting) {
46+
// We somehow landed in a recursion.
47+
// This happens for example if:
48+
// - hub.addBreadcrumb() called print() itself
49+
// - This happens for example if hub.isEnabled == false and
50+
// options.logger == dartLogger
51+
//
52+
// Anyway, in order to not cause a stack overflow due to recursion
53+
// we drop any further print() call while adding a breadcrumb.
54+
parent.print(
55+
zone,
56+
'Recursion during print() call.'
57+
'Abort adding print() call as Breadcrumb.',
58+
);
59+
return;
60+
}
61+
62+
try {
63+
_isPrinting = true;
64+
await hub.addBreadcrumb(
65+
Breadcrumb.console(
66+
message: line,
67+
level: SentryLevel.debug,
68+
),
69+
);
70+
parent.print(zone, line);
71+
} finally {
72+
_isPrinting = false;
73+
}
74+
},
75+
);
76+
return runZonedGuarded(
77+
body,
78+
sentryOnError,
79+
zoneValues: zoneValues,
80+
zoneSpecification: sentryZoneSpecification,
81+
);
82+
}
83+
84+
static Future<void> _captureError(
85+
Hub hub,
86+
SentryOptions options,
87+
Object exception,
88+
StackTrace stackTrace,
89+
) async {
90+
options.logger(
91+
SentryLevel.error,
92+
'Uncaught zone error',
93+
logger: 'sentry.runZonedGuarded',
94+
exception: exception,
95+
stackTrace: stackTrace,
96+
);
97+
98+
// runZonedGuarded doesn't crash the app, but is not handled by the user.
99+
final mechanism = Mechanism(type: 'runZonedGuarded', handled: false);
100+
final throwableMechanism = ThrowableMechanism(mechanism, exception);
101+
102+
final event = SentryEvent(
103+
throwable: throwableMechanism,
104+
level: options.markAutomaticallyCollectedErrorsAsFatal
105+
? SentryLevel.fatal
106+
: SentryLevel.error,
107+
timestamp: hub.options.clock(),
108+
);
109+
110+
// marks the span status if none to `internal_error` in case there's an
111+
// unhandled error
112+
hub.configureScope(
113+
(scope) => scope.span?.status ??= const SpanStatus.internalError(),
114+
);
115+
116+
await hub.captureEvent(event, stackTrace: stackTrace);
117+
}
118+
}

0 commit comments

Comments
 (0)