From 3d3d635124df96816c5682e2b525f4f775529453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Sun, 20 Jun 2021 16:59:09 +0200 Subject: [PATCH 01/20] Attachment support --- CHANGELOG.md | 2 + dart/lib/attachment.dart | 64 +++++++++++++++++++ dart/lib/sentry.dart | 1 + dart/lib/src/scope.dart | 14 +++- .../_io_scope_extensions.dart | 38 +++++++++++ .../_web_scope_extensions.dart | 7 ++ .../scope_extensions/scope_extensions.dart | 51 +++++++++++++++ dart/lib/src/sentry_client.dart | 11 +++- dart/lib/src/sentry_envelope.dart | 17 ++++- dart/lib/src/sentry_envelope_item.dart | 32 +++++++--- dart/lib/src/sentry_envelope_item_header.dart | 28 ++++---- flutter/example/lib/main.dart | 13 ++++ 12 files changed, 252 insertions(+), 26 deletions(-) create mode 100644 dart/lib/attachment.dart create mode 100644 dart/lib/src/scope_extensions/_io_scope_extensions.dart create mode 100644 dart/lib/src/scope_extensions/_web_scope_extensions.dart create mode 100644 dart/lib/src/scope_extensions/scope_extensions.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 70fc7d470c..c18cf6d8ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ * Feat: Add Culture Context (#491) * Feat: Add DeduplicationEventProcessor (#498) * Feat: Capture failed requests as event (#473) +* Feat: `beforeSend` callback accepts async code (#494) +* Feat: Add support for attachments (#494) ## Breaking Changes: diff --git a/dart/lib/attachment.dart b/dart/lib/attachment.dart new file mode 100644 index 0000000000..717bc9b3d5 --- /dev/null +++ b/dart/lib/attachment.dart @@ -0,0 +1,64 @@ +import 'dart:typed_data'; + +// https://develop.sentry.dev/sdk/envelopes/#attachment +class Attachment { + Attachment({ + required this.content, + required this.fileName, + AttachmentType? type, + String? mimeType, + }) : mimeType = mimeType ?? 'application/octet-stream', + type = type ?? AttachmentType.attachment; + + /// Attachment type. + final AttachmentType type; + + /// Attachment content. + final Uint8List content; + + /// Attachment file name. + final String fileName; + + /// Attachment content type. + final String mimeType; +} + +/// Attachment type. +enum AttachmentType { + /// Standard attachment without special meaning. + attachment, + + /// Minidump file that creates an error event and is symbolicated. + /// The file should start with the MDMP magic bytes. + minidump, + + /// Apple crash report file that creates an error event and is symbolicated. + appleCrashReport, + + /// XML file containing UE4 crash meta data. + /// During event ingestion, event contexts and extra fields are extracted from + /// this file. + unrealContext, + + /// Plain-text log file obtained from UE4 crashes. + /// During event ingestion, the last logs are extracted into event + /// breadcrumbs. + unrealLogs +} + +extension AttachmentTypeX on AttachmentType { + String toSentryIdentifier() { + switch (this) { + case AttachmentType.attachment: + return 'event.attachment'; + case AttachmentType.minidump: + return 'event.minidump'; + case AttachmentType.appleCrashReport: + return 'event.applecrashreport'; + case AttachmentType.unrealContext: + return 'unreal.context'; + case AttachmentType.unrealLogs: + return 'unreal.logs'; + } + } +} diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index d358cea613..bc01773e28 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -22,3 +22,4 @@ export 'src/transport/transport.dart'; export 'src/integration.dart'; export 'src/event_processor.dart'; export 'src/http_client/sentry_http_client.dart'; +export 'src/scope_extensions/scope_extensions.dart'; diff --git a/dart/lib/src/scope.dart b/dart/lib/src/scope.dart index bd78b14921..6e26d8af86 100644 --- a/dart/lib/src/scope.dart +++ b/dart/lib/src/scope.dart @@ -1,5 +1,5 @@ import 'dart:collection'; - +import '../attachment.dart'; import 'event_processor.dart'; import 'protocol.dart'; import 'sentry_options.dart'; @@ -81,6 +81,10 @@ class Scope { final SentryOptions _options; + final List _attachements = []; + + List get attachements => UnmodifiableListView(_attachements); + Scope(this._options); /// Adds a breadcrumb to the breadcrumbs queue @@ -116,6 +120,10 @@ class Scope { _breadcrumbs.add(breadcrumb); } + void addAttachment(Attachment attachment) async { + _attachements.add(attachment); + } + /// Clear all the breadcrumbs void clearBreadcrumbs() { _breadcrumbs.clear(); @@ -285,6 +293,10 @@ class Scope { } }); + for (final attachment in _attachements) { + clone._attachements.add(attachment); + } + return clone; } } diff --git a/dart/lib/src/scope_extensions/_io_scope_extensions.dart b/dart/lib/src/scope_extensions/_io_scope_extensions.dart new file mode 100644 index 0000000000..40c7ed5826 --- /dev/null +++ b/dart/lib/src/scope_extensions/_io_scope_extensions.dart @@ -0,0 +1,38 @@ +import 'dart:io'; + +import '../../attachment.dart'; +import '../scope.dart'; + +extension IoScopeExtensions on Scope { + Future addAttachementPath( + String path, { + AttachmentType? type, + String? mimeType, + }) async { + final file = File(path); + + addAttachment( + Attachment( + type: type, + content: await file.readAsBytes(), + fileName: file.uri.pathSegments.last, + mimeType: mimeType, + ), + ); + } + + Future addAttachementFile( + File file, { + AttachmentType? type, + String? mimeType, + }) async { + addAttachment( + Attachment( + type: type, + content: await file.readAsBytes(), + fileName: file.uri.pathSegments.last, + mimeType: mimeType, + ), + ); + } +} diff --git a/dart/lib/src/scope_extensions/_web_scope_extensions.dart b/dart/lib/src/scope_extensions/_web_scope_extensions.dart new file mode 100644 index 0000000000..abc51e5117 --- /dev/null +++ b/dart/lib/src/scope_extensions/_web_scope_extensions.dart @@ -0,0 +1,7 @@ +import '../scope.dart'; + +extension IoScopeExtensions on Scope { + Future addAttachementPath(String path) async { + // no op + } +} diff --git a/dart/lib/src/scope_extensions/scope_extensions.dart b/dart/lib/src/scope_extensions/scope_extensions.dart new file mode 100644 index 0000000000..e78de25e5e --- /dev/null +++ b/dart/lib/src/scope_extensions/scope_extensions.dart @@ -0,0 +1,51 @@ +export '_io_scope_extensions.dart' + if (dart.library.html) '_web_scope_extensions.dart'; + +import 'dart:typed_data'; + +import '../../attachment.dart'; +import '../scope.dart'; + +extension ScopeExtensions on Scope { + void addAttachmentBytes( + Uint8List bytes, + String fileName, { + String? mimeType, + AttachmentType? type, + }) { + addAttachment(Attachment( + type: type, + content: bytes, + fileName: fileName, + mimeType: mimeType, + )); + } + + void addAttachmentIntList( + List bytes, + String fileName, { + String? mimeType, + AttachmentType? type, + }) { + addAttachment(Attachment( + type: type, + content: Uint8List.fromList(bytes), + fileName: fileName, + mimeType: mimeType, + )); + } + + void addAttachmentByteData( + ByteData bytes, + String fileName, { + String? mimeType, + AttachmentType? type, + }) { + addAttachment(Attachment( + type: type, + content: bytes.buffer.asUint8List(), + fileName: fileName, + mimeType: mimeType, + )); + } +} diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 2ca151d0e1..834d8a633d 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -108,8 +108,13 @@ class SentryClient { return _sentryId; } } - final envelope = SentryEnvelope.fromEvent(preparedEvent, _options.sdk); - return await _options.transport.send(envelope); + final envelope = SentryEnvelope.fromEvent( + preparedEvent, + _options.sdk, + attachments: scope?.attachements, + ); + + return captureEnvelope(envelope); } SentryEvent _prepareEvent(SentryEvent event, {dynamic stackTrace}) { @@ -201,7 +206,7 @@ class SentryClient { } /// Reports the [envelope] to Sentry.io. - Future captureEnvelope(SentryEnvelope envelope) { + Future captureEnvelope(SentryEnvelope envelope) { return _options.transport.send(envelope); } diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index 18a1835231..b8cc16aca5 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import '../attachment.dart'; import 'sentry_envelope_header.dart'; import 'sentry_envelope_item.dart'; import 'protocol/sentry_event.dart'; @@ -16,9 +17,19 @@ class SentryEnvelope { final List items; /// Create an `SentryEnvelope` with containing one `SentryEnvelopeItem` which holds the `SentyEvent` data. - factory SentryEnvelope.fromEvent(SentryEvent event, SdkVersion sdkVersion) { - return SentryEnvelope(SentryEnvelopeHeader(event.eventId, sdkVersion), - [SentryEnvelopeItem.fromEvent(event)]); + factory SentryEnvelope.fromEvent( + SentryEvent event, + SdkVersion sdkVersion, { + List? attachments, + }) { + return SentryEnvelope( + SentryEnvelopeHeader(event.eventId, sdkVersion), + [ + SentryEnvelopeItem.fromEvent(event), + if (attachments != null) + ...attachments.map((e) => SentryEnvelopeItem.fromAttachment(e)) + ], + ); } /// Stream binary data representation of `Envelope` file encoded. diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index 5c00a4e1e6..be0d6ced24 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import '../attachment.dart'; import 'sentry_item_type.dart'; import 'protocol/sentry_event.dart'; import 'sentry_envelope_item_header.dart'; @@ -8,11 +9,16 @@ import 'sentry_envelope_item_header.dart'; class SentryEnvelopeItem { SentryEnvelopeItem(this.header, this.dataFactory); - /// Header with info about type and length of data in bytes. - final SentryEnvelopeItemHeader header; - - /// Create binary data representation of item data. - final Future> Function() dataFactory; + factory SentryEnvelopeItem.fromAttachment(Attachment attachment) { + final header = SentryEnvelopeItemHeader( + SentryItemType.attachment, + () async => attachment.content.lengthInBytes, + contentType: attachment.mimeType, + fileName: attachment.fileName, + attachmentType: attachment.type.toSentryIdentifier(), + ); + return SentryEnvelopeItem(header, () async => attachment.content); + } /// Create an `SentryEnvelopeItem` which holds the `SentyEvent` data. factory SentryEnvelopeItem.fromEvent(SentryEvent event) { @@ -26,11 +32,21 @@ class SentryEnvelopeItem { }; return SentryEnvelopeItem( - SentryEnvelopeItemHeader(SentryItemType.event, getLength, - contentType: 'application/json'), - cachedItem.getData); + SentryEnvelopeItemHeader( + SentryItemType.event, + getLength, + contentType: 'application/json', + ), + cachedItem.getData, + ); } + /// Header with info about type and length of data in bytes. + final SentryEnvelopeItemHeader header; + + /// Create binary data representation of item data. + final Future> Function() dataFactory; + /// Stream binary data of `Envelope` item. Stream> envelopeItemStream() async* { yield utf8.encode(jsonEncode(await header.toJson())); diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index 013a62d35b..db1abc9bbd 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -1,7 +1,12 @@ /// Header with item info about type and length of data in bytes. class SentryEnvelopeItemHeader { - SentryEnvelopeItemHeader(this.type, this.length, - {this.contentType, this.fileName}); + SentryEnvelopeItemHeader( + this.type, + this.length, { + this.contentType, + this.fileName, + this.attachmentType, + }); /// Type of encoded data. final String type; @@ -13,17 +18,18 @@ class SentryEnvelopeItemHeader { final String? fileName; + final String? attachmentType; + /// Item header encoded as JSON Future> toJson() async { - final json = {}; - if (contentType != null) { - json['content_type'] = contentType; - } - if (fileName != null) { - json['filename'] = fileName; - } - json['type'] = type; - json['length'] = await length(); + final json = { + if (contentType != null) 'content_type': contentType, + if (fileName != null) 'filename': fileName, + if (attachmentType != null) 'attachment_type': type, + 'type': type, + 'length': await length(), + }; + return json; } } diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 8fcbdc36f8..2b2180a3d8 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -128,6 +129,18 @@ class MainScaffold extends StatelessWidget { ); }, ), + RaisedButton( + child: const Text('Capture message with attachment'), + onPressed: () { + Sentry.captureMessage( + 'This message has an attachment', + withScope: (scope) { + final txt = 'Lorem Ipsum dolar sit amet'; + scope.addAttachmentIntList(utf8.encode(txt), 'foobar.txt'); + }, + ); + }, + ), if (UniversalPlatform.isIOS || UniversalPlatform.isMacOS) const CocoaExample(), if (UniversalPlatform.isAndroid) const AndroidExample(), From 9ecad73ae224a2ca75555a9d2eea7e9c7fcd6619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Sun, 20 Jun 2021 20:07:29 +0200 Subject: [PATCH 02/20] fix pr id --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c18cf6d8ca..875d8f56e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ * Feat: Add DeduplicationEventProcessor (#498) * Feat: Capture failed requests as event (#473) * Feat: `beforeSend` callback accepts async code (#494) -* Feat: Add support for attachments (#494) +* Feat: Add support for attachments (#505) ## Breaking Changes: From 901f3b45c15bfd85f8bc18e9769ecd3c630ac89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Mon, 21 Jun 2021 18:11:55 +0200 Subject: [PATCH 03/20] wip --- dart/lib/sentry.dart | 2 +- dart/lib/{ => src}/attachment.dart | 48 +++++++++++++++++ .../_io_attachment_extensions.dart | 40 +++++++++++++++ .../_web_attachment_extensions.dart | 3 ++ .../attachment_extensions.dart | 2 + dart/lib/src/scope.dart | 2 +- .../_io_scope_extensions.dart | 38 -------------- .../_web_scope_extensions.dart | 7 --- .../scope_extensions/scope_extensions.dart | 51 ------------------- dart/lib/src/sentry_envelope.dart | 2 +- dart/lib/src/sentry_envelope_item.dart | 2 +- flutter/example/lib/main.dart | 7 ++- flutter/lib/sentry_flutter.dart | 1 + flutter/lib/src/attachment_extensions.dart | 30 +++++++++++ 14 files changed, 134 insertions(+), 101 deletions(-) rename dart/lib/{ => src}/attachment.dart (61%) create mode 100644 dart/lib/src/attachment_extensions/_io_attachment_extensions.dart create mode 100644 dart/lib/src/attachment_extensions/_web_attachment_extensions.dart create mode 100644 dart/lib/src/attachment_extensions/attachment_extensions.dart delete mode 100644 dart/lib/src/scope_extensions/_io_scope_extensions.dart delete mode 100644 dart/lib/src/scope_extensions/_web_scope_extensions.dart delete mode 100644 dart/lib/src/scope_extensions/scope_extensions.dart create mode 100644 flutter/lib/src/attachment_extensions.dart diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index bc01773e28..50646e1055 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -22,4 +22,4 @@ export 'src/transport/transport.dart'; export 'src/integration.dart'; export 'src/event_processor.dart'; export 'src/http_client/sentry_http_client.dart'; -export 'src/scope_extensions/scope_extensions.dart'; +export 'src/attachment.dart'; diff --git a/dart/lib/attachment.dart b/dart/lib/src/attachment.dart similarity index 61% rename from dart/lib/attachment.dart rename to dart/lib/src/attachment.dart index 717bc9b3d5..7e95c21a6a 100644 --- a/dart/lib/attachment.dart +++ b/dart/lib/src/attachment.dart @@ -1,6 +1,9 @@ import 'dart:typed_data'; +export 'attachment_extensions/attachment_extensions.dart'; // https://develop.sentry.dev/sdk/envelopes/#attachment + +/// Arbitrary content which gets attached to an event. class Attachment { Attachment({ required this.content, @@ -10,6 +13,51 @@ class Attachment { }) : mimeType = mimeType ?? 'application/octet-stream', type = type ?? AttachmentType.attachment; + /// Creates an [Attachment] from a [Uint8List] + factory Attachment.fromUint8List( + Uint8List bytes, + String fileName, { + String? mimeType, + AttachmentType? type, + }) { + return Attachment( + type: type, + content: bytes, + fileName: fileName, + mimeType: mimeType, + ); + } + + /// Creates an [Attachment] from a [List] + factory Attachment.fromIntList( + List bytes, + String fileName, { + String? mimeType, + AttachmentType? type, + }) { + return Attachment( + type: type, + content: Uint8List.fromList(bytes), + fileName: fileName, + mimeType: mimeType, + ); + } + + /// Creates an [Attachment] from [ByteData] + factory Attachment.fromByteData( + ByteData bytes, + String fileName, { + String? mimeType, + AttachmentType? type, + }) { + return Attachment( + type: type, + content: bytes.buffer.asUint8List(), + fileName: fileName, + mimeType: mimeType, + ); + } + /// Attachment type. final AttachmentType type; diff --git a/dart/lib/src/attachment_extensions/_io_attachment_extensions.dart b/dart/lib/src/attachment_extensions/_io_attachment_extensions.dart new file mode 100644 index 0000000000..8a46d73c39 --- /dev/null +++ b/dart/lib/src/attachment_extensions/_io_attachment_extensions.dart @@ -0,0 +1,40 @@ +import 'dart:io'; + +import '../attachment.dart'; +import '../scope.dart'; + +extension IoScopeExtensions on Scope { + /// Creates an attachment from a given path. + /// Only available on `dart:io` platforms. + /// Not available on web. + static Future fromPath( + String path, { + AttachmentType? type, + String? mimeType, + }) async { + final file = File(path); + + return Attachment( + type: type, + content: await file.readAsBytes(), + fileName: file.uri.pathSegments.last, + mimeType: mimeType, + ); + } + + /// Creates an attachment from a given [File]. + /// Only available on `dart:io` platforms. + /// Not available on web. + static Future fromFile( + File file, { + AttachmentType? type, + String? mimeType, + }) async { + return Attachment( + type: type, + content: await file.readAsBytes(), + fileName: file.uri.pathSegments.last, + mimeType: mimeType, + ); + } +} diff --git a/dart/lib/src/attachment_extensions/_web_attachment_extensions.dart b/dart/lib/src/attachment_extensions/_web_attachment_extensions.dart new file mode 100644 index 0000000000..6479520018 --- /dev/null +++ b/dart/lib/src/attachment_extensions/_web_attachment_extensions.dart @@ -0,0 +1,3 @@ +import '../attachment.dart'; + +extension WebAttachmentExtensions on Attachment {} diff --git a/dart/lib/src/attachment_extensions/attachment_extensions.dart b/dart/lib/src/attachment_extensions/attachment_extensions.dart new file mode 100644 index 0000000000..58b5d73b83 --- /dev/null +++ b/dart/lib/src/attachment_extensions/attachment_extensions.dart @@ -0,0 +1,2 @@ +export '_io_attachment_extensions.dart' + if (dart.library.html) '_web_scope_extensions.dart'; diff --git a/dart/lib/src/scope.dart b/dart/lib/src/scope.dart index 6e26d8af86..aee2f011bd 100644 --- a/dart/lib/src/scope.dart +++ b/dart/lib/src/scope.dart @@ -1,5 +1,5 @@ import 'dart:collection'; -import '../attachment.dart'; +import 'attachment.dart'; import 'event_processor.dart'; import 'protocol.dart'; import 'sentry_options.dart'; diff --git a/dart/lib/src/scope_extensions/_io_scope_extensions.dart b/dart/lib/src/scope_extensions/_io_scope_extensions.dart deleted file mode 100644 index 40c7ed5826..0000000000 --- a/dart/lib/src/scope_extensions/_io_scope_extensions.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'dart:io'; - -import '../../attachment.dart'; -import '../scope.dart'; - -extension IoScopeExtensions on Scope { - Future addAttachementPath( - String path, { - AttachmentType? type, - String? mimeType, - }) async { - final file = File(path); - - addAttachment( - Attachment( - type: type, - content: await file.readAsBytes(), - fileName: file.uri.pathSegments.last, - mimeType: mimeType, - ), - ); - } - - Future addAttachementFile( - File file, { - AttachmentType? type, - String? mimeType, - }) async { - addAttachment( - Attachment( - type: type, - content: await file.readAsBytes(), - fileName: file.uri.pathSegments.last, - mimeType: mimeType, - ), - ); - } -} diff --git a/dart/lib/src/scope_extensions/_web_scope_extensions.dart b/dart/lib/src/scope_extensions/_web_scope_extensions.dart deleted file mode 100644 index abc51e5117..0000000000 --- a/dart/lib/src/scope_extensions/_web_scope_extensions.dart +++ /dev/null @@ -1,7 +0,0 @@ -import '../scope.dart'; - -extension IoScopeExtensions on Scope { - Future addAttachementPath(String path) async { - // no op - } -} diff --git a/dart/lib/src/scope_extensions/scope_extensions.dart b/dart/lib/src/scope_extensions/scope_extensions.dart deleted file mode 100644 index e78de25e5e..0000000000 --- a/dart/lib/src/scope_extensions/scope_extensions.dart +++ /dev/null @@ -1,51 +0,0 @@ -export '_io_scope_extensions.dart' - if (dart.library.html) '_web_scope_extensions.dart'; - -import 'dart:typed_data'; - -import '../../attachment.dart'; -import '../scope.dart'; - -extension ScopeExtensions on Scope { - void addAttachmentBytes( - Uint8List bytes, - String fileName, { - String? mimeType, - AttachmentType? type, - }) { - addAttachment(Attachment( - type: type, - content: bytes, - fileName: fileName, - mimeType: mimeType, - )); - } - - void addAttachmentIntList( - List bytes, - String fileName, { - String? mimeType, - AttachmentType? type, - }) { - addAttachment(Attachment( - type: type, - content: Uint8List.fromList(bytes), - fileName: fileName, - mimeType: mimeType, - )); - } - - void addAttachmentByteData( - ByteData bytes, - String fileName, { - String? mimeType, - AttachmentType? type, - }) { - addAttachment(Attachment( - type: type, - content: bytes.buffer.asUint8List(), - fileName: fileName, - mimeType: mimeType, - )); - } -} diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index b8cc16aca5..f287898f07 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import '../attachment.dart'; +import 'attachment.dart'; import 'sentry_envelope_header.dart'; import 'sentry_envelope_item.dart'; import 'protocol/sentry_event.dart'; diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index be0d6ced24..db9173b995 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import '../attachment.dart'; +import 'attachment.dart'; import 'sentry_item_type.dart'; import 'protocol/sentry_event.dart'; import 'sentry_envelope_item_header.dart'; diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 2b2180a3d8..0a2159b618 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -136,7 +136,12 @@ class MainScaffold extends StatelessWidget { 'This message has an attachment', withScope: (scope) { final txt = 'Lorem Ipsum dolar sit amet'; - scope.addAttachmentIntList(utf8.encode(txt), 'foobar.txt'); + scope.addAttachment( + Attachment.fromIntList( + txt.encode(txt), + 'foobar.txt', + ), + ); }, ); }, diff --git a/flutter/lib/sentry_flutter.dart b/flutter/lib/sentry_flutter.dart index 5fc6546099..7c3666a02f 100644 --- a/flutter/lib/sentry_flutter.dart +++ b/flutter/lib/sentry_flutter.dart @@ -5,3 +5,4 @@ export 'src/default_integrations.dart'; export 'src/navigation/sentry_navigator_observer.dart'; export 'src/sentry_flutter.dart'; export 'src/sentry_flutter_options.dart'; +export 'src/attachment_extensions.dart'; diff --git a/flutter/lib/src/attachment_extensions.dart b/flutter/lib/src/attachment_extensions.dart new file mode 100644 index 0000000000..9f78c026ca --- /dev/null +++ b/flutter/lib/src/attachment_extensions.dart @@ -0,0 +1,30 @@ +import 'package:flutter/services.dart'; +import 'package:sentry/sentry.dart'; + +extension AttachmentExtension on Attachment { + /// Creates an attachment from an asset out of a [AssetBundle]. + /// If no bundle is given, it's using the [rootBundle]. + /// Typically you want to use it like this: + /// ```dart + /// final attachment = Attachment.fromAsset( + /// 'assets/foo_bar.txt', + /// bundle: DefaultAssetBundle.of(context), + /// ); + /// ``` + static Future fromAsset( + String key, { + AssetBundle? bundle, + AttachmentType? type, + String? mimeType, + }) async { + final data = await (bundle ?? rootBundle).load(key); + final fileName = Uri.parse(key).pathSegments.last; + + return Attachment.fromByteData( + data, + fileName, + type: type, + mimeType: mimeType, + ); + } +} From f6db8f156222eeda0adcc9338d7eea0710e98b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Mon, 21 Jun 2021 20:17:38 +0200 Subject: [PATCH 04/20] More examples --- flutter/example/lib/main.dart | 42 ++++++++++++++++++++++++++++++----- flutter/example/pubspec.yaml | 1 + 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 0a2159b618..07a4ff4c17 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:universal_platform/universal_platform.dart'; +import 'package:feedback/feedback.dart' as feedback; // ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io const String _exampleDsn = @@ -34,11 +35,13 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { - return MaterialApp( - navigatorObservers: [ - SentryNavigatorObserver(), - ], - home: const MainScaffold(), + return feedback.BetterFeedback( + child: MaterialApp( + navigatorObservers: [ + SentryNavigatorObserver(), + ], + home: const MainScaffold(), + ), ); } } @@ -138,14 +141,41 @@ class MainScaffold extends StatelessWidget { final txt = 'Lorem Ipsum dolar sit amet'; scope.addAttachment( Attachment.fromIntList( - txt.encode(txt), + utf8.encode(txt), 'foobar.txt', + mimeType: 'text/plain', ), ); }, ); }, ), + RaisedButton( + child: const Text('Capture message with image attachment'), + onPressed: () { + feedback.BetterFeedback.of(context) + .show((feedback.UserFeedback feedback) { + Sentry.captureMessage( + feedback.text, + withScope: (scope) { + final entries = feedback.extra?.entries; + if (entries != null) { + for (final extra in entries) { + scope.setExtra(extra.key, extra.value); + } + } + scope.addAttachment( + Attachment.fromUint8List( + feedback.screenshot, + 'feedback.png', + mimeType: 'image/png', + ), + ); + }, + ); + }); + }, + ), if (UniversalPlatform.isIOS || UniversalPlatform.isMacOS) const CocoaExample(), if (UniversalPlatform.isAndroid) const AndroidExample(), diff --git a/flutter/example/pubspec.yaml b/flutter/example/pubspec.yaml index 9f98b64f39..0f7bf84ad6 100644 --- a/flutter/example/pubspec.yaml +++ b/flutter/example/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: sdk: flutter sentry_flutter: any universal_platform: ^1.0.0-nullsafety + feedback: 2.0.0 dependency_overrides: sentry: From 6468814d63c582ea73f74da9d21a8df79f49242e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Uek=C3=B6tter?= Date: Tue, 22 Jun 2021 14:16:42 +0200 Subject: [PATCH 05/20] Update dart/lib/src/sentry_envelope_item_header.dart Co-authored-by: Manoel Aranda Neto <5731772+marandaneto@users.noreply.github.com> --- dart/lib/src/sentry_envelope_item_header.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dart/lib/src/sentry_envelope_item_header.dart b/dart/lib/src/sentry_envelope_item_header.dart index db1abc9bbd..a69de56c81 100644 --- a/dart/lib/src/sentry_envelope_item_header.dart +++ b/dart/lib/src/sentry_envelope_item_header.dart @@ -25,7 +25,7 @@ class SentryEnvelopeItemHeader { final json = { if (contentType != null) 'content_type': contentType, if (fileName != null) 'filename': fileName, - if (attachmentType != null) 'attachment_type': type, + if (attachmentType != null) 'attachment_type': attachmentType, 'type': type, 'length': await length(), }; From 5e0cde0d1e328a161d559abaebe06de60dd86829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Sat, 26 Jun 2021 17:45:55 +0200 Subject: [PATCH 06/20] WIP --- dart/lib/sentry.dart | 2 +- dart/lib/src/attachment.dart | 112 ------------------ .../_io_attachment_extensions.dart | 40 ------- .../_web_attachment_extensions.dart | 3 - dart/lib/src/scope.dart | 13 +- dart/lib/src/sentry_attachment.dart | 102 ++++++++++++++++ .../_io_attachment_extensions.dart | 42 +++++++ .../_web_attachment_extensions.dart | 3 + .../attachment_extensions.dart | 0 dart/lib/src/sentry_client.dart | 5 +- dart/lib/src/sentry_envelope.dart | 4 +- dart/lib/src/sentry_envelope_item.dart | 22 ++-- flutter/example/lib/main.dart | 8 +- flutter/lib/src/attachment_extensions.dart | 14 +-- 14 files changed, 187 insertions(+), 183 deletions(-) delete mode 100644 dart/lib/src/attachment.dart delete mode 100644 dart/lib/src/attachment_extensions/_io_attachment_extensions.dart delete mode 100644 dart/lib/src/attachment_extensions/_web_attachment_extensions.dart create mode 100644 dart/lib/src/sentry_attachment.dart create mode 100644 dart/lib/src/sentry_attachment_extensions/_io_attachment_extensions.dart create mode 100644 dart/lib/src/sentry_attachment_extensions/_web_attachment_extensions.dart rename dart/lib/src/{attachment_extensions => sentry_attachment_extensions}/attachment_extensions.dart (100%) diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index 50646e1055..59abda21cd 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -22,4 +22,4 @@ export 'src/transport/transport.dart'; export 'src/integration.dart'; export 'src/event_processor.dart'; export 'src/http_client/sentry_http_client.dart'; -export 'src/attachment.dart'; +export 'src/sentry_attachment.dart'; diff --git a/dart/lib/src/attachment.dart b/dart/lib/src/attachment.dart deleted file mode 100644 index 7e95c21a6a..0000000000 --- a/dart/lib/src/attachment.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'dart:typed_data'; -export 'attachment_extensions/attachment_extensions.dart'; - -// https://develop.sentry.dev/sdk/envelopes/#attachment - -/// Arbitrary content which gets attached to an event. -class Attachment { - Attachment({ - required this.content, - required this.fileName, - AttachmentType? type, - String? mimeType, - }) : mimeType = mimeType ?? 'application/octet-stream', - type = type ?? AttachmentType.attachment; - - /// Creates an [Attachment] from a [Uint8List] - factory Attachment.fromUint8List( - Uint8List bytes, - String fileName, { - String? mimeType, - AttachmentType? type, - }) { - return Attachment( - type: type, - content: bytes, - fileName: fileName, - mimeType: mimeType, - ); - } - - /// Creates an [Attachment] from a [List] - factory Attachment.fromIntList( - List bytes, - String fileName, { - String? mimeType, - AttachmentType? type, - }) { - return Attachment( - type: type, - content: Uint8List.fromList(bytes), - fileName: fileName, - mimeType: mimeType, - ); - } - - /// Creates an [Attachment] from [ByteData] - factory Attachment.fromByteData( - ByteData bytes, - String fileName, { - String? mimeType, - AttachmentType? type, - }) { - return Attachment( - type: type, - content: bytes.buffer.asUint8List(), - fileName: fileName, - mimeType: mimeType, - ); - } - - /// Attachment type. - final AttachmentType type; - - /// Attachment content. - final Uint8List content; - - /// Attachment file name. - final String fileName; - - /// Attachment content type. - final String mimeType; -} - -/// Attachment type. -enum AttachmentType { - /// Standard attachment without special meaning. - attachment, - - /// Minidump file that creates an error event and is symbolicated. - /// The file should start with the MDMP magic bytes. - minidump, - - /// Apple crash report file that creates an error event and is symbolicated. - appleCrashReport, - - /// XML file containing UE4 crash meta data. - /// During event ingestion, event contexts and extra fields are extracted from - /// this file. - unrealContext, - - /// Plain-text log file obtained from UE4 crashes. - /// During event ingestion, the last logs are extracted into event - /// breadcrumbs. - unrealLogs -} - -extension AttachmentTypeX on AttachmentType { - String toSentryIdentifier() { - switch (this) { - case AttachmentType.attachment: - return 'event.attachment'; - case AttachmentType.minidump: - return 'event.minidump'; - case AttachmentType.appleCrashReport: - return 'event.applecrashreport'; - case AttachmentType.unrealContext: - return 'unreal.context'; - case AttachmentType.unrealLogs: - return 'unreal.logs'; - } - } -} diff --git a/dart/lib/src/attachment_extensions/_io_attachment_extensions.dart b/dart/lib/src/attachment_extensions/_io_attachment_extensions.dart deleted file mode 100644 index 8a46d73c39..0000000000 --- a/dart/lib/src/attachment_extensions/_io_attachment_extensions.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'dart:io'; - -import '../attachment.dart'; -import '../scope.dart'; - -extension IoScopeExtensions on Scope { - /// Creates an attachment from a given path. - /// Only available on `dart:io` platforms. - /// Not available on web. - static Future fromPath( - String path, { - AttachmentType? type, - String? mimeType, - }) async { - final file = File(path); - - return Attachment( - type: type, - content: await file.readAsBytes(), - fileName: file.uri.pathSegments.last, - mimeType: mimeType, - ); - } - - /// Creates an attachment from a given [File]. - /// Only available on `dart:io` platforms. - /// Not available on web. - static Future fromFile( - File file, { - AttachmentType? type, - String? mimeType, - }) async { - return Attachment( - type: type, - content: await file.readAsBytes(), - fileName: file.uri.pathSegments.last, - mimeType: mimeType, - ); - } -} diff --git a/dart/lib/src/attachment_extensions/_web_attachment_extensions.dart b/dart/lib/src/attachment_extensions/_web_attachment_extensions.dart deleted file mode 100644 index 6479520018..0000000000 --- a/dart/lib/src/attachment_extensions/_web_attachment_extensions.dart +++ /dev/null @@ -1,3 +0,0 @@ -import '../attachment.dart'; - -extension WebAttachmentExtensions on Attachment {} diff --git a/dart/lib/src/scope.dart b/dart/lib/src/scope.dart index aee2f011bd..bcff9506f5 100644 --- a/dart/lib/src/scope.dart +++ b/dart/lib/src/scope.dart @@ -1,5 +1,5 @@ import 'dart:collection'; -import 'attachment.dart'; +import 'sentry_attachment.dart'; import 'event_processor.dart'; import 'protocol.dart'; import 'sentry_options.dart'; @@ -81,9 +81,10 @@ class Scope { final SentryOptions _options; - final List _attachements = []; + final List _attachements = []; - List get attachements => UnmodifiableListView(_attachements); + List get attachements => + UnmodifiableListView(_attachements); Scope(this._options); @@ -120,10 +121,14 @@ class Scope { _breadcrumbs.add(breadcrumb); } - void addAttachment(Attachment attachment) async { + void addAttachment(SentryAttachment attachment) async { _attachements.add(attachment); } + void clearAttachments() { + _attachements.clear(); + } + /// Clear all the breadcrumbs void clearBreadcrumbs() { _breadcrumbs.clear(); diff --git a/dart/lib/src/sentry_attachment.dart b/dart/lib/src/sentry_attachment.dart new file mode 100644 index 0000000000..0a0e12139b --- /dev/null +++ b/dart/lib/src/sentry_attachment.dart @@ -0,0 +1,102 @@ +import 'dart:async'; +import 'dart:typed_data'; +export 'sentry_attachment_extensions/attachment_extensions.dart'; + +// https://develop.sentry.dev/sdk/envelopes/#attachment + +typedef ContentLoader = FutureOr Function(); + +/// Arbitrary content which gets attached to an event. +class SentryAttachment { + SentryAttachment({ + required ContentLoader loader, + required this.filename, + String? attachmentType, + this.contentType, + }) : _loader = loader, + attachmentType = attachmentType ?? AttachmentType.attachment; + + /// Creates an [SentryAttachment] from a [Uint8List] + factory SentryAttachment.fromUint8List( + Uint8List bytes, + String fileName, { + String? contentType, + String? attachmentType, + }) { + return SentryAttachment( + attachmentType: attachmentType, + loader: () => bytes, + filename: fileName, + contentType: contentType, + ); + } + + /// Creates an [SentryAttachment] from a [List] + factory SentryAttachment.fromIntList( + List bytes, + String fileName, { + String? contentType, + String? attachmentType, + }) { + return SentryAttachment( + attachmentType: attachmentType, + loader: () => Uint8List.fromList(bytes), + filename: fileName, + contentType: contentType, + ); + } + + /// Creates an [SentryAttachment] from [ByteData] + factory SentryAttachment.fromByteData( + ByteData bytes, + String fileName, { + String? contentType, + String? attachmentType, + }) { + return SentryAttachment( + attachmentType: attachmentType, + loader: () => bytes.buffer.asUint8List(), + filename: fileName, + contentType: contentType, + ); + } + + /// Attachment type. + /// Should be one of types given in [AttachmentType]. + final String attachmentType; + + /// Attachment content. + FutureOr get bytes => _loader(); + + final ContentLoader _loader; + + /// Attachment file name. + final String filename; + + /// Attachment content type. + /// Inferred by Sentry if it's not given. + final String? contentType; +} + +/// Attachment type. +class AttachmentType { + /// Standard attachment without special meaning. + static const String attachment = 'event.attachment'; + + /// Minidump file that creates an error event and is symbolicated. + /// The file should start with the `MDMP` magic bytes. + static const String minidump = 'event.minidump'; + + /// Apple crash report file that creates an error event and is symbolicated. + static const String appleCrashReport = 'event.applecrashreport'; + + /// XML file containing UE4 crash meta data. + /// During event ingestion, event contexts and extra fields are extracted from + /// this file. + static const String unrealContext = 'unreal.context'; + + /// Plain-text log file obtained from UE4 crashes. + /// During event ingestion, the last logs are extracted into event + /// breadcrumbs. + static const String unrealLogs = 'unreal.logs'; +} diff --git a/dart/lib/src/sentry_attachment_extensions/_io_attachment_extensions.dart b/dart/lib/src/sentry_attachment_extensions/_io_attachment_extensions.dart new file mode 100644 index 0000000000..292f0d1fd8 --- /dev/null +++ b/dart/lib/src/sentry_attachment_extensions/_io_attachment_extensions.dart @@ -0,0 +1,42 @@ +import 'dart:io'; + +import '../sentry_attachment.dart'; +import '../scope.dart'; + +extension IoAttachmentExtensions on Scope { + /// Creates an attachment from a given path. + /// Only available on `dart:io` platforms. + /// Not available on web. + static SentryAttachment fromPath( + String path, { + String? filename, + String? attachmentType, + String? contentType, + }) { + final file = File(path); + + return fromFile( + file, + attachmentType: attachmentType, + contentType: contentType, + filename: filename, + ); + } + + /// Creates an attachment from a given [File]. + /// Only available on `dart:io` platforms. + /// Not available on web. + static SentryAttachment fromFile( + File file, { + String? filename, + String? attachmentType, + String? contentType, + }) { + return SentryAttachment( + attachmentType: attachmentType, + loader: () => file.readAsBytes(), + filename: filename ?? file.uri.pathSegments.last, + contentType: contentType, + ); + } +} diff --git a/dart/lib/src/sentry_attachment_extensions/_web_attachment_extensions.dart b/dart/lib/src/sentry_attachment_extensions/_web_attachment_extensions.dart new file mode 100644 index 0000000000..38b44b4cf1 --- /dev/null +++ b/dart/lib/src/sentry_attachment_extensions/_web_attachment_extensions.dart @@ -0,0 +1,3 @@ +import '../sentry_attachment.dart'; + +extension WebAttachmentExtensions on SentryAttachment {} diff --git a/dart/lib/src/attachment_extensions/attachment_extensions.dart b/dart/lib/src/sentry_attachment_extensions/attachment_extensions.dart similarity index 100% rename from dart/lib/src/attachment_extensions/attachment_extensions.dart rename to dart/lib/src/sentry_attachment_extensions/attachment_extensions.dart diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 834d8a633d..398aeb5629 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -114,7 +114,8 @@ class SentryClient { attachments: scope?.attachements, ); - return captureEnvelope(envelope); + final id = await captureEnvelope(envelope); + return id!; } SentryEvent _prepareEvent(SentryEvent event, {dynamic stackTrace}) { @@ -206,7 +207,7 @@ class SentryClient { } /// Reports the [envelope] to Sentry.io. - Future captureEnvelope(SentryEnvelope envelope) { + Future captureEnvelope(SentryEnvelope envelope) { return _options.transport.send(envelope); } diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index f287898f07..5445f8e2f7 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'attachment.dart'; +import 'sentry_attachment.dart'; import 'sentry_envelope_header.dart'; import 'sentry_envelope_item.dart'; import 'protocol/sentry_event.dart'; @@ -20,7 +20,7 @@ class SentryEnvelope { factory SentryEnvelope.fromEvent( SentryEvent event, SdkVersion sdkVersion, { - List? attachments, + List? attachments, }) { return SentryEnvelope( SentryEnvelopeHeader(event.eventId, sdkVersion), diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index db9173b995..0825c7c28a 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'attachment.dart'; +import 'sentry_attachment.dart'; import 'sentry_item_type.dart'; import 'protocol/sentry_event.dart'; import 'sentry_envelope_item_header.dart'; @@ -9,18 +9,24 @@ import 'sentry_envelope_item_header.dart'; class SentryEnvelopeItem { SentryEnvelopeItem(this.header, this.dataFactory); - factory SentryEnvelopeItem.fromAttachment(Attachment attachment) { + factory SentryEnvelopeItem.fromAttachment(SentryAttachment attachment) { + final cachedItem = _CachedItem(() async { + return await attachment.bytes; + }); + + final getLength = () async => (await cachedItem.getData()).length; + final header = SentryEnvelopeItemHeader( SentryItemType.attachment, - () async => attachment.content.lengthInBytes, - contentType: attachment.mimeType, - fileName: attachment.fileName, - attachmentType: attachment.type.toSentryIdentifier(), + getLength, + contentType: attachment.contentType, + fileName: attachment.filename, + attachmentType: attachment.attachmentType, ); - return SentryEnvelopeItem(header, () async => attachment.content); + return SentryEnvelopeItem(header, cachedItem.getData); } - /// Create an `SentryEnvelopeItem` which holds the `SentyEvent` data. + /// Create an [SentryEnvelopeItem] which holds the [SentryEvent] data. factory SentryEnvelopeItem.fromEvent(SentryEvent event) { final cachedItem = _CachedItem(() async { final jsonEncoded = jsonEncode(event.toJson()); diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 07a4ff4c17..daf855ef0d 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -140,10 +140,10 @@ class MainScaffold extends StatelessWidget { withScope: (scope) { final txt = 'Lorem Ipsum dolar sit amet'; scope.addAttachment( - Attachment.fromIntList( + SentryAttachment.fromIntList( utf8.encode(txt), 'foobar.txt', - mimeType: 'text/plain', + contentType: 'text/plain', ), ); }, @@ -165,10 +165,10 @@ class MainScaffold extends StatelessWidget { } } scope.addAttachment( - Attachment.fromUint8List( + SentryAttachment.fromUint8List( feedback.screenshot, 'feedback.png', - mimeType: 'image/png', + contentType: 'image/png', ), ); }, diff --git a/flutter/lib/src/attachment_extensions.dart b/flutter/lib/src/attachment_extensions.dart index 9f78c026ca..fb92e1c8d2 100644 --- a/flutter/lib/src/attachment_extensions.dart +++ b/flutter/lib/src/attachment_extensions.dart @@ -1,7 +1,7 @@ import 'package:flutter/services.dart'; import 'package:sentry/sentry.dart'; -extension AttachmentExtension on Attachment { +extension AttachmentExtension on SentryAttachment { /// Creates an attachment from an asset out of a [AssetBundle]. /// If no bundle is given, it's using the [rootBundle]. /// Typically you want to use it like this: @@ -11,20 +11,20 @@ extension AttachmentExtension on Attachment { /// bundle: DefaultAssetBundle.of(context), /// ); /// ``` - static Future fromAsset( + static Future fromAsset( String key, { AssetBundle? bundle, - AttachmentType? type, - String? mimeType, + String? type, + String? contentType, }) async { final data = await (bundle ?? rootBundle).load(key); final fileName = Uri.parse(key).pathSegments.last; - return Attachment.fromByteData( + return SentryAttachment.fromByteData( data, fileName, - type: type, - mimeType: mimeType, + attachmentType: type, + contentType: contentType, ); } } From 6cca7013e42df3a7bbddccd23f47cfb7108e8a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Sat, 26 Jun 2021 18:13:13 +0200 Subject: [PATCH 07/20] WIP --- dart/lib/sentry_io.dart | 2 ++ dart/lib/src/sentry_attachment.dart | 1 - .../_web_attachment_extensions.dart | 3 --- .../sentry_attachment_extensions/attachment_extensions.dart | 2 -- ...ttachment_extensions.dart => io_attachment_extensions.dart} | 0 5 files changed, 2 insertions(+), 6 deletions(-) create mode 100644 dart/lib/sentry_io.dart delete mode 100644 dart/lib/src/sentry_attachment_extensions/_web_attachment_extensions.dart delete mode 100644 dart/lib/src/sentry_attachment_extensions/attachment_extensions.dart rename dart/lib/src/sentry_attachment_extensions/{_io_attachment_extensions.dart => io_attachment_extensions.dart} (100%) diff --git a/dart/lib/sentry_io.dart b/dart/lib/sentry_io.dart new file mode 100644 index 0000000000..53b2620cf0 --- /dev/null +++ b/dart/lib/sentry_io.dart @@ -0,0 +1,2 @@ +export 'sentry.dart'; +export 'src/sentry_attachment_extensions/io_attachment_extensions.dart'; diff --git a/dart/lib/src/sentry_attachment.dart b/dart/lib/src/sentry_attachment.dart index 0a0e12139b..78b6704fc2 100644 --- a/dart/lib/src/sentry_attachment.dart +++ b/dart/lib/src/sentry_attachment.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:typed_data'; -export 'sentry_attachment_extensions/attachment_extensions.dart'; // https://develop.sentry.dev/sdk/envelopes/#attachment diff --git a/dart/lib/src/sentry_attachment_extensions/_web_attachment_extensions.dart b/dart/lib/src/sentry_attachment_extensions/_web_attachment_extensions.dart deleted file mode 100644 index 38b44b4cf1..0000000000 --- a/dart/lib/src/sentry_attachment_extensions/_web_attachment_extensions.dart +++ /dev/null @@ -1,3 +0,0 @@ -import '../sentry_attachment.dart'; - -extension WebAttachmentExtensions on SentryAttachment {} diff --git a/dart/lib/src/sentry_attachment_extensions/attachment_extensions.dart b/dart/lib/src/sentry_attachment_extensions/attachment_extensions.dart deleted file mode 100644 index 58b5d73b83..0000000000 --- a/dart/lib/src/sentry_attachment_extensions/attachment_extensions.dart +++ /dev/null @@ -1,2 +0,0 @@ -export '_io_attachment_extensions.dart' - if (dart.library.html) '_web_scope_extensions.dart'; diff --git a/dart/lib/src/sentry_attachment_extensions/_io_attachment_extensions.dart b/dart/lib/src/sentry_attachment_extensions/io_attachment_extensions.dart similarity index 100% rename from dart/lib/src/sentry_attachment_extensions/_io_attachment_extensions.dart rename to dart/lib/src/sentry_attachment_extensions/io_attachment_extensions.dart From 5445b83826a49747f02d0cd0acbf219973b42cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Sun, 27 Jun 2021 10:34:15 +0200 Subject: [PATCH 08/20] more tests --- dart/lib/src/scope.dart | 1 + dart/lib/src/sentry_attachment.dart | 1 + dart/test/scope_test.dart | 40 +++++++++++++ dart/test/sentry_attachment_test.dart | 85 +++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 dart/test/sentry_attachment_test.dart diff --git a/dart/lib/src/scope.dart b/dart/lib/src/scope.dart index bcff9506f5..5c640be328 100644 --- a/dart/lib/src/scope.dart +++ b/dart/lib/src/scope.dart @@ -142,6 +142,7 @@ class Scope { /// Resets the Scope to its default state void clear() { clearBreadcrumbs(); + clearAttachments(); level = null; transaction = null; user = null; diff --git a/dart/lib/src/sentry_attachment.dart b/dart/lib/src/sentry_attachment.dart index 78b6704fc2..920a127ec8 100644 --- a/dart/lib/src/sentry_attachment.dart +++ b/dart/lib/src/sentry_attachment.dart @@ -65,6 +65,7 @@ class SentryAttachment { final String attachmentType; /// Attachment content. + /// Is loaded while sending this attachment. FutureOr get bytes => _loader(); final ContentLoader _loader; diff --git a/dart/test/scope_test.dart b/dart/test/scope_test.dart index 9a07456fb7..3176ebc715 100644 --- a/dart/test/scope_test.dart +++ b/dart/test/scope_test.dart @@ -154,6 +154,38 @@ void main() { expect(sut.breadcrumbs.length, 0); }); + test('adds $SentryAttachment', () { + final sut = fixture.getSut(); + + final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); + sut.addAttachment(attachment); + + expect(sut.attachements.last, attachment); + expect(sut.attachements.length, 1); + }); + + test('clear() removes all $SentryAttachment', () { + final sut = fixture.getSut(); + + final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); + sut.addAttachment(attachment); + expect(sut.attachements.length, 1); + sut.clear(); + + expect(sut.attachements.length, 0); + }); + + test('clearAttachments() removes all $SentryAttachment', () { + final sut = fixture.getSut(); + + final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); + sut.addAttachment(attachment); + expect(sut.attachements.length, 1); + sut.clearAttachments(); + + expect(sut.attachements.length, 0); + }); + test('sets tag', () { final sut = fixture.getSut(); @@ -232,6 +264,13 @@ void main() { test('clones', () { final sut = fixture.getSut(); + + sut.addBreadcrumb(Breadcrumb( + message: 'test log', + timestamp: DateTime.utc(2019), + )); + sut.addAttachment(SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt')); + final clone = sut.clone(); expect(sut.user, clone.user); expect(sut.transaction, clone.transaction); @@ -239,6 +278,7 @@ void main() { expect(sut.tags, clone.tags); expect(sut.breadcrumbs, clone.breadcrumbs); expect(sut.contexts, clone.contexts); + expect(sut.attachements, clone.attachements); expect(ListEquality().equals(sut.fingerprint, clone.fingerprint), true); expect( ListEquality().equals(sut.eventProcessors, clone.eventProcessors), diff --git a/dart/test/sentry_attachment_test.dart b/dart/test/sentry_attachment_test.dart new file mode 100644 index 0000000000..9192c7fd69 --- /dev/null +++ b/dart/test/sentry_attachment_test.dart @@ -0,0 +1,85 @@ +import 'dart:typed_data'; + +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; +import 'mocks/mock_transport.dart'; + +void main() { + group('$SentryAttachment ctor', () { + test('default', () async { + final attachment = SentryAttachment( + loader: () => Uint8List.fromList([0, 0, 0, 0]), + filename: 'test.txt', + ); + expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'test.txt'); + await expectLater(await attachment.bytes, [0, 0, 0, 0]); + }); + + test('fromIntList', () async { + final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); + expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'test.txt'); + await expectLater(await attachment.bytes, [0, 0, 0, 0]); + }); + + test('fromUint8List', () async { + final attachment = SentryAttachment.fromUint8List( + Uint8List.fromList([0, 0, 0, 0]), + 'test.txt', + ); + expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'test.txt'); + await expectLater(await attachment.bytes, [0, 0, 0, 0]); + }); + + test('fromByteData', () async { + final attachment = SentryAttachment.fromByteData( + ByteData.sublistView(Uint8List.fromList([0, 0, 0, 0])), + 'test.txt', + ); + expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'test.txt'); + await expectLater(await attachment.bytes, [0, 0, 0, 0]); + }); + }); + + group('$Scope $SentryAttachment tests', () { + test('Sending with attachments', () async { + final options = SentryOptions(dsn: fakeDsn); + final transport = MockTransport(); + options.transport = transport; + final hub = Hub(options); + await hub.captureEvent(SentryEvent(), withScope: (scope) { + scope.addAttachment( + SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'), + ); + }); + expect(transport.envelopes.length, 1); + expect(transport.envelopes.first.items.length, 2); + final attachmentEnvelope = transport.envelopes.first.items[1]; + expect( + attachmentEnvelope.header.attachmentType, + AttachmentType.attachment, + ); + expect( + attachmentEnvelope.header.contentType, + isNull, + ); + expect( + attachmentEnvelope.header.fileName, + 'test.txt', + ); + await expectLater( + await attachmentEnvelope.header.length(), + 4, + ); + }); + }); +} From 32ee66b7fd1dd2a22633c2407b5f5938ee0571a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Mon, 28 Jun 2021 13:14:17 +0200 Subject: [PATCH 09/20] use list unmodifiable --- dart/lib/src/scope.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dart/lib/src/scope.dart b/dart/lib/src/scope.dart index 5c640be328..e79f8285ba 100644 --- a/dart/lib/src/scope.dart +++ b/dart/lib/src/scope.dart @@ -83,8 +83,7 @@ class Scope { final List _attachements = []; - List get attachements => - UnmodifiableListView(_attachements); + List get attachements => List.unmodifiable(_attachements); Scope(this._options); From 664617e137d4d314a44db70aa46db3470617e9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Mon, 28 Jun 2021 18:30:37 +0200 Subject: [PATCH 10/20] transport can return null --- dart/lib/src/sentry_client.dart | 2 +- dart/lib/src/transport/http_transport.dart | 7 +++++-- dart/lib/src/transport/noop_transport.dart | 3 +-- dart/lib/src/transport/transport.dart | 2 +- flutter/lib/src/file_system_transport.dart | 6 +++--- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 398aeb5629..a901ebc8a4 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -115,7 +115,7 @@ class SentryClient { ); final id = await captureEnvelope(envelope); - return id!; + return id ?? SentryId.empty(); } SentryEvent _prepareEvent(SentryEvent event, {dynamic stackTrace}) { diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index 3a93da51f1..7137308381 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -45,7 +45,7 @@ class HttpTransport implements Transport { } @override - Future send(SentryEnvelope envelope) async { + Future send(SentryEnvelope envelope) async { final filteredEnvelope = _rateLimiter.filter(envelope); if (filteredEnvelope == null) { return SentryId.empty(); @@ -77,7 +77,10 @@ class HttpTransport implements Transport { } final eventId = json.decode(response.body)['id']; - return eventId != null ? SentryId.fromId(eventId) : SentryId.empty(); + if (eventId == null) { + return null; + } + return SentryId.fromId(eventId); } Future _createStreamedRequest( diff --git a/dart/lib/src/transport/noop_transport.dart b/dart/lib/src/transport/noop_transport.dart index 705dfc3a9c..117a8756cd 100644 --- a/dart/lib/src/transport/noop_transport.dart +++ b/dart/lib/src/transport/noop_transport.dart @@ -7,6 +7,5 @@ import 'transport.dart'; class NoOpTransport implements Transport { @override - Future send(SentryEnvelope envelope) => - Future.value(SentryId.empty()); + Future send(SentryEnvelope envelope) => Future.value(null); } diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 08de9e9322..f0a6a2c996 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -6,5 +6,5 @@ import '../protocol.dart'; /// A transport is in charge of sending the event/envelope either via http /// or caching in the disk. abstract class Transport { - Future send(SentryEnvelope envelope); + Future send(SentryEnvelope envelope); } diff --git a/flutter/lib/src/file_system_transport.dart b/flutter/lib/src/file_system_transport.dart index 55d6e29de3..9574607d6c 100644 --- a/flutter/lib/src/file_system_transport.dart +++ b/flutter/lib/src/file_system_transport.dart @@ -10,7 +10,7 @@ class FileSystemTransport implements Transport { final SentryOptions _options; @override - Future send(SentryEnvelope envelope) async { + Future send(SentryEnvelope envelope) async { final envelopeData = []; await envelope.envelopeStream().forEach(envelopeData.addAll); // https://flutter.dev/docs/development/platform-integration/platform-channels#codec @@ -24,9 +24,9 @@ class FileSystemTransport implements Transport { exception: exception, stackTrace: stackTrace, ); - return SentryId.empty(); + return null; } - return envelope.header.eventId ?? SentryId.empty(); + return envelope.header.eventId; } } From 75a7b604d4de9c4f961d54b6287fff1d99a00421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Mon, 28 Jun 2021 18:45:49 +0200 Subject: [PATCH 11/20] fix test --- dart/test/transport/http_transport_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dart/test/transport/http_transport_test.dart b/dart/test/transport/http_transport_test.dart index 89e5613814..0597ecfcd2 100644 --- a/dart/test/transport/http_transport_test.dart +++ b/dart/test/transport/http_transport_test.dart @@ -92,7 +92,7 @@ void main() { SentryEnvelope.fromEvent(sentryEvent, fixture.options.sdk); final eventId = await sut.send(envelope); - expect(eventId, SentryId.empty()); + expect(eventId.toString(), SentryId.empty().toString()); expect(httpCalled, false); }); }); From 887adda1ed014892e7ca69409d272a801deb45fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Mon, 28 Jun 2021 19:46:51 +0200 Subject: [PATCH 12/20] wip --- dart/lib/sentry.dart | 2 +- dart/lib/sentry_io.dart | 2 +- dart/lib/src/scope.dart | 2 +- .../io_sentry_attachment.dart | 35 +++++++++++++ .../sentry_attachment.dart | 51 +++++++++---------- .../io_attachment_extensions.dart | 42 --------------- dart/lib/src/sentry_envelope.dart | 2 +- dart/lib/src/sentry_envelope_item.dart | 2 +- dart/test/sentry_attachment_test.dart | 2 +- dart/test/sentry_attachment_test_io.dart | 31 +++++++++++ dart/test_resources/testfile.txt | 1 + flutter/lib/sentry_flutter.dart | 2 +- ...ns.dart => flutter_sentry_attachment.dart} | 25 +++++---- .../test/flutter_sentry_attachment_test.dart | 1 + 14 files changed, 110 insertions(+), 90 deletions(-) create mode 100644 dart/lib/src/sentry_attachment/io_sentry_attachment.dart rename dart/lib/src/{ => sentry_attachment}/sentry_attachment.dart (74%) delete mode 100644 dart/lib/src/sentry_attachment_extensions/io_attachment_extensions.dart create mode 100644 dart/test/sentry_attachment_test_io.dart create mode 100644 dart/test_resources/testfile.txt rename flutter/lib/src/{attachment_extensions.dart => flutter_sentry_attachment.dart} (53%) create mode 100644 flutter/test/flutter_sentry_attachment_test.dart diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index 59abda21cd..439bf21587 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -22,4 +22,4 @@ export 'src/transport/transport.dart'; export 'src/integration.dart'; export 'src/event_processor.dart'; export 'src/http_client/sentry_http_client.dart'; -export 'src/sentry_attachment.dart'; +export 'src/sentry_attachment/sentry_attachment.dart'; diff --git a/dart/lib/sentry_io.dart b/dart/lib/sentry_io.dart index 53b2620cf0..db73e953ba 100644 --- a/dart/lib/sentry_io.dart +++ b/dart/lib/sentry_io.dart @@ -1,2 +1,2 @@ export 'sentry.dart'; -export 'src/sentry_attachment_extensions/io_attachment_extensions.dart'; +export 'src/sentry_attachment/io_sentry_attachment.dart'; diff --git a/dart/lib/src/scope.dart b/dart/lib/src/scope.dart index e79f8285ba..73a7cbd96b 100644 --- a/dart/lib/src/scope.dart +++ b/dart/lib/src/scope.dart @@ -1,5 +1,5 @@ import 'dart:collection'; -import 'sentry_attachment.dart'; +import 'sentry_attachment/sentry_attachment.dart'; import 'event_processor.dart'; import 'protocol.dart'; import 'sentry_options.dart'; diff --git a/dart/lib/src/sentry_attachment/io_sentry_attachment.dart b/dart/lib/src/sentry_attachment/io_sentry_attachment.dart new file mode 100644 index 0000000000..a1e2585556 --- /dev/null +++ b/dart/lib/src/sentry_attachment/io_sentry_attachment.dart @@ -0,0 +1,35 @@ +import 'dart:io'; + +import 'sentry_attachment.dart'; + +class IoSentryAttachment extends SentryAttachment { + /// Creates an attachment from a given path. + /// Only available on `dart:io` platforms. + /// Not available on web. + IoSentryAttachment.fromPath( + String path, { + String? filename, + String? attachmentType, + String? contentType, + }) : this.fromFile( + File(path), + attachmentType: attachmentType, + contentType: contentType, + filename: filename, + ); + + /// Creates an attachment from a given [File]. + /// Only available on `dart:io` platforms. + /// Not available on web. + IoSentryAttachment.fromFile( + File file, { + String? filename, + String? attachmentType, + String? contentType, + }) : super.fromLoader( + loader: () => file.readAsBytes(), + filename: filename ?? file.uri.pathSegments.last, + attachmentType: attachmentType, + contentType: contentType, + ); +} diff --git a/dart/lib/src/sentry_attachment.dart b/dart/lib/src/sentry_attachment/sentry_attachment.dart similarity index 74% rename from dart/lib/src/sentry_attachment.dart rename to dart/lib/src/sentry_attachment/sentry_attachment.dart index 920a127ec8..89f82ed9b0 100644 --- a/dart/lib/src/sentry_attachment.dart +++ b/dart/lib/src/sentry_attachment/sentry_attachment.dart @@ -1,13 +1,14 @@ import 'dart:async'; import 'dart:typed_data'; +// https://develop.sentry.dev/sdk/features/#attachments // https://develop.sentry.dev/sdk/envelopes/#attachment typedef ContentLoader = FutureOr Function(); /// Arbitrary content which gets attached to an event. class SentryAttachment { - SentryAttachment({ + SentryAttachment.fromLoader({ required ContentLoader loader, required this.filename, String? attachmentType, @@ -16,49 +17,43 @@ class SentryAttachment { attachmentType = attachmentType ?? AttachmentType.attachment; /// Creates an [SentryAttachment] from a [Uint8List] - factory SentryAttachment.fromUint8List( + SentryAttachment.fromUint8List( Uint8List bytes, String fileName, { String? contentType, String? attachmentType, - }) { - return SentryAttachment( - attachmentType: attachmentType, - loader: () => bytes, - filename: fileName, - contentType: contentType, - ); - } + }) : this.fromLoader( + attachmentType: attachmentType, + loader: () => bytes, + filename: fileName, + contentType: contentType, + ); /// Creates an [SentryAttachment] from a [List] - factory SentryAttachment.fromIntList( + SentryAttachment.fromIntList( List bytes, String fileName, { String? contentType, String? attachmentType, - }) { - return SentryAttachment( - attachmentType: attachmentType, - loader: () => Uint8List.fromList(bytes), - filename: fileName, - contentType: contentType, - ); - } + }) : this.fromLoader( + attachmentType: attachmentType, + loader: () => Uint8List.fromList(bytes), + filename: fileName, + contentType: contentType, + ); /// Creates an [SentryAttachment] from [ByteData] - factory SentryAttachment.fromByteData( + SentryAttachment.fromByteData( ByteData bytes, String fileName, { String? contentType, String? attachmentType, - }) { - return SentryAttachment( - attachmentType: attachmentType, - loader: () => bytes.buffer.asUint8List(), - filename: fileName, - contentType: contentType, - ); - } + }) : this.fromLoader( + attachmentType: attachmentType, + loader: () => bytes.buffer.asUint8List(), + filename: fileName, + contentType: contentType, + ); /// Attachment type. /// Should be one of types given in [AttachmentType]. diff --git a/dart/lib/src/sentry_attachment_extensions/io_attachment_extensions.dart b/dart/lib/src/sentry_attachment_extensions/io_attachment_extensions.dart deleted file mode 100644 index 292f0d1fd8..0000000000 --- a/dart/lib/src/sentry_attachment_extensions/io_attachment_extensions.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'dart:io'; - -import '../sentry_attachment.dart'; -import '../scope.dart'; - -extension IoAttachmentExtensions on Scope { - /// Creates an attachment from a given path. - /// Only available on `dart:io` platforms. - /// Not available on web. - static SentryAttachment fromPath( - String path, { - String? filename, - String? attachmentType, - String? contentType, - }) { - final file = File(path); - - return fromFile( - file, - attachmentType: attachmentType, - contentType: contentType, - filename: filename, - ); - } - - /// Creates an attachment from a given [File]. - /// Only available on `dart:io` platforms. - /// Not available on web. - static SentryAttachment fromFile( - File file, { - String? filename, - String? attachmentType, - String? contentType, - }) { - return SentryAttachment( - attachmentType: attachmentType, - loader: () => file.readAsBytes(), - filename: filename ?? file.uri.pathSegments.last, - contentType: contentType, - ); - } -} diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index 5445f8e2f7..ce79f61c68 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'sentry_attachment.dart'; +import 'sentry_attachment/sentry_attachment.dart'; import 'sentry_envelope_header.dart'; import 'sentry_envelope_item.dart'; import 'protocol/sentry_event.dart'; diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index 0825c7c28a..9c9e5f021d 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'sentry_attachment.dart'; +import 'sentry_attachment/sentry_attachment.dart'; import 'sentry_item_type.dart'; import 'protocol/sentry_event.dart'; import 'sentry_envelope_item_header.dart'; diff --git a/dart/test/sentry_attachment_test.dart b/dart/test/sentry_attachment_test.dart index 9192c7fd69..ccf00da2f6 100644 --- a/dart/test/sentry_attachment_test.dart +++ b/dart/test/sentry_attachment_test.dart @@ -9,7 +9,7 @@ import 'mocks/mock_transport.dart'; void main() { group('$SentryAttachment ctor', () { test('default', () async { - final attachment = SentryAttachment( + final attachment = SentryAttachment.fromLoader( loader: () => Uint8List.fromList([0, 0, 0, 0]), filename: 'test.txt', ); diff --git a/dart/test/sentry_attachment_test_io.dart b/dart/test/sentry_attachment_test_io.dart new file mode 100644 index 0000000000..1ef161da43 --- /dev/null +++ b/dart/test/sentry_attachment_test_io.dart @@ -0,0 +1,31 @@ +@TestOn('vm') + +import 'dart:io'; + +import 'package:sentry/sentry_io.dart'; +import 'package:test/test.dart'; + +void main() { + group('$SentryAttachment ctor', () { + test('fromFile', () async { + final file = File('test_resources/testfile.txt'); + + final attachment = IoSentryAttachment.fromFile(file); + expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'testfile.txt'); + await expectLater( + await attachment.bytes, [102, 111, 111, 32, 98, 97, 114]); + }); + + test('fromPath', () async { + final attachment = + IoSentryAttachment.fromPath('test_resources/testfile.txt'); + expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'testfile.txt'); + await expectLater( + await attachment.bytes, [102, 111, 111, 32, 98, 97, 114]); + }); + }); +} diff --git a/dart/test_resources/testfile.txt b/dart/test_resources/testfile.txt new file mode 100644 index 0000000000..96c906756d --- /dev/null +++ b/dart/test_resources/testfile.txt @@ -0,0 +1 @@ +foo bar \ No newline at end of file diff --git a/flutter/lib/sentry_flutter.dart b/flutter/lib/sentry_flutter.dart index 7c3666a02f..9d4b8cc89f 100644 --- a/flutter/lib/sentry_flutter.dart +++ b/flutter/lib/sentry_flutter.dart @@ -5,4 +5,4 @@ export 'src/default_integrations.dart'; export 'src/navigation/sentry_navigator_observer.dart'; export 'src/sentry_flutter.dart'; export 'src/sentry_flutter_options.dart'; -export 'src/attachment_extensions.dart'; +export 'src/flutter_sentry_attachment.dart'; diff --git a/flutter/lib/src/attachment_extensions.dart b/flutter/lib/src/flutter_sentry_attachment.dart similarity index 53% rename from flutter/lib/src/attachment_extensions.dart rename to flutter/lib/src/flutter_sentry_attachment.dart index fb92e1c8d2..c644a942c2 100644 --- a/flutter/lib/src/attachment_extensions.dart +++ b/flutter/lib/src/flutter_sentry_attachment.dart @@ -1,7 +1,7 @@ import 'package:flutter/services.dart'; import 'package:sentry/sentry.dart'; -extension AttachmentExtension on SentryAttachment { +class FlutterSentryAttachment extends SentryAttachment { /// Creates an attachment from an asset out of a [AssetBundle]. /// If no bundle is given, it's using the [rootBundle]. /// Typically you want to use it like this: @@ -11,20 +11,19 @@ extension AttachmentExtension on SentryAttachment { /// bundle: DefaultAssetBundle.of(context), /// ); /// ``` - static Future fromAsset( + FlutterSentryAttachment.fromAsset( String key, { + String? filename, AssetBundle? bundle, String? type, String? contentType, - }) async { - final data = await (bundle ?? rootBundle).load(key); - final fileName = Uri.parse(key).pathSegments.last; - - return SentryAttachment.fromByteData( - data, - fileName, - attachmentType: type, - contentType: contentType, - ); - } + }) : super.fromLoader( + loader: () async { + final data = await (bundle ?? rootBundle).load(key); + return data.buffer.asUint8List(); + }, + filename: filename ?? Uri.parse(key).pathSegments.last, + attachmentType: type, + contentType: contentType, + ); } diff --git a/flutter/test/flutter_sentry_attachment_test.dart b/flutter/test/flutter_sentry_attachment_test.dart new file mode 100644 index 0000000000..ab73b3a234 --- /dev/null +++ b/flutter/test/flutter_sentry_attachment_test.dart @@ -0,0 +1 @@ +void main() {} From 0e415032b336847b40c73041762478ebcc121fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Mon, 28 Jun 2021 19:55:58 +0200 Subject: [PATCH 13/20] test --- .../test/flutter_sentry_attachment_test.dart | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/flutter/test/flutter_sentry_attachment_test.dart b/flutter/test/flutter_sentry_attachment_test.dart index ab73b3a234..26194c29ea 100644 --- a/flutter/test/flutter_sentry_attachment_test.dart +++ b/flutter/test/flutter_sentry_attachment_test.dart @@ -1 +1,33 @@ -void main() {} +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; + +void main() { + test('fromAsset', () async { + final attachment = FlutterSentryAttachment.fromAsset( + 'foobar.txt', + bundle: TestAssetBundle(), + ); + + expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.contentType, isNull); + expect(attachment.filename, 'foobar.txt'); + await expectLater(await attachment.bytes, [102, 111, 111, 32, 98, 97, 114]); + }); +} + +class TestAssetBundle extends CachingAssetBundle { + @override + Future load(String key) async { + if (key == 'foobar.txt') { + return ByteData.view( + Uint8List.fromList(utf8.encode('foo bar')).buffer, + ); + } + + return ByteData(0); + } +} From 661116b5a795320b323016f08225b2239aa5c89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Mon, 28 Jun 2021 20:05:27 +0200 Subject: [PATCH 14/20] fix file system transport --- flutter/lib/src/file_system_transport.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/lib/src/file_system_transport.dart b/flutter/lib/src/file_system_transport.dart index 9574607d6c..d155a93538 100644 --- a/flutter/lib/src/file_system_transport.dart +++ b/flutter/lib/src/file_system_transport.dart @@ -24,7 +24,7 @@ class FileSystemTransport implements Transport { exception: exception, stackTrace: stackTrace, ); - return null; + return SentryId.empty(); } return envelope.header.eventId; From 3a12ed7a8106efa4634200b8d1eab60eb2fbe745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Tue, 29 Jun 2021 08:06:33 +0200 Subject: [PATCH 15/20] skip attachment if it throws --- dart/lib/src/sentry_envelope.dart | 4 +- dart/lib/src/sentry_envelope_item.dart | 16 +- dart/test/sentry_client_test.dart | 5 +- dart/test/sentry_envelope_item_test.dart | 3 +- dart/test/sentry_envelope_test.dart | 11 +- dart/test/sentry_envelope_vm_test.dart | 251 +++++++++++++++++++++++ 6 files changed, 270 insertions(+), 20 deletions(-) diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index ce79f61c68..cdd455e9c5 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -38,9 +38,7 @@ class SentryEnvelope { final newLineData = utf8.encode('\n'); for (final item in items) { yield newLineData; - await for (final chunk in item.envelopeItemStream()) { - yield chunk; - } + yield await item.envelopeItemStream(); } } } diff --git a/dart/lib/src/sentry_envelope_item.dart b/dart/lib/src/sentry_envelope_item.dart index 9c9e5f021d..f33ac682c1 100644 --- a/dart/lib/src/sentry_envelope_item.dart +++ b/dart/lib/src/sentry_envelope_item.dart @@ -54,10 +54,18 @@ class SentryEnvelopeItem { final Future> Function() dataFactory; /// Stream binary data of `Envelope` item. - Stream> envelopeItemStream() async* { - yield utf8.encode(jsonEncode(await header.toJson())); - yield utf8.encode('\n'); - yield await dataFactory(); + Future> envelopeItemStream() async { + // Each item needs to be encoded as one unit. + // Otherwise the header alredy got yielded if the content throws + // an exception. + try { + final itemHeader = utf8.encode(jsonEncode(await header.toJson())); + final newLine = utf8.encode('\n'); + final data = await dataFactory(); + return [...itemHeader, ...newLine, ...data]; + } catch (e) { + return []; + } } } diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart index aec2f74b8e..3e42cfea35 100644 --- a/dart/test/sentry_client_test.dart +++ b/dart/test/sentry_client_test.dart @@ -725,9 +725,8 @@ void main() { Future eventFromEnvelope(SentryEnvelope envelope) async { final envelopeItemData = []; - await envelope.items.first - .envelopeItemStream() - .forEach(envelopeItemData.addAll); + envelopeItemData.addAll(await envelope.items.first.envelopeItemStream()); + final envelopeItem = utf8.decode(envelopeItemData); final envelopeItemJson = jsonDecode(envelopeItem.split('\n').last); return SentryEvent.fromJson(envelopeItemJson as Map); diff --git a/dart/test/sentry_envelope_item_test.dart b/dart/test/sentry_envelope_item_test.dart index 3e000cf953..c367e57c9c 100644 --- a/dart/test/sentry_envelope_item_test.dart +++ b/dart/test/sentry_envelope_item_test.dart @@ -24,8 +24,7 @@ void main() { final headerJsonEncoded = jsonEncode(headerJson); final expected = utf8.encode('$headerJsonEncoded\n{fixture}'); - final actualItem = []; - await sut.envelopeItemStream().forEach(actualItem.addAll); + final actualItem = await sut.envelopeItemStream(); expect(actualItem, expected); }); diff --git a/dart/test/sentry_envelope_test.dart b/dart/test/sentry_envelope_test.dart index 624e4b0992..c153509ab6 100644 --- a/dart/test/sentry_envelope_test.dart +++ b/dart/test/sentry_envelope_test.dart @@ -31,8 +31,7 @@ void main() { final expectedHeaderJson = header.toJson(); final expectedHeaderJsonSerialized = jsonEncode(expectedHeaderJson); - final expectedItem = []; - await item.envelopeItemStream().forEach(expectedItem.addAll); + final expectedItem = await item.envelopeItemStream(); final expectedItemSerialized = utf8.decode(expectedItem); final expected = utf8.encode( @@ -60,13 +59,9 @@ void main() { expect(await sut.items[0].header.length(), await expectedEnvelopeItem.header.length()); - final actualItem = []; - await sut.items[0].envelopeItemStream().forEach(actualItem.addAll); + final actualItem = await sut.items[0].envelopeItemStream(); - final expectedItem = []; - await expectedEnvelopeItem - .envelopeItemStream() - .forEach(expectedItem.addAll); + final expectedItem = await expectedEnvelopeItem.envelopeItemStream(); expect(actualItem, expectedItem); }); diff --git a/dart/test/sentry_envelope_vm_test.dart b/dart/test/sentry_envelope_vm_test.dart index ffbdd14e08..497552a2f9 100644 --- a/dart/test/sentry_envelope_vm_test.dart +++ b/dart/test/sentry_envelope_vm_test.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:sentry/sentry.dart'; +import 'package:sentry/sentry_io.dart'; import 'package:sentry/src/sentry_envelope.dart'; import 'package:sentry/src/sentry_envelope_header.dart'; import 'package:sentry/src/sentry_envelope_item_header.dart'; @@ -42,5 +43,255 @@ void main() { expect(expectedEnvelopeData, envelopeData); }); + + test('skips attachment if path is invalid', () async { + final event = SentryEvent( + eventId: SentryId.empty(), + timestamp: DateTime(1970, 1, 1), + ); + final sdkVersion = SdkVersion(name: '', version: ''); + final attachment = + IoSentryAttachment.fromPath('this_path_does_not_exist.txt'); + + final envelope = SentryEnvelope.fromEvent(event, sdkVersion, + attachments: [attachment]); + + final data = + (await envelope.envelopeStream().toList()).reduce((a, b) => a + b); + + expect(data, envelopeBinaryData); + }); }); } + +final envelopeBinaryData = [ + 123, + 34, + 101, + 118, + 101, + 110, + 116, + 95, + 105, + 100, + 34, + 58, + 34, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 34, + 44, + 34, + 115, + 100, + 107, + 34, + 58, + 123, + 34, + 110, + 97, + 109, + 101, + 34, + 58, + 34, + 34, + 44, + 34, + 118, + 101, + 114, + 115, + 105, + 111, + 110, + 34, + 58, + 34, + 34, + 125, + 125, + 10, + 123, + 34, + 99, + 111, + 110, + 116, + 101, + 110, + 116, + 95, + 116, + 121, + 112, + 101, + 34, + 58, + 34, + 97, + 112, + 112, + 108, + 105, + 99, + 97, + 116, + 105, + 111, + 110, + 47, + 106, + 115, + 111, + 110, + 34, + 44, + 34, + 116, + 121, + 112, + 101, + 34, + 58, + 34, + 101, + 118, + 101, + 110, + 116, + 34, + 44, + 34, + 108, + 101, + 110, + 103, + 116, + 104, + 34, + 58, + 56, + 54, + 125, + 10, + 123, + 34, + 101, + 118, + 101, + 110, + 116, + 95, + 105, + 100, + 34, + 58, + 34, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 34, + 44, + 34, + 116, + 105, + 109, + 101, + 115, + 116, + 97, + 109, + 112, + 34, + 58, + 34, + 49, + 57, + 55, + 48, + 45, + 48, + 49, + 45, + 48, + 49, + 84, + 48, + 48, + 58, + 48, + 48, + 58, + 48, + 48, + 46, + 48, + 48, + 48, + 90, + 34, + 125, + 10 +]; From 9dae6ab55cf1c3b45b3a248703721e9c3f336990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Thu, 1 Jul 2021 08:38:21 +0200 Subject: [PATCH 16/20] fix sentry id --- dart/lib/src/protocol/sentry_id.dart | 30 ++++++++++++----- dart/test/sentry_id_test.dart | 35 ++++++++++++++++++++ dart/test/transport/http_transport_test.dart | 2 +- 3 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 dart/test/sentry_id_test.dart diff --git a/dart/lib/src/protocol/sentry_id.dart b/dart/lib/src/protocol/sentry_id.dart index d9291d42d0..500c8980a2 100644 --- a/dart/lib/src/protocol/sentry_id.dart +++ b/dart/lib/src/protocol/sentry_id.dart @@ -1,28 +1,40 @@ import 'package:meta/meta.dart'; import 'package:uuid/uuid.dart'; -/// Hexadecimal string representing a uuid4 value +/// Hexadecimal string representing a uuid4 value. +/// The length is exactly 32 +/// characters. Dashes are not allowed. Has to be lowercase. @immutable class SentryId { - static final SentryId _emptyId = - SentryId.fromId('00000000-0000-0000-0000-000000000000'); - /// The ID Sentry.io assigned to the submitted event for future reference. final String _id; static final Uuid _uuidGenerator = Uuid(); - SentryId._internal({String? id}) : _id = id ?? _uuidGenerator.v4(); + SentryId._internal({String? id}) + : _id = + id?.replaceAll('-', '') ?? _uuidGenerator.v4().replaceAll('-', ''); /// Generates a new SentryId - factory SentryId.newId() => SentryId._internal(); + SentryId.newId() : this._internal(); /// Generates a SentryId with the given UUID - factory SentryId.fromId(String id) => SentryId._internal(id: id); + SentryId.fromId(String id) : this._internal(id: id); /// SentryId with an empty UUID - factory SentryId.empty() => _emptyId; + const SentryId.empty() : _id = '00000000000000000000000000000000'; + + @override + String toString() => _id; + + @override + int get hashCode => _id.hashCode; @override - String toString() => _id.replaceAll('-', ''); + bool operator ==(o) { + if (o is SentryId) { + return o._id == _id; + } + return false; + } } diff --git a/dart/test/sentry_id_test.dart b/dart/test/sentry_id_test.dart new file mode 100644 index 0000000000..23878f7082 --- /dev/null +++ b/dart/test/sentry_id_test.dart @@ -0,0 +1,35 @@ +import 'package:sentry/src/protocol/sentry_id.dart'; +import 'package:test/test.dart'; + +void main() { + test('empty id', () { + expect(SentryId.empty().toString(), '00000000000000000000000000000000'); + }); + + test('empty id equals from empty id', () { + expect( + SentryId.empty(), + SentryId.fromId('00000000000000000000000000000000'), + ); + }); + + test('uuid format with dashes', () { + expect( + SentryId.fromId('00000000-0000-0000-0000-000000000000'), + SentryId.empty(), + ); + }); + + test('empty id equality', () { + expect(SentryId.empty(), SentryId.empty()); + }); + + test('id roundtrip', () { + final id = SentryId.newId(); + expect(id, SentryId.fromId(id.toString())); + }); + + test('newId should not be equal to newId', () { + expect(SentryId.newId() == SentryId.newId(), false); + }); +} diff --git a/dart/test/transport/http_transport_test.dart b/dart/test/transport/http_transport_test.dart index 0597ecfcd2..89e5613814 100644 --- a/dart/test/transport/http_transport_test.dart +++ b/dart/test/transport/http_transport_test.dart @@ -92,7 +92,7 @@ void main() { SentryEnvelope.fromEvent(sentryEvent, fixture.options.sdk); final eventId = await sut.send(envelope); - expect(eventId.toString(), SentryId.empty().toString()); + expect(eventId, SentryId.empty()); expect(httpCalled, false); }); }); From 313d407486c7a5a2daad74581fadce27bc29dedc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Fri, 2 Jul 2021 19:19:48 +0200 Subject: [PATCH 17/20] WIP --- CHANGELOG.md | 3 +- dart/lib/src/scope.dart | 4 +- .../sentry_attachment/sentry_attachment.dart | 45 +++++++++---------- dart/lib/src/sentry_envelope.dart | 7 ++- dart/test/sentry_attachment_test.dart | 10 ++--- dart/test/sentry_attachment_test_io.dart | 4 +- dart/test/sentry_envelope_vm_test.dart | 10 +++-- 7 files changed, 43 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 875d8f56e3..149a454b70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +* Feat: Add support for attachments (#505) + # 6.0.0-beta.1 * Feat: Browser detection (#502) @@ -8,7 +10,6 @@ * Feat: Add DeduplicationEventProcessor (#498) * Feat: Capture failed requests as event (#473) * Feat: `beforeSend` callback accepts async code (#494) -* Feat: Add support for attachments (#505) ## Breaking Changes: diff --git a/dart/lib/src/scope.dart b/dart/lib/src/scope.dart index 73a7cbd96b..89b003df20 100644 --- a/dart/lib/src/scope.dart +++ b/dart/lib/src/scope.dart @@ -120,7 +120,7 @@ class Scope { _breadcrumbs.add(breadcrumb); } - void addAttachment(SentryAttachment attachment) async { + void addAttachment(SentryAttachment attachment) { _attachements.add(attachment); } @@ -299,7 +299,7 @@ class Scope { }); for (final attachment in _attachements) { - clone._attachements.add(attachment); + clone.addAttachment(attachment); } return clone; diff --git a/dart/lib/src/sentry_attachment/sentry_attachment.dart b/dart/lib/src/sentry_attachment/sentry_attachment.dart index 89f82ed9b0..33d609e097 100644 --- a/dart/lib/src/sentry_attachment/sentry_attachment.dart +++ b/dart/lib/src/sentry_attachment/sentry_attachment.dart @@ -8,13 +8,33 @@ typedef ContentLoader = FutureOr Function(); /// Arbitrary content which gets attached to an event. class SentryAttachment { + /// Standard attachment without special meaning. + static const String typeAttachmentDefault = 'event.attachment'; + + /// Minidump file that creates an error event and is symbolicated. + /// The file should start with the `MDMP` magic bytes. + static const String typeMinidump = 'event.minidump'; + + /// Apple crash report file that creates an error event and is symbolicated. + static const String typeAppleCrashReport = 'event.applecrashreport'; + + /// XML file containing UE4 crash meta data. + /// During event ingestion, event contexts and extra fields are extracted from + /// this file. + static const String typeUnrealContext = 'unreal.context'; + + /// Plain-text log file obtained from UE4 crashes. + /// During event ingestion, the last logs are extracted into event + /// breadcrumbs. + static const String typeUnrealLogs = 'unreal.logs'; + SentryAttachment.fromLoader({ required ContentLoader loader, required this.filename, String? attachmentType, this.contentType, }) : _loader = loader, - attachmentType = attachmentType ?? AttachmentType.attachment; + attachmentType = attachmentType ?? typeAttachmentDefault; /// Creates an [SentryAttachment] from a [Uint8List] SentryAttachment.fromUint8List( @@ -72,26 +92,3 @@ class SentryAttachment { /// Inferred by Sentry if it's not given. final String? contentType; } - -/// Attachment type. -class AttachmentType { - /// Standard attachment without special meaning. - static const String attachment = 'event.attachment'; - - /// Minidump file that creates an error event and is symbolicated. - /// The file should start with the `MDMP` magic bytes. - static const String minidump = 'event.minidump'; - - /// Apple crash report file that creates an error event and is symbolicated. - static const String appleCrashReport = 'event.applecrashreport'; - - /// XML file containing UE4 crash meta data. - /// During event ingestion, event contexts and extra fields are extracted from - /// this file. - static const String unrealContext = 'unreal.context'; - - /// Plain-text log file obtained from UE4 crashes. - /// During event ingestion, the last logs are extracted into event - /// breadcrumbs. - static const String unrealLogs = 'unreal.logs'; -} diff --git a/dart/lib/src/sentry_envelope.dart b/dart/lib/src/sentry_envelope.dart index cdd455e9c5..6de2f69a4b 100644 --- a/dart/lib/src/sentry_envelope.dart +++ b/dart/lib/src/sentry_envelope.dart @@ -37,8 +37,11 @@ class SentryEnvelope { yield utf8.encode(jsonEncode(header.toJson())); final newLineData = utf8.encode('\n'); for (final item in items) { - yield newLineData; - yield await item.envelopeItemStream(); + final itemStream = await item.envelopeItemStream(); + if (itemStream.isNotEmpty) { + yield newLineData; + yield itemStream; + } } } } diff --git a/dart/test/sentry_attachment_test.dart b/dart/test/sentry_attachment_test.dart index ccf00da2f6..b4d47bbfdd 100644 --- a/dart/test/sentry_attachment_test.dart +++ b/dart/test/sentry_attachment_test.dart @@ -13,7 +13,7 @@ void main() { loader: () => Uint8List.fromList([0, 0, 0, 0]), filename: 'test.txt', ); - expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); expect(attachment.contentType, isNull); expect(attachment.filename, 'test.txt'); await expectLater(await attachment.bytes, [0, 0, 0, 0]); @@ -21,7 +21,7 @@ void main() { test('fromIntList', () async { final attachment = SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'); - expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); expect(attachment.contentType, isNull); expect(attachment.filename, 'test.txt'); await expectLater(await attachment.bytes, [0, 0, 0, 0]); @@ -32,7 +32,7 @@ void main() { Uint8List.fromList([0, 0, 0, 0]), 'test.txt', ); - expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); expect(attachment.contentType, isNull); expect(attachment.filename, 'test.txt'); await expectLater(await attachment.bytes, [0, 0, 0, 0]); @@ -43,7 +43,7 @@ void main() { ByteData.sublistView(Uint8List.fromList([0, 0, 0, 0])), 'test.txt', ); - expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); expect(attachment.contentType, isNull); expect(attachment.filename, 'test.txt'); await expectLater(await attachment.bytes, [0, 0, 0, 0]); @@ -66,7 +66,7 @@ void main() { final attachmentEnvelope = transport.envelopes.first.items[1]; expect( attachmentEnvelope.header.attachmentType, - AttachmentType.attachment, + SentryAttachment.typeAttachmentDefault, ); expect( attachmentEnvelope.header.contentType, diff --git a/dart/test/sentry_attachment_test_io.dart b/dart/test/sentry_attachment_test_io.dart index 1ef161da43..2752355a2b 100644 --- a/dart/test/sentry_attachment_test_io.dart +++ b/dart/test/sentry_attachment_test_io.dart @@ -11,7 +11,7 @@ void main() { final file = File('test_resources/testfile.txt'); final attachment = IoSentryAttachment.fromFile(file); - expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); expect(attachment.contentType, isNull); expect(attachment.filename, 'testfile.txt'); await expectLater( @@ -21,7 +21,7 @@ void main() { test('fromPath', () async { final attachment = IoSentryAttachment.fromPath('test_resources/testfile.txt'); - expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); expect(attachment.contentType, isNull); expect(attachment.filename, 'testfile.txt'); await expectLater( diff --git a/dart/test/sentry_envelope_vm_test.dart b/dart/test/sentry_envelope_vm_test.dart index 497552a2f9..fc0396dc5a 100644 --- a/dart/test/sentry_envelope_vm_test.dart +++ b/dart/test/sentry_envelope_vm_test.dart @@ -53,8 +53,11 @@ void main() { final attachment = IoSentryAttachment.fromPath('this_path_does_not_exist.txt'); - final envelope = SentryEnvelope.fromEvent(event, sdkVersion, - attachments: [attachment]); + final envelope = SentryEnvelope.fromEvent( + event, + sdkVersion, + attachments: [attachment], + ); final data = (await envelope.envelopeStream().toList()).reduce((a, b) => a + b); @@ -292,6 +295,5 @@ final envelopeBinaryData = [ 48, 90, 34, - 125, - 10 + 125 ]; From cd6aae62ed8b5b88d2fc7749c15d887f81d2eaab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Fri, 2 Jul 2021 20:20:40 +0200 Subject: [PATCH 18/20] rename test file to be identified as test --- ...try_attachment_test_io.dart => sentry_attachment_io_test.dart} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dart/test/{sentry_attachment_test_io.dart => sentry_attachment_io_test.dart} (100%) diff --git a/dart/test/sentry_attachment_test_io.dart b/dart/test/sentry_attachment_io_test.dart similarity index 100% rename from dart/test/sentry_attachment_test_io.dart rename to dart/test/sentry_attachment_io_test.dart From 20af83732c5872103142a61a2d6af8f02956868e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Fri, 2 Jul 2021 20:35:22 +0200 Subject: [PATCH 19/20] fix --- flutter/test/flutter_sentry_attachment_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/test/flutter_sentry_attachment_test.dart b/flutter/test/flutter_sentry_attachment_test.dart index 26194c29ea..621683e6bb 100644 --- a/flutter/test/flutter_sentry_attachment_test.dart +++ b/flutter/test/flutter_sentry_attachment_test.dart @@ -12,7 +12,7 @@ void main() { bundle: TestAssetBundle(), ); - expect(attachment.attachmentType, AttachmentType.attachment); + expect(attachment.attachmentType, SentryAttachment.typeAttachmentDefault); expect(attachment.contentType, isNull); expect(attachment.filename, 'foobar.txt'); await expectLater(await attachment.bytes, [102, 111, 111, 32, 98, 97, 114]); From 5727ef1837b3860cb0efca9aa5b7667a1a2e15e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Ueko=CC=88tter?= Date: Mon, 5 Jul 2021 08:17:49 +0200 Subject: [PATCH 20/20] test fixture --- dart/test/sentry_attachment_test.dart | 29 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/dart/test/sentry_attachment_test.dart b/dart/test/sentry_attachment_test.dart index b4d47bbfdd..96bf0b53da 100644 --- a/dart/test/sentry_attachment_test.dart +++ b/dart/test/sentry_attachment_test.dart @@ -51,19 +51,22 @@ void main() { }); group('$Scope $SentryAttachment tests', () { + late Fixture fixture; + + setUp(() { + fixture = Fixture(); + }); + test('Sending with attachments', () async { - final options = SentryOptions(dsn: fakeDsn); - final transport = MockTransport(); - options.transport = transport; - final hub = Hub(options); - await hub.captureEvent(SentryEvent(), withScope: (scope) { + final sut = fixture.getSut(); + await sut.captureEvent(SentryEvent(), withScope: (scope) { scope.addAttachment( SentryAttachment.fromIntList([0, 0, 0, 0], 'test.txt'), ); }); - expect(transport.envelopes.length, 1); - expect(transport.envelopes.first.items.length, 2); - final attachmentEnvelope = transport.envelopes.first.items[1]; + expect(fixture.transport.envelopes.length, 1); + expect(fixture.transport.envelopes.first.items.length, 2); + final attachmentEnvelope = fixture.transport.envelopes.first.items[1]; expect( attachmentEnvelope.header.attachmentType, SentryAttachment.typeAttachmentDefault, @@ -83,3 +86,13 @@ void main() { }); }); } + +class Fixture { + MockTransport transport = MockTransport(); + + Hub getSut() { + final options = SentryOptions(dsn: fakeDsn); + options.transport = transport; + return Hub(options); + } +}