Skip to content

Commit

Permalink
use yield for items in envelope, remove length closure from header, s…
Browse files Browse the repository at this point in the history
…kip throwing envelope data closure
  • Loading branch information
denrase committed Nov 19, 2024
1 parent dbc646a commit 2532dbf
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 207 deletions.
31 changes: 17 additions & 14 deletions dart/lib/src/sentry_envelope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,23 +122,26 @@ class SentryEnvelope {

final newLineData = utf8.encode('\n');
for (final item in items) {
final length = await item.header.length();
// A length smaller than 0 indicates an invalid envelope, which should not
// be send to Sentry.io
if (length < 0) {
continue;
}
// Only attachments should be filtered according to
// SentryOptions.maxAttachmentSize
if (item.header.type == SentryItemType.attachment) {
if (await item.header.length() > options.maxAttachmentSize) {
try {
final data = await item.dataFactory();

// Only attachments should be filtered according to
// SentryOptions.maxAttachmentSize
if (item.header.type == SentryItemType.attachment &&
data.length > options.maxAttachmentSize) {
continue;
}
}
final itemStream = await item.envelopeItemStream();
if (itemStream.isNotEmpty) {

yield newLineData;
yield utf8.encode(jsonEncode(await item.header.toJson(data.length)));
yield newLineData;
yield itemStream;
yield data;
} catch (_) {
if (options.automatedTestMode) {
rethrow;
}
// Skip throwing envelope item data closure.
continue;
}
}
}
Expand Down
102 changes: 22 additions & 80 deletions dart/lib/src/sentry_envelope_item.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

import 'client_reports/client_report.dart';
import 'metrics/metric.dart';
Expand All @@ -20,82 +19,72 @@ class SentryEnvelopeItem {

/// Creates a [SentryEnvelopeItem] which sends [SentryTransaction].
factory SentryEnvelopeItem.fromTransaction(SentryTransaction transaction) {
final cachedItem =
_CachedItem(() async => utf8JsonEncoder.convert(transaction.toJson()));

final header = SentryEnvelopeItemHeader(
SentryItemType.transaction,
cachedItem.getDataLength,
contentType: 'application/json',
);
return SentryEnvelopeItem(header, cachedItem.getData,
return SentryEnvelopeItem(
header, () async => utf8JsonEncoder.convert(transaction.toJson()),
originalObject: transaction);
}

factory SentryEnvelopeItem.fromAttachment(SentryAttachment attachment) {
final cachedItem = _CachedItem(() async => attachment.bytes);

final header = SentryEnvelopeItemHeader(
SentryItemType.attachment,
cachedItem.getDataLength,
contentType: attachment.contentType,
fileName: attachment.filename,
attachmentType: attachment.attachmentType,
);
return SentryEnvelopeItem(header, cachedItem.getData,
originalObject: attachment);
return SentryEnvelopeItem(
header,
() async => attachment.bytes,
originalObject: attachment,
);
}

/// Create a [SentryEnvelopeItem] which sends [SentryUserFeedback].
@Deprecated('Will be removed in a future version.')
factory SentryEnvelopeItem.fromUserFeedback(SentryUserFeedback feedback) {
final cachedItem =
_CachedItem(() async => utf8JsonEncoder.convert(feedback.toJson()));
final dataFactory = () async => utf8JsonEncoder.convert(feedback.toJson());

final header = SentryEnvelopeItemHeader(
SentryItemType.userFeedback,
cachedItem.getDataLength,
contentType: 'application/json',
);
return SentryEnvelopeItem(header, cachedItem.getData,
originalObject: feedback);
return SentryEnvelopeItem(
header,
dataFactory,
originalObject: feedback,
);
}

/// Create a [SentryEnvelopeItem] which holds the [SentryEvent] data.
factory SentryEnvelopeItem.fromEvent(SentryEvent event) {
final cachedItem =
_CachedItem(() async => utf8JsonEncoder.convert(event.toJson()));

return SentryEnvelopeItem(
SentryEnvelopeItemHeader(
event.type == 'feedback' ? 'feedback' : SentryItemType.event,
cachedItem.getDataLength,
contentType: 'application/json',
),
cachedItem.getData,
() async => utf8JsonEncoder.convert(event.toJson()),
originalObject: event,
);
}

/// Create a [SentryEnvelopeItem] which holds the [ClientReport] data.
factory SentryEnvelopeItem.fromClientReport(ClientReport clientReport) {
final cachedItem =
_CachedItem(() async => utf8JsonEncoder.convert(clientReport.toJson()));

return SentryEnvelopeItem(
SentryEnvelopeItemHeader(
SentryItemType.clientReport,
cachedItem.getDataLength,
contentType: 'application/json',
),
cachedItem.getData,
() async => utf8JsonEncoder.convert(clientReport.toJson()),
originalObject: clientReport,
);
}

/// Creates a [SentryEnvelopeItem] which holds several [Metric] data.
factory SentryEnvelopeItem.fromMetrics(Map<int, Iterable<Metric>> buckets) {
final cachedItem = _CachedItem(() async {
final dataFactory = () async {
final statsd = StringBuffer();
// Encode all metrics of a bucket in statsd format, using the bucket key,
// which is the timestamp of the bucket.
Expand All @@ -105,68 +94,21 @@ class SentryEnvelopeItem {
statsd.write(encodedMetrics.join('\n'));
}
return utf8.encode(statsd.toString());
});

};
final header = SentryEnvelopeItemHeader(
SentryItemType.statsd,
cachedItem.getDataLength,
contentType: 'application/octet-stream',
);
return SentryEnvelopeItem(header, cachedItem.getData,
originalObject: buckets);
return SentryEnvelopeItem(
header,
dataFactory,
originalObject: buckets,
);
}

/// Header with info about type and length of data in bytes.
final SentryEnvelopeItemHeader header;

/// Create binary data representation of item data.
final Future<List<int>> Function() dataFactory;

/// Stream binary data of `Envelope` item.
Future<List<int>> envelopeItemStream() async {
// Each item needs to be encoded as one unit.
// Otherwise the header already got yielded if the content throws
// an exception.
try {
final itemHeader = utf8JsonEncoder.convert(await header.toJson());
final newLine = utf8.encode('\n');
final data = await dataFactory();

final totalLength = itemHeader.length + newLine.length + data.length;

final result = Uint8List(totalLength);
result.setRange(0, itemHeader.length, itemHeader);
result.setRange(
itemHeader.length,
itemHeader.length + newLine.length,
newLine,
);
result.setRange(itemHeader.length + newLine.length, totalLength, data);

return result;
} catch (e) {
// TODO rethrow in options.automatedTestMode (currently not available here to check)
return [];
}
}
}

class _CachedItem {
_CachedItem(this._dataFactory);

final Future<List<int>> Function() _dataFactory;
List<int>? _data;

Future<List<int>> getData() async {
_data ??= await _dataFactory();
return _data!;
}

Future<int> getDataLength() async {
try {
return (await getData()).length;
} catch (_) {
return -1;
}
}
}
16 changes: 4 additions & 12 deletions dart/lib/src/sentry_envelope_item_header.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/// Header with item info about type and length of data in bytes.
class SentryEnvelopeItemHeader {
SentryEnvelopeItemHeader(
this.type,
this.length, {
this.type, {
this.contentType,
this.fileName,
this.attachmentType,
Expand All @@ -11,27 +10,20 @@ class SentryEnvelopeItemHeader {
/// Type of encoded data.
final String type;

/// The number of bytes of the encoded item JSON.
/// A negative number indicates an invalid envelope which should not be send
/// to Sentry.io.
final Future<int> Function() length;

final String? contentType;

final String? fileName;

final String? attachmentType;

/// Item header encoded as JSON
Future<Map<String, dynamic>> toJson() async {
final json = <String, dynamic>{
Future<Map<String, dynamic>> toJson(int length) async {
return {
if (contentType != null) 'content_type': contentType,
if (fileName != null) 'filename': fileName,
if (attachmentType != null) 'attachment_type': attachmentType,
'type': type,
'length': await length(),
'length': length,
};

return json;
}
}
6 changes: 3 additions & 3 deletions dart/test/mocks/mock_transport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ class MockTransport implements Transport {
}

Future<void> _eventFromEnvelope(SentryEnvelope envelope) async {
final envelopeItemData = <int>[];
final RegExp statSdRegex = RegExp('^(?!{).+@.+:.+\\|.+', multiLine: true);
envelopeItemData.addAll(await envelope.items.first.envelopeItemStream());

final envelopeItem = utf8.decode(envelopeItemData).split('\n').last;
final envelopeItemData = await envelope.items.first.dataFactory();
final envelopeItem = utf8.decode(envelopeItemData);

if (statSdRegex.hasMatch(envelopeItem)) {
statsdItems.add(envelopeItem);
} else {
Expand Down
2 changes: 1 addition & 1 deletion dart/test/sentry_attachment_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ void main() {
'test.txt',
);
await expectLater(
await attachmentEnvelope.header.length(),
(await attachmentEnvelope.dataFactory()).length,
4,
);
});
Expand Down
16 changes: 6 additions & 10 deletions dart/test/sentry_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2197,21 +2197,17 @@ void main() {
}

Future<SentryEvent> eventFromEnvelope(SentryEnvelope envelope) async {
final envelopeItemData = <int>[];
envelopeItemData.addAll(await envelope.items.first.envelopeItemStream());

final envelopeItem = utf8.decode(envelopeItemData);
final envelopeItemJson = jsonDecode(envelopeItem.split('\n').last);
final data = await envelope.items.first.dataFactory();
final utf8Data = utf8.decode(data);
final envelopeItemJson = jsonDecode(utf8Data);
return SentryEvent.fromJson(envelopeItemJson as Map<String, dynamic>);
}

Future<Map<String, dynamic>> transactionFromEnvelope(
SentryEnvelope envelope) async {
final envelopeItemData = <int>[];
envelopeItemData.addAll(await envelope.items.first.envelopeItemStream());

final envelopeItem = utf8.decode(envelopeItemData);
final envelopeItemJson = jsonDecode(envelopeItem.split('\n').last);
final data = await envelope.items.first.dataFactory();
final utf8Data = utf8.decode(data);
final envelopeItemJson = jsonDecode(utf8Data);
return envelopeItemJson as Map<String, dynamic>;
}

Expand Down
7 changes: 3 additions & 4 deletions dart/test/sentry_envelope_item_header_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import 'package:test/test.dart';
void main() {
group('SentryEnvelopeItemHeader', () {
test('serialize', () async {
final sut = SentryEnvelopeItemHeader(SentryItemType.event, () async {
return 3;
}, contentType: 'application/json');
final sut = SentryEnvelopeItemHeader(SentryItemType.event,
contentType: 'application/json');
final expected = <String, dynamic>{
'content_type': 'application/json',
'type': 'event',
'length': 3
};
expect(await sut.toJson(), expected);
expect(await sut.toJson(3), expected);
});
});
}
Loading

0 comments on commit 2532dbf

Please sign in to comment.