Skip to content

Commit 634d450

Browse files
committed
implement loadStructuredBinaryData
1 parent d301b11 commit 634d450

File tree

3 files changed

+256
-4
lines changed

3 files changed

+256
-4
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Implement `loadStructuredBinaryData` from updated AssetBundle ([#1272](https://github.com/getsentry/sentry-dart/pull/1272))
8+
59
### Dependencies
610

711
- Bump Android SDK from v6.13.0 to v6.13.1 ([#1273](https://github.com/getsentry/sentry-dart/pull/1273))

flutter/lib/src/sentry_asset_bundle.dart

+100-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import 'package:flutter/services.dart';
99
import 'package:flutter/material.dart';
1010
import 'package:sentry/sentry.dart';
1111

12-
typedef _Parser<T> = Future<T> Function(String value);
12+
typedef _StringParser<T> = Future<T> Function(String value);
13+
typedef _ByteParser<T> = FutureOr<T> Function(ByteData value);
1314

1415
/// An [AssetBundle] which creates automatic performance traces for loading
1516
/// assets.
@@ -79,15 +80,15 @@ class SentryAssetBundle implements AssetBundle {
7980
}
8081

8182
@override
82-
Future<T> loadStructuredData<T>(String key, _Parser<T> parser) {
83+
Future<T> loadStructuredData<T>(String key, _StringParser<T> parser) {
8384
if (_enableStructuredDataTracing) {
8485
return _loadStructuredDataWithTracing(key, parser);
8586
}
8687
return _bundle.loadStructuredData(key, parser);
8788
}
8889

8990
Future<T> _loadStructuredDataWithTracing<T>(
90-
String key, _Parser<T> parser) async {
91+
String key, _StringParser<T> parser) async {
9192
final span = _hub.getSpan()?.startChild(
9293
'file.read',
9394
description: 'AssetBundle.loadStructuredData<$T>: ${_fileName(key)}',
@@ -125,6 +126,46 @@ class SentryAssetBundle implements AssetBundle {
125126
return data;
126127
}
127128

129+
FutureOr<T> _loadStructuredBinaryDataWithTracing<T>(
130+
String key, _ByteParser<T> parser) async {
131+
final span = _hub.getSpan()?.startChild(
132+
'file.read',
133+
description:
134+
'AssetBundle.loadStructuredBinaryData<$T>: ${_fileName(key)}',
135+
);
136+
span?.setData('file.path', key);
137+
138+
final completer = Completer<T>();
139+
140+
// This future is intentionally not awaited. Otherwise we deadlock with
141+
// the completer.
142+
// ignore: unawaited_futures
143+
runZonedGuarded(() async {
144+
final data = await _loadStructuredBinaryDataWrapper(
145+
key,
146+
(value) async => await _wrapBinaryParsing(parser, value, key, span),
147+
);
148+
span?.status = SpanStatus.ok();
149+
completer.complete(data);
150+
}, (exception, stackTrace) {
151+
completer.completeError(exception, stackTrace);
152+
});
153+
154+
T data;
155+
try {
156+
data = await completer.future;
157+
_setDataLength(data, span);
158+
span?.status = const SpanStatus.ok();
159+
} catch (e) {
160+
span?.throwable = e;
161+
span?.status = const SpanStatus.internalError();
162+
rethrow;
163+
} finally {
164+
await span?.finish();
165+
}
166+
return data;
167+
}
168+
128169
@override
129170
Future<String> loadString(String key, {bool cache = true}) async {
130171
final span = _hub.getSpan()?.startChild(
@@ -236,7 +277,7 @@ class SentryAssetBundle implements AssetBundle {
236277
}
237278

238279
static Future<T> _wrapParsing<T>(
239-
_Parser<T> parser,
280+
_StringParser<T> parser,
240281
String value,
241282
String key,
242283
ISentrySpan? outerSpan,
@@ -259,4 +300,59 @@ class SentryAssetBundle implements AssetBundle {
259300

260301
return data;
261302
}
303+
304+
static FutureOr<T> _wrapBinaryParsing<T>(
305+
_ByteParser<T> parser,
306+
ByteData value,
307+
String key,
308+
ISentrySpan? outerSpan,
309+
) async {
310+
final span = outerSpan?.startChild(
311+
'serialize.file.read',
312+
description: 'parsing "$key" to "$T"',
313+
);
314+
T data;
315+
try {
316+
data = await parser(value);
317+
span?.status = const SpanStatus.ok();
318+
} catch (e) {
319+
span?.throwable = e;
320+
span?.status = const SpanStatus.internalError();
321+
rethrow;
322+
} finally {
323+
await span?.finish();
324+
}
325+
326+
return data;
327+
}
328+
329+
@override
330+
// ignore: override_on_non_overriding_member
331+
Future<T> loadStructuredBinaryData<T>(
332+
String key,
333+
FutureOr<T> Function(ByteData data) parser,
334+
) async {
335+
if (_enableStructuredDataTracing) {
336+
return _loadStructuredBinaryDataWithTracing<T>(key, parser);
337+
}
338+
339+
return _loadStructuredBinaryDataWrapper<T>(key, parser);
340+
}
341+
342+
// helper method to have a "typesafe" method
343+
Future<T> _loadStructuredBinaryDataWrapper<T>(
344+
String key,
345+
FutureOr<T> Function(ByteData data) parser,
346+
) async {
347+
// The loadStructuredBinaryData method exists as of Flutter greater than 3.8
348+
// Previous versions don't have it, but later versions do.
349+
// We can't use `extends` in order to provide this method because this is
350+
// a wrapper and thus the method call must be forwarded.
351+
// On Flutter versions <=3.8 we can't forward this call.
352+
// On later version the call gets correctly forwarded.
353+
// The error doesn't need to handled since it can't be called on earlier versions,
354+
// and it's correctly forwarded on later versions.
355+
return (_bundle as dynamic).loadStructuredBinaryData<T>(key, parser)
356+
as Future<T>;
357+
}
262358
}

flutter/test/sentry_asset_bundle_test.dart

+152
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,158 @@ void main() {
333333
},
334334
);
335335

336+
test(
337+
'loadStructuredBinaryData: does not create any spans and just forwords the call to the underlying assetbundle if disabled',
338+
() async {
339+
final sut = fixture.getSut(structuredDataTracing: false);
340+
final tr = fixture._hub.startTransaction(
341+
'name',
342+
'op',
343+
bindToScope: true,
344+
);
345+
346+
final data = await sut.loadStructuredBinaryData<String>(
347+
_testFileName,
348+
(value) async => utf8.decode(
349+
value.buffer.asUint8List(value.offsetInBytes, value.lengthInBytes),
350+
),
351+
);
352+
expect(data, 'Hello World!');
353+
354+
await tr.finish();
355+
356+
final tracer = (tr as SentryTracer);
357+
358+
expect(tracer.children.length, 0);
359+
},
360+
);
361+
362+
test(
363+
'loadStructuredBinaryData: finish with errored span if loading fails',
364+
() async {
365+
final sut = fixture.getSut(throwException: true);
366+
final tr = fixture._hub.startTransaction(
367+
'name',
368+
'op',
369+
bindToScope: true,
370+
);
371+
await expectLater(
372+
sut.loadStructuredBinaryData<String>(
373+
_testFileName,
374+
(value) async => utf8.decode(
375+
value.buffer
376+
.asUint8List(value.offsetInBytes, value.lengthInBytes),
377+
),
378+
),
379+
throwsA(isA<Exception>()),
380+
);
381+
382+
await tr.finish();
383+
384+
final tracer = (tr as SentryTracer);
385+
final span = tracer.children.first;
386+
387+
expect(span.status, SpanStatus.internalError());
388+
expect(span.finished, true);
389+
expect(span.throwable, isA<Exception>());
390+
expect(span.context.operation, 'file.read');
391+
expect(
392+
span.context.description,
393+
'AssetBundle.loadStructuredBinaryData<String>: test.txt',
394+
);
395+
},
396+
);
397+
398+
test(
399+
'loadStructuredBinaryData: finish with errored span if parsing fails',
400+
() async {
401+
final sut = fixture.getSut(throwException: false);
402+
final tr = fixture._hub.startTransaction(
403+
'name',
404+
'op',
405+
bindToScope: true,
406+
);
407+
await expectLater(
408+
sut.loadStructuredBinaryData<String>(
409+
_testFileName,
410+
(value) async => throw Exception('error while parsing'),
411+
),
412+
throwsA(isA<Exception>()),
413+
);
414+
415+
await tr.finish();
416+
417+
final tracer = (tr as SentryTracer);
418+
var span = tracer.children.first;
419+
420+
expect(tracer.children.length, 2);
421+
422+
expect(span.status, SpanStatus.internalError());
423+
expect(span.finished, true);
424+
expect(span.throwable, isA<Exception>());
425+
expect(span.context.operation, 'file.read');
426+
expect(
427+
span.context.description,
428+
'AssetBundle.loadStructuredBinaryData<String>: test.txt',
429+
);
430+
431+
span = tracer.children[1];
432+
433+
expect(span.status, SpanStatus.internalError());
434+
expect(span.finished, true);
435+
expect(span.throwable, isA<Exception>());
436+
expect(span.context.operation, 'serialize.file.read');
437+
expect(
438+
span.context.description,
439+
'parsing "resources/test.txt" to "String"',
440+
);
441+
},
442+
);
443+
444+
test(
445+
'loadStructuredBinaryData: finish with successfully',
446+
() async {
447+
final sut = fixture.getSut(throwException: false);
448+
final tr = fixture._hub.startTransaction(
449+
'name',
450+
'op',
451+
bindToScope: true,
452+
);
453+
454+
await sut.loadStructuredBinaryData<String>(
455+
_testFileName,
456+
(value) async => utf8.decode(
457+
value.buffer.asUint8List(value.offsetInBytes, value.lengthInBytes),
458+
),
459+
);
460+
461+
await tr.finish();
462+
463+
final tracer = (tr as SentryTracer);
464+
var span = tracer.children.first;
465+
466+
expect(tracer.children.length, 2);
467+
468+
expect(span.status, SpanStatus.ok());
469+
expect(span.finished, true);
470+
expect(span.context.operation, 'file.read');
471+
expect(
472+
span.context.description,
473+
'AssetBundle.loadStructuredBinaryData<String>: test.txt',
474+
);
475+
476+
span = tracer.children[1];
477+
478+
expect(span.status, SpanStatus.ok());
479+
expect(span.finished, true);
480+
expect(span.context.operation, 'serialize.file.read');
481+
expect(
482+
span.context.description,
483+
'parsing "resources/test.txt" to "String"',
484+
);
485+
},
486+
);
487+
336488
test(
337489
'evict call gets forwarded',
338490
() {

0 commit comments

Comments
 (0)