Skip to content

Commit e754f11

Browse files
authored
Report isolate as thread (#847)
1 parent 9bedb1b commit e754f11

File tree

9 files changed

+110
-14
lines changed

9 files changed

+110
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
* Feat: Attach Isolate name to thread context (#847)
56
* Fix: Fix `SentryAssetBundle` on Flutter >= 3.1 (#877)
67
* Feat: Add Android thread to platform stacktraces (#853)
78
* Fix: Rename auto initialize property (#857)

dart/lib/src/sentry_client.dart

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'sentry_options.dart';
1212
import 'sentry_stack_trace_factory.dart';
1313
import 'transport/http_transport.dart';
1414
import 'transport/noop_transport.dart';
15+
import 'utils/isolate_utils.dart';
1516
import 'version.dart';
1617
import 'sentry_envelope.dart';
1718
import 'client_reports/client_report_recorder.dart';
@@ -140,18 +141,48 @@ class SentryClient {
140141
return event;
141142
}
142143

143-
if (event.exceptions?.isNotEmpty ?? false) return event;
144+
if (event.exceptions?.isNotEmpty ?? false) {
145+
return event;
146+
}
147+
148+
final isolateName = getIsolateName();
149+
// Isolates have no id, so the hashCode of the name will be used as id
150+
final isolateId = isolateName?.hashCode;
144151

145152
if (event.throwableMechanism != null) {
146-
final sentryException = _exceptionFactory.getSentryException(
153+
var sentryException = _exceptionFactory.getSentryException(
147154
event.throwableMechanism,
148155
stackTrace: stackTrace,
149156
);
150157

151-
return event.copyWith(exceptions: [
152-
...(event.exceptions ?? []),
153-
sentryException,
154-
]);
158+
if (_options.platformChecker.isWeb) {
159+
return event.copyWith(
160+
exceptions: [
161+
...?event.exceptions,
162+
sentryException,
163+
],
164+
);
165+
}
166+
167+
SentryThread? thread;
168+
169+
if (isolateName != null && _options.attachThreads) {
170+
sentryException = sentryException.copyWith(threadId: isolateId);
171+
thread = SentryThread(
172+
id: isolateId,
173+
name: isolateName,
174+
crashed: true,
175+
current: true,
176+
);
177+
}
178+
179+
return event.copyWith(
180+
exceptions: [...?event.exceptions, sentryException],
181+
threads: [
182+
...?event.threads,
183+
if (thread != null) thread,
184+
],
185+
);
155186
}
156187

157188
// The stacktrace is not part of an exception,
@@ -163,8 +194,10 @@ class SentryClient {
163194

164195
if (frames.isNotEmpty) {
165196
event = event.copyWith(threads: [
166-
...(event.threads ?? []),
197+
...?event.threads,
167198
SentryThread(
199+
name: isolateName,
200+
id: isolateId,
168201
crashed: false,
169202
current: true,
170203
stacktrace: SentryStackTrace(frames: frames),

dart/lib/src/sentry_exception_factory.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,11 @@ class SentryExceptionFactory {
4545

4646
// if --obfuscate feature is enabled, 'type' won't be human readable.
4747
// https://flutter.dev/docs/deployment/obfuscate#caveat
48-
final sentryException = SentryException(
49-
type: '${throwable.runtimeType}',
50-
value: '$throwable',
48+
return SentryException(
49+
type: (throwable.runtimeType).toString(),
50+
value: throwable.toString(),
5151
mechanism: mechanism,
5252
stackTrace: sentryStackTrace,
5353
);
54-
55-
return sentryException;
5654
}
5755
}

dart/lib/src/sentry_options.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,15 @@ class SentryOptions {
218218
/// variables. This is useful in tests.
219219
EnvironmentVariables environmentVariables = EnvironmentVariables.instance();
220220

221-
/// When enabled, all the threads are automatically attached to all logged events (Android).
221+
/// When enabled, the current isolate will be attached to the event.
222+
/// This only applies to Dart:io platforms and only the current isolate.
223+
/// The Dart runtime doesn't provide information about other active isolates.
224+
///
225+
/// When running on web, this option has no effect at all.
226+
///
227+
/// When running in the Flutter context, this enables attaching of threads
228+
/// for native events, if supported for the native platform.
229+
/// Currently, this is only supported on Android.
222230
bool attachThreads = false;
223231

224232
/// Whether to send personal identifiable information along with events
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import 'dart:isolate';
2+
3+
String? getIsolateName() => Isolate.current.debugName;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
String? getIsolateName() => null;

dart/lib/src/utils/isolate_utils.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import '_io_get_isolate_name.dart'
2+
if (dart.library.html) '_web_get_isolate_name.dart' as isolate_getter;
3+
4+
String? getIsolateName() => isolate_getter.getIsolateName();

dart/test/sentry_client_test.dart

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,50 @@ void main() {
110110
expect(capturedEvent.exceptions?.first.stackTrace, isNotNull);
111111
});
112112

113+
test(
114+
'should attach isolate info in thread',
115+
() async {
116+
final client = fixture.getSut(attachThreads: true);
117+
118+
await client.captureException(
119+
Exception(),
120+
stackTrace: StackTrace.current,
121+
);
122+
123+
final capturedEnvelope = (fixture.transport).envelopes.first;
124+
final capturedEvent = await eventFromEnvelope(capturedEnvelope);
125+
126+
expect(capturedEvent.threads?.first.current, true);
127+
expect(capturedEvent.threads?.first.crashed, true);
128+
expect(capturedEvent.threads?.first.name, isNotNull);
129+
expect(capturedEvent.threads?.first.id, isNotNull);
130+
131+
expect(
132+
capturedEvent.exceptions?.first.threadId,
133+
capturedEvent.threads?.first.id,
134+
);
135+
},
136+
onPlatform: {'js': Skip("Isolates don't exist on the web")},
137+
);
138+
139+
test(
140+
'should not attach isolate info in thread if disabled',
141+
() async {
142+
final client = fixture.getSut(attachThreads: false);
143+
144+
await client.captureException(
145+
Exception(),
146+
stackTrace: StackTrace.current,
147+
);
148+
149+
final capturedEnvelope = (fixture.transport).envelopes.first;
150+
final capturedEvent = await eventFromEnvelope(capturedEnvelope);
151+
152+
expect(capturedEvent.threads, null);
153+
},
154+
onPlatform: {'js': Skip("Isolates don't exist on the web")},
155+
);
156+
113157
test('should capture message', () async {
114158
final client = fixture.getSut();
115159
await client.captureMessage(
@@ -1014,6 +1058,7 @@ class Fixture {
10141058
SentryClient getSut({
10151059
bool sendDefaultPii = false,
10161060
bool attachStacktrace = true,
1061+
bool attachThreads = false,
10171062
double? sampleRate,
10181063
BeforeSendCallback? beforeSend,
10191064
EventProcessor? eventProcessor,
@@ -1029,14 +1074,16 @@ class Fixture {
10291074
options.tracesSampleRate = 1.0;
10301075
options.sendDefaultPii = sendDefaultPii;
10311076
options.attachStacktrace = attachStacktrace;
1077+
options.attachThreads = attachThreads;
10321078
options.sampleRate = sampleRate;
10331079
options.beforeSend = beforeSend;
1080+
10341081
if (eventProcessor != null) {
10351082
options.addEventProcessor(eventProcessor);
10361083
}
10371084
options.transport = transport;
10381085
final client = SentryClient(options);
1039-
// hub.bindClient(client);
1086+
10401087
if (provideMockRecorder) {
10411088
options.recorder = recorder;
10421089
}

flutter/example/lib/main.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Future<void> main() async {
2626
options.reportPackages = false;
2727
options.addInAppInclude('sentry_flutter_example');
2828
options.considerInAppFramesByDefault = false;
29+
options.attachThreads = true;
2930
},
3031
// Init your App.
3132
appRunner: () => runApp(

0 commit comments

Comments
 (0)