Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
implement loadStructuredBinaryData
  • Loading branch information
arnemolland committed Feb 27, 2023
commit 634d4507814aa3bb1f68896a80e21231808a136c
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Implement `loadStructuredBinaryData` from updated AssetBundle ([#1272](https://github.com/getsentry/sentry-dart/pull/1272))

### Dependencies

- Bump Android SDK from v6.13.0 to v6.13.1 ([#1273](https://github.com/getsentry/sentry-dart/pull/1273))
Expand Down
104 changes: 100 additions & 4 deletions flutter/lib/src/sentry_asset_bundle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:sentry/sentry.dart';

typedef _Parser<T> = Future<T> Function(String value);
typedef _StringParser<T> = Future<T> Function(String value);
typedef _ByteParser<T> = FutureOr<T> Function(ByteData value);

/// An [AssetBundle] which creates automatic performance traces for loading
/// assets.
Expand Down Expand Up @@ -79,15 +80,15 @@ class SentryAssetBundle implements AssetBundle {
}

@override
Future<T> loadStructuredData<T>(String key, _Parser<T> parser) {
Future<T> loadStructuredData<T>(String key, _StringParser<T> parser) {
if (_enableStructuredDataTracing) {
return _loadStructuredDataWithTracing(key, parser);
}
return _bundle.loadStructuredData(key, parser);
}

Future<T> _loadStructuredDataWithTracing<T>(
String key, _Parser<T> parser) async {
String key, _StringParser<T> parser) async {
final span = _hub.getSpan()?.startChild(
'file.read',
description: 'AssetBundle.loadStructuredData<$T>: ${_fileName(key)}',
Expand Down Expand Up @@ -125,6 +126,46 @@ class SentryAssetBundle implements AssetBundle {
return data;
}

FutureOr<T> _loadStructuredBinaryDataWithTracing<T>(
String key, _ByteParser<T> parser) async {
final span = _hub.getSpan()?.startChild(
'file.read',
description:
'AssetBundle.loadStructuredBinaryData<$T>: ${_fileName(key)}',
);
span?.setData('file.path', key);

final completer = Completer<T>();

// This future is intentionally not awaited. Otherwise we deadlock with
// the completer.
// ignore: unawaited_futures
runZonedGuarded(() async {
final data = await _loadStructuredBinaryDataWrapper(
key,
(value) async => await _wrapBinaryParsing(parser, value, key, span),
);
span?.status = SpanStatus.ok();
completer.complete(data);
}, (exception, stackTrace) {
completer.completeError(exception, stackTrace);
});

T data;
try {
data = await completer.future;
_setDataLength(data, span);
span?.status = const SpanStatus.ok();
} catch (e) {
span?.throwable = e;
span?.status = const SpanStatus.internalError();
rethrow;
} finally {
await span?.finish();
}
return data;
}

@override
Future<String> loadString(String key, {bool cache = true}) async {
final span = _hub.getSpan()?.startChild(
Expand Down Expand Up @@ -236,7 +277,7 @@ class SentryAssetBundle implements AssetBundle {
}

static Future<T> _wrapParsing<T>(
_Parser<T> parser,
_StringParser<T> parser,
String value,
String key,
ISentrySpan? outerSpan,
Expand All @@ -259,4 +300,59 @@ class SentryAssetBundle implements AssetBundle {

return data;
}

static FutureOr<T> _wrapBinaryParsing<T>(
_ByteParser<T> parser,
ByteData value,
String key,
ISentrySpan? outerSpan,
) async {
final span = outerSpan?.startChild(
'serialize.file.read',
description: 'parsing "$key" to "$T"',
);
T data;
try {
data = await parser(value);
span?.status = const SpanStatus.ok();
} catch (e) {
span?.throwable = e;
span?.status = const SpanStatus.internalError();
rethrow;
} finally {
await span?.finish();
}

return data;
}

@override
// ignore: override_on_non_overriding_member
Future<T> loadStructuredBinaryData<T>(
String key,
FutureOr<T> Function(ByteData data) parser,
) async {
if (_enableStructuredDataTracing) {
return _loadStructuredBinaryDataWithTracing<T>(key, parser);
}

return _loadStructuredBinaryDataWrapper<T>(key, parser);
}

// helper method to have a "typesafe" method
Future<T> _loadStructuredBinaryDataWrapper<T>(
String key,
FutureOr<T> Function(ByteData data) parser,
) async {
// The loadStructuredBinaryData method exists as of Flutter greater than 3.8
// Previous versions don't have it, but later versions do.
// We can't use `extends` in order to provide this method because this is
// a wrapper and thus the method call must be forwarded.
// On Flutter versions <=3.8 we can't forward this call.
// On later version the call gets correctly forwarded.
// The error doesn't need to handled since it can't be called on earlier versions,
// and it's correctly forwarded on later versions.
return (_bundle as dynamic).loadStructuredBinaryData<T>(key, parser)
as Future<T>;
}
}
152 changes: 152 additions & 0 deletions flutter/test/sentry_asset_bundle_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,158 @@ void main() {
},
);

test(
'loadStructuredBinaryData: does not create any spans and just forwords the call to the underlying assetbundle if disabled',
() async {
final sut = fixture.getSut(structuredDataTracing: false);
final tr = fixture._hub.startTransaction(
'name',
'op',
bindToScope: true,
);

final data = await sut.loadStructuredBinaryData<String>(
_testFileName,
(value) async => utf8.decode(
value.buffer.asUint8List(value.offsetInBytes, value.lengthInBytes),
),
);
expect(data, 'Hello World!');

await tr.finish();

final tracer = (tr as SentryTracer);

expect(tracer.children.length, 0);
},
);

test(
'loadStructuredBinaryData: finish with errored span if loading fails',
() async {
final sut = fixture.getSut(throwException: true);
final tr = fixture._hub.startTransaction(
'name',
'op',
bindToScope: true,
);
await expectLater(
sut.loadStructuredBinaryData<String>(
_testFileName,
(value) async => utf8.decode(
value.buffer
.asUint8List(value.offsetInBytes, value.lengthInBytes),
),
),
throwsA(isA<Exception>()),
);

await tr.finish();

final tracer = (tr as SentryTracer);
final span = tracer.children.first;

expect(span.status, SpanStatus.internalError());
expect(span.finished, true);
expect(span.throwable, isA<Exception>());
expect(span.context.operation, 'file.read');
expect(
span.context.description,
'AssetBundle.loadStructuredBinaryData<String>: test.txt',
);
},
);

test(
'loadStructuredBinaryData: finish with errored span if parsing fails',
() async {
final sut = fixture.getSut(throwException: false);
final tr = fixture._hub.startTransaction(
'name',
'op',
bindToScope: true,
);
await expectLater(
sut.loadStructuredBinaryData<String>(
_testFileName,
(value) async => throw Exception('error while parsing'),
),
throwsA(isA<Exception>()),
);

await tr.finish();

final tracer = (tr as SentryTracer);
var span = tracer.children.first;

expect(tracer.children.length, 2);

expect(span.status, SpanStatus.internalError());
expect(span.finished, true);
expect(span.throwable, isA<Exception>());
expect(span.context.operation, 'file.read');
expect(
span.context.description,
'AssetBundle.loadStructuredBinaryData<String>: test.txt',
);

span = tracer.children[1];

expect(span.status, SpanStatus.internalError());
expect(span.finished, true);
expect(span.throwable, isA<Exception>());
expect(span.context.operation, 'serialize.file.read');
expect(
span.context.description,
'parsing "resources/test.txt" to "String"',
);
},
);

test(
'loadStructuredBinaryData: finish with successfully',
() async {
final sut = fixture.getSut(throwException: false);
final tr = fixture._hub.startTransaction(
'name',
'op',
bindToScope: true,
);

await sut.loadStructuredBinaryData<String>(
_testFileName,
(value) async => utf8.decode(
value.buffer.asUint8List(value.offsetInBytes, value.lengthInBytes),
),
);

await tr.finish();

final tracer = (tr as SentryTracer);
var span = tracer.children.first;

expect(tracer.children.length, 2);

expect(span.status, SpanStatus.ok());
expect(span.finished, true);
expect(span.context.operation, 'file.read');
expect(
span.context.description,
'AssetBundle.loadStructuredBinaryData<String>: test.txt',
);

span = tracer.children[1];

expect(span.status, SpanStatus.ok());
expect(span.finished, true);
expect(span.context.operation, 'serialize.file.read');
expect(
span.context.description,
'parsing "resources/test.txt" to "String"',
);
},
);

test(
'evict call gets forwarded',
() {
Expand Down