From a10d740a45194d12b4e52d3bb059f814ec8b083a Mon Sep 17 00:00:00 2001 From: rxlabz Date: Wed, 21 Oct 2020 11:34:07 +0200 Subject: [PATCH 01/34] remove the SentryResponse class --- dart/lib/src/client.dart | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 12ff28b3b1..a510deea2e 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -208,28 +208,3 @@ abstract class SentryClient { return headers; } } - -/// A response from Sentry.io. -/// -/// If [isSuccessful] the [eventId] field will contain the ID assigned to the -/// captured event by the Sentry.io backend. Otherwise, the [error] field will -/// contain the description of the error. -@immutable -class SentryResponse { - const SentryResponse.success({@required this.eventId}) - : isSuccessful = true, - error = null; - - const SentryResponse.failure(this.error) - : isSuccessful = false, - eventId = null; - - /// Whether event was submitted successfully. - final bool isSuccessful; - - /// The ID Sentry.io assigned to the submitted event for future reference. - final String eventId; - - /// Error message, if the response is not successful. - final String error; -} From 623c9049b0a83ee8b3b9a06ff04408a4faf9cc94 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Wed, 21 Oct 2020 14:11:06 +0200 Subject: [PATCH 02/34] refactor : add a Transport class --- dart/lib/src/browser_client.dart | 25 ++-- dart/lib/src/client.dart | 102 +++------------ dart/lib/src/io_client.dart | 68 +++++----- dart/lib/src/noop_client.dart | 31 +---- dart/lib/src/transport/body_encoder.dart | 17 +++ .../src/transport/body_encoder_browser.dart | 12 ++ dart/lib/src/transport/transport.dart | 118 ++++++++++++++++++ dart/test/test_utils.dart | 48 +++---- flutter/example/lib/main.dart | 2 +- 9 files changed, 237 insertions(+), 186 deletions(-) create mode 100644 dart/lib/src/transport/body_encoder.dart create mode 100644 dart/lib/src/transport/body_encoder_browser.dart create mode 100644 dart/lib/src/transport/transport.dart diff --git a/dart/lib/src/browser_client.dart b/dart/lib/src/browser_client.dart index b34c862e9d..2965fc2e0f 100644 --- a/dart/lib/src/browser_client.dart +++ b/dart/lib/src/browser_client.dart @@ -3,14 +3,15 @@ // found in the LICENSE file. /// A pure Dart client for Sentry.io crash reporting. -import 'dart:convert'; import 'dart:html' show window; import 'package:http/browser_client.dart'; +import 'package:meta/meta.dart'; import 'client.dart'; import 'protocol.dart'; import 'sentry_options.dart'; +import 'transport/transport.dart'; import 'version.dart'; SentryClient createSentryClient(SentryOptions options) => @@ -41,19 +42,19 @@ class SentryBrowserClient extends SentryClient { ); } - SentryBrowserClient._(SentryOptions options, {String origin, String platform}) + SentryBrowserClient._(SentryOptions options, + {String origin, @required String platform}) : super.base( options, + transport: Transport( + dsn: options.dsn, + httpClient: options.httpClient, + clock: options.clock, + compressPayload: false, + sdk: Sdk(name: browserSdkName, version: sdkVersion), + headersBuilder: SentryClient.buildHeaders, + platform: platform, + ), origin: origin, - sdk: Sdk(name: browserSdkName, version: sdkVersion), - platform: platform, ); - - @override - List bodyEncoder( - Map data, - Map headers, - ) => - // Gzip compression is implicit on browser - utf8.encode(json.encode(data)); } diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index a510deea2e..81696f953b 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; @@ -9,8 +8,8 @@ import 'client_stub.dart' if (dart.library.io) 'io_client.dart'; import 'protocol.dart'; import 'stack_trace.dart'; +import 'transport/transport.dart'; import 'utils.dart'; -import 'version.dart'; /// Logs crash reports and events to the Sentry.io service. abstract class SentryClient { @@ -22,36 +21,15 @@ abstract class SentryClient { SentryClient.base( this.options, { - String platform, this.origin, - Sdk sdk, - }) : _dsn = Dsn.parse(options.dsn), - _platform = platform ?? sdkPlatform, - sdk = sdk ?? Sdk(name: sdkName, version: sdkVersion); - - final Dsn _dsn; + @required this.transport, + }); @protected SentryOptions options; - /// The DSN URI. - @visibleForTesting - Uri get dsnUri => _dsn.uri; - - /// The Sentry.io public key for the project. @visibleForTesting - // ignore: invalid_use_of_visible_for_testing_member - String get publicKey => _dsn.publicKey; - - /// The Sentry.io secret key for the project. - @visibleForTesting - // ignore: invalid_use_of_visible_for_testing_member - String get secretKey => _dsn.secretKey; - - /// The ID issued by Sentry.io to your project. - /// - /// Attached to the event payload. - String get projectId => _dsn.projectId; + final Transport transport; /// Information about the current user. /// @@ -68,51 +46,16 @@ abstract class SentryClient { /// Use for browser stacktrace String origin; - /// Used by sentry to differentiate browser from io environment - final String _platform; - - final Sdk sdk; - - String get clientId => sdk.identifier; - - @visibleForTesting - String get postUri { - final port = dsnUri.hasPort && - ((dsnUri.scheme == 'http' && dsnUri.port != 80) || - (dsnUri.scheme == 'https' && dsnUri.port != 443)) - ? ':${dsnUri.port}' - : ''; - final pathLength = dsnUri.pathSegments.length; - String apiPath; - if (pathLength > 1) { - // some paths would present before the projectID in the dsnUri - apiPath = - (dsnUri.pathSegments.sublist(0, pathLength - 1) + ['api']).join('/'); - } else { - apiPath = 'api'; - } - return '${dsnUri.scheme}://${dsnUri.host}$port/$apiPath/$projectId/store/'; - } - /// Reports an [event] to Sentry.io. Future captureEvent( SentryEvent event, { StackFrameFilter stackFrameFilter, Scope scope, }) async { - final now = options.clock(); - var authHeader = 'Sentry sentry_version=6, sentry_client=$clientId, ' - 'sentry_timestamp=${now.millisecondsSinceEpoch}, sentry_key=$publicKey'; - if (secretKey != null) { - authHeader += ', sentry_secret=$secretKey'; - } - - final headers = buildHeaders(authHeader); + event = _processEvent(event, eventProcessors: options.eventProcessors); final data = { - 'project': projectId, 'event_id': event.eventId.toString(), - 'timestamp': formatDateAsIso8601WithSecondPrecision(event.timestamp), }; if (options.environmentAttributes != null) { @@ -132,22 +75,8 @@ abstract class SentryClient { ), into: data, ); - mergeAttributes({'platform': _platform}, into: data); - - final body = bodyEncoder(data, headers); - - final response = await options.httpClient.post( - postUri, - headers: headers, - body: body, - ); - - if (response.statusCode != 200) { - return SentryId.empty(); - } - final eventId = json.decode(response.body)['id']; - return eventId != null ? SentryId.fromId(eventId) : SentryId.empty(); + return transport.send(data); } /// Reports the [throwable] and optionally its [stackTrace] to Sentry.io. @@ -188,15 +117,22 @@ abstract class SentryClient { options.httpClient?.close(); } - @override - String toString() => '$SentryClient("$postUri")'; + SentryEvent _processEvent( + SentryEvent event, { + dynamic hint, + List eventProcessors, + }) { + for (final processor in eventProcessors) { + event = processor(event, hint); + } + return event; + } - @protected - List bodyEncoder(Map data, Map headers); + @override + String toString() => '$SentryClient("${options.dsn}")'; @protected - @mustCallSuper - Map buildHeaders(String authHeader) { + static Map buildHeaders(String authHeader) { final headers = { 'Content-Type': 'application/json', }; diff --git a/dart/lib/src/io_client.dart b/dart/lib/src/io_client.dart index a7f5d59297..827904268a 100644 --- a/dart/lib/src/io_client.dart +++ b/dart/lib/src/io_client.dart @@ -2,60 +2,48 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -/// A pure Dart client for Sentry.io crash reporting. -import 'dart:convert'; -import 'dart:io'; +import 'package:meta/meta.dart'; +/// A pure Dart client for Sentry.io crash reporting. import 'package:sentry/sentry.dart'; +import 'package:sentry/src/transport/transport.dart'; import 'client.dart'; +import 'protocol.dart'; SentryClient createSentryClient(SentryOptions options) => SentryIOClient(options); /// Logs crash reports and events to the Sentry.io service. class SentryIOClient extends SentryClient { - /// Instantiates a client using [dsn] issued to your project by Sentry.io as - /// the endpoint for submitting events. - /// - /// [environmentAttributes] contain event attributes that do not change over - /// the course of a program's lifecycle. These attributes will be added to - /// all events captured via this client. The following attributes often fall - /// under this category: [Event.serverName], [Event.release], [Event.environment]. - /// - /// If [compressPayload] is `true` the outgoing HTTP payloads are compressed - /// using gzip. Otherwise, the payloads are sent in plain UTF8-encoded JSON - /// text. If not specified, the compression is enabled by default. - /// - /// If [httpClient] is provided, it is used instead of the default client to - /// make HTTP calls to Sentry.io. This is useful in tests. - factory SentryIOClient(SentryOptions options) => SentryIOClient._(options); - - SentryIOClient._(SentryOptions options) : super.base(options); - - @override - Map buildHeaders(String authHeader) { - final headers = super.buildHeaders(authHeader); + /// Instantiates a client using [SentryOptions] + factory SentryIOClient(SentryOptions options) => + SentryIOClient._(options, platform: sdkPlatform); + + static const sdk = Sdk(name: sdkName, version: sdkVersion); + + SentryIOClient._(SentryOptions options, {@required String platform}) + : super.base( + options, + transport: Transport( + compressPayload: options.compressPayload, + httpClient: options.httpClient, + clock: options.clock, + sdk: sdk, + dsn: options.dsn, + headersBuilder: buildHeaders, + platform: platform, + ), + ); + + @protected + static Map buildHeaders(String authHeader) { + final headers = SentryClient.buildHeaders(authHeader); // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why // for web it use browser user agent - headers['User-Agent'] = clientId; + headers['User-Agent'] = sdk.identifier; return headers; } - - @override - List bodyEncoder( - Map data, - Map headers, - ) { - // [SentryIOClient] implement gzip compression - // gzip compression is not available on browser - var body = utf8.encode(json.encode(data)); - if (options.compressPayload) { - headers['Content-Encoding'] = 'gzip'; - body = gzip.encode(body); - } - return body; - } } diff --git a/dart/lib/src/noop_client.dart b/dart/lib/src/noop_client.dart index c5f5c7d438..0d1880b4ab 100644 --- a/dart/lib/src/noop_client.dart +++ b/dart/lib/src/noop_client.dart @@ -4,6 +4,7 @@ import 'client.dart'; import 'protocol.dart'; import 'scope.dart'; import 'sentry_options.dart'; +import 'transport/transport.dart'; class NoOpSentryClient implements SentryClient { NoOpSentryClient._(); @@ -24,14 +25,7 @@ class NoOpSentryClient implements SentryClient { String origin; @override - List bodyEncoder( - Map data, - Map headers, - ) => - []; - - @override - Map buildHeaders(String authHeader) => {}; + Transport transport; @override Future captureEvent(SentryEvent event, {stackFrameFilter, scope}) => @@ -51,29 +45,8 @@ class NoOpSentryClient implements SentryClient { }) => Future.value(SentryId.empty()); - @override - String get clientId => 'No-op'; - @override Future close() async { return; } - - @override - Uri get dsnUri => null; - - @override - String get postUri => null; - - @override - String get projectId => null; - - @override - String get publicKey => null; - - @override - Sdk get sdk => null; - - @override - String get secretKey => null; } diff --git a/dart/lib/src/transport/body_encoder.dart b/dart/lib/src/transport/body_encoder.dart new file mode 100644 index 0000000000..eb46bfc16d --- /dev/null +++ b/dart/lib/src/transport/body_encoder.dart @@ -0,0 +1,17 @@ +import 'dart:convert'; +import 'dart:io'; + +List bodyEncoder( + Map data, + Map headers, { + bool compressPayload, +}) { + // [SentryIOClient] implement gzip compression + // gzip compression is not available on browser + var body = utf8.encode(json.encode(data)); + if (compressPayload) { + headers['Content-Encoding'] = 'gzip'; + body = gzip.encode(body); + } + return body; +} diff --git a/dart/lib/src/transport/body_encoder_browser.dart b/dart/lib/src/transport/body_encoder_browser.dart new file mode 100644 index 0000000000..5e587c8bf6 --- /dev/null +++ b/dart/lib/src/transport/body_encoder_browser.dart @@ -0,0 +1,12 @@ +import 'dart:convert'; + +List bodyEncoder( + Map data, + Map headers, { + bool compressPayload, +}) { + // [SentryIOClient] implement gzip compression + // gzip compression is not available on browser + var body = utf8.encode(json.encode(data)); + return body; +} diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart new file mode 100644 index 0000000000..42aec9b7e1 --- /dev/null +++ b/dart/lib/src/transport/transport.dart @@ -0,0 +1,118 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:http/http.dart'; +import 'package:meta/meta.dart'; +import 'package:sentry/src/utils.dart'; + +import '../protocol.dart'; +import 'body_encoder_browser.dart' if (dart.library.io) 'body_encoder.dart'; + +typedef BodyEncoder = List Function( + Map data, + Map headers, { + bool compressPayload, +}); + +typedef HeadersBuilder = Map Function(String authHeader); + +class Transport { + final Client httpClient; + + final dynamic clock; + + final Dsn _dsn; + + final Sdk sdk; + + final HeadersBuilder headersBuilder; + + final bool compressPayload; + + /// Used by sentry to differentiate browser from io environment + final String platform; + + Transport({ + @required this.compressPayload, + @required this.httpClient, + @required this.clock, + @required this.sdk, + @required String dsn, + @required this.headersBuilder, + @required this.platform, + }) : _dsn = Dsn.parse(dsn); + + /// The DSN URI. + @visibleForTesting + Uri get dsnUri => _dsn.uri; + + /// The Sentry.io public key for the project. + @visibleForTesting + // ignore: invalid_use_of_visible_for_testing_member + String get publicKey => _dsn.publicKey; + + /// The Sentry.io secret key for the project. + @visibleForTesting + // ignore: invalid_use_of_visible_for_testing_member + String get secretKey => _dsn.secretKey; + + /// The ID issued by Sentry.io to your project. + /// + /// Attached to the event payload. + String get projectId => _dsn.projectId; + + String get clientId => sdk.identifier; + + @visibleForTesting + String get postUri { + final port = dsnUri.hasPort && + ((dsnUri.scheme == 'http' && dsnUri.port != 80) || + (dsnUri.scheme == 'https' && dsnUri.port != 443)) + ? ':${dsnUri.port}' + : ''; + final pathLength = dsnUri.pathSegments.length; + String apiPath; + if (pathLength > 1) { + // some paths would present before the projectID in the dsnUri + apiPath = + (dsnUri.pathSegments.sublist(0, pathLength - 1) + ['api']).join('/'); + } else { + apiPath = 'api'; + } + return '${dsnUri.scheme}://${dsnUri.host}$port/$apiPath/$projectId/store/'; + } + + Future send(Map data) async { + final now = clock(); + var authHeader = 'Sentry sentry_version=6, sentry_client=$clientId, ' + 'sentry_timestamp=${now.millisecondsSinceEpoch}, sentry_key=$publicKey'; + if (secretKey != null) { + authHeader += ', sentry_secret=$secretKey'; + } + + mergeAttributes(_getContext(now), into: data); + + final headers = headersBuilder(authHeader); + + final body = bodyEncoder(data, headers, compressPayload: compressPayload); + + final response = await httpClient.post( + postUri, + headers: headers, + body: body, + ); + + if (response.statusCode != 200) { + return SentryId.empty(); + } + + final eventId = json.decode(response.body)['id']; + return eventId != null ? SentryId.fromId(eventId) : SentryId.empty(); + } + + Map _getContext(DateTime now) => { + 'project': projectId, + 'timestamp': formatDateAsIso8601WithSecondPrecision(now), + 'platform': platform, + }; +} diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index 1ffd29a6cc..e58d66f02f 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -91,7 +91,7 @@ Future testCaptureException( expect('$sentryId', 'testeventid'); } - expect(postUri, client.postUri); + expect(postUri, client.transport.postUri); testHeaders( headers, @@ -182,40 +182,46 @@ Future testCaptureException( void runTest({Codec, List> gzip, bool isWeb = false}) { test('can parse DSN', () async { final client = SentryClient(SentryOptions(dsn: testDsn)); - expect(client.dsnUri, Uri.parse(testDsn)); - expect(client.postUri, 'https://sentry.example.com/api/1/store/'); - expect(client.publicKey, 'public'); - expect(client.secretKey, 'secret'); - expect(client.projectId, '1'); + expect(client.transport.dsnUri, Uri.parse(testDsn)); + expect(client.transport.postUri, 'https://sentry.example.com/api/1/store/'); + expect(client.transport.publicKey, 'public'); + expect(client.transport.secretKey, 'secret'); + expect(client.transport.projectId, '1'); await client.close(); }); test('can parse DSN without secret', () async { final client = SentryClient(SentryOptions(dsn: _testDsnWithoutSecret)); - expect(client.dsnUri, Uri.parse(_testDsnWithoutSecret)); - expect(client.postUri, 'https://sentry.example.com/api/1/store/'); - expect(client.publicKey, 'public'); - expect(client.secretKey, null); - expect(client.projectId, '1'); + expect(client.transport.dsnUri, Uri.parse(_testDsnWithoutSecret)); + expect(client.transport.postUri, 'https://sentry.example.com/api/1/store/'); + expect(client.transport.publicKey, 'public'); + expect(client.transport.secretKey, null); + expect(client.transport.projectId, '1'); await client.close(); }); test('can parse DSN with path', () async { final client = SentryClient(SentryOptions(dsn: _testDsnWithPath)); - expect(client.dsnUri, Uri.parse(_testDsnWithPath)); - expect(client.postUri, 'https://sentry.example.com/path/api/1/store/'); - expect(client.publicKey, 'public'); - expect(client.secretKey, 'secret'); - expect(client.projectId, '1'); + expect(client.transport.dsnUri, Uri.parse(_testDsnWithPath)); + expect( + client.transport.postUri, + 'https://sentry.example.com/path/api/1/store/', + ); + expect(client.transport.publicKey, 'public'); + expect(client.transport.secretKey, 'secret'); + expect(client.transport.projectId, '1'); await client.close(); }); test('can parse DSN with port', () async { final client = SentryClient(SentryOptions(dsn: _testDsnWithPort)); - expect(client.dsnUri, Uri.parse(_testDsnWithPort)); - expect(client.postUri, 'https://sentry.example.com:8888/api/1/store/'); - expect(client.publicKey, 'public'); - expect(client.secretKey, 'secret'); - expect(client.projectId, '1'); + expect(client.transport.dsnUri, Uri.parse(_testDsnWithPort)); + expect( + client.transport.postUri, + 'https://sentry.example.com:8888/api/1/store/', + ); + expect(client.transport.publicKey, 'public'); + expect(client.transport.secretKey, 'secret'); + expect(client.transport.projectId, '1'); await client.close(); }); test('sends client auth header without secret', () async { diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 4b12dd8ca7..bb64f97a47 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -40,7 +40,7 @@ Future main() async { stackTrace: stackTrace, // release is required on Web to match the source maps release: _release, - sdk: _sentry.sdk, + sdk: const Sdk(name: sdkName, version: sdkVersion), ); await _sentry.captureEvent(event); }); From 0dcb61343a696fc01598e11795a6d9b2c66b9e5f Mon Sep 17 00:00:00 2001 From: rxlabz Date: Wed, 21 Oct 2020 15:06:39 +0200 Subject: [PATCH 03/34] refactor : move the clock initialization to Transport --- dart/lib/sentry.dart | 5 +++-- dart/lib/src/transport/transport.dart | 16 +++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index 471852847b..222edeac9c 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -5,8 +5,9 @@ /// A pure Dart client for Sentry.io crash reporting. export 'src/client.dart'; export 'src/protocol.dart'; +export 'src/scope.dart'; export 'src/sentry.dart'; export 'src/sentry_options.dart'; -export 'src/version.dart'; -export 'src/scope.dart'; export 'src/sentry_options.dart'; +export 'src/transport/transport.dart'; +export 'src/version.dart'; diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 42aec9b7e1..ae8ae11315 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:http/http.dart'; import 'package:meta/meta.dart'; +import 'package:sentry/sentry.dart'; import 'package:sentry/src/utils.dart'; import '../protocol.dart'; @@ -16,11 +17,13 @@ typedef BodyEncoder = List Function( typedef HeadersBuilder = Map Function(String authHeader); +/// Used to provide timestamp for logging. +typedef ClockProvider = DateTime Function(); + +/// A transport is in charge of sending the event to the Sentry server. class Transport { final Client httpClient; - final dynamic clock; - final Dsn _dsn; final Sdk sdk; @@ -32,15 +35,18 @@ class Transport { /// Used by sentry to differentiate browser from io environment final String platform; + final ClockProvider _clock; + Transport({ @required this.compressPayload, @required this.httpClient, - @required this.clock, + @required ClockProvider clock, @required this.sdk, @required String dsn, @required this.headersBuilder, @required this.platform, - }) : _dsn = Dsn.parse(dsn); + }) : _dsn = Dsn.parse(dsn), + _clock = clock ?? getUtcDateTime; /// The DSN URI. @visibleForTesting @@ -83,7 +89,7 @@ class Transport { } Future send(Map data) async { - final now = clock(); + final now = _clock(); var authHeader = 'Sentry sentry_version=6, sentry_client=$clientId, ' 'sentry_timestamp=${now.millisecondsSinceEpoch}, sentry_key=$publicKey'; if (secretKey != null) { From c97c98b8386d65ba7a70531e45634aa9bb7198ba Mon Sep 17 00:00:00 2001 From: rxlabz Date: Wed, 21 Oct 2020 17:42:06 +0200 Subject: [PATCH 04/34] - refactor : applyScope + move origin to Transport --- dart/lib/src/browser_client.dart | 2 +- dart/lib/src/client.dart | 54 +++++++++++++++++++++------ dart/lib/src/noop_client.dart | 3 -- dart/lib/src/transport/transport.dart | 8 +++- dart/test/test_utils.dart | 50 ++++++++++++++----------- 5 files changed, 78 insertions(+), 39 deletions(-) diff --git a/dart/lib/src/browser_client.dart b/dart/lib/src/browser_client.dart index 2965fc2e0f..42957f4653 100644 --- a/dart/lib/src/browser_client.dart +++ b/dart/lib/src/browser_client.dart @@ -54,7 +54,7 @@ class SentryBrowserClient extends SentryClient { sdk: Sdk(name: browserSdkName, version: sdkVersion), headersBuilder: SentryClient.buildHeaders, platform: platform, + origin: origin, ), - origin: origin, ); } diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 81696f953b..56192748ad 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -21,7 +21,6 @@ abstract class SentryClient { SentryClient.base( this.options, { - this.origin, @required this.transport, }); @@ -43,9 +42,6 @@ abstract class SentryClient { /// * https://docs.sentry.io/learn/context/#capturing-the-user User userContext; - /// Use for browser stacktrace - String origin; - /// Reports an [event] to Sentry.io. Future captureEvent( SentryEvent event, { @@ -54,6 +50,8 @@ abstract class SentryClient { }) async { event = _processEvent(event, eventProcessors: options.eventProcessors); + event = _applyScope(event: event, scope: scope); + final data = { 'event_id': event.eventId.toString(), }; @@ -62,16 +60,10 @@ abstract class SentryClient { mergeAttributes(options.environmentAttributes.toJson(), into: data); } - // Merge the user context. - if (userContext != null) { - mergeAttributes({'user': userContext.toJson()}, - into: data); - } - mergeAttributes( event.toJson( stackFrameFilter: stackFrameFilter, - origin: origin, + origin: transport.origin, ), into: data, ); @@ -128,6 +120,46 @@ abstract class SentryClient { return event; } + Event _applyScope({@required Event event, @required Scope scope}) { + if (scope != null) { + // Merge the scope transaction. + if (event.transaction == null) { + event = event.copyWith(transaction: scope.transaction); + } + + // Merge the user context. + if (event.userContext == null) { + event = event.copyWith(userContext: scope.user); + } + + // Merge the scope fingerprint. + if (event.fingerprint == null) { + event = event.copyWith(fingerprint: scope.fingerprint); + } + + // Merge the scope breadcrumbs. + if (event.breadcrumbs == null) { + event = event.copyWith(breadcrumbs: scope.breadcrumbs); + } + + // Merge the scope tags. + if (event.tags == null) { + event = event.copyWith(tags: scope.tags); + } + + // Merge the scope extra. + if (event.extra == null) { + event = event.copyWith(extra: scope.extra); + } + + // Merge the scope level. + if (event.level == null) { + event = event.copyWith(level: scope.level); + } + } + return event; + } + @override String toString() => '$SentryClient("${options.dsn}")'; diff --git a/dart/lib/src/noop_client.dart b/dart/lib/src/noop_client.dart index 0d1880b4ab..ceea628062 100644 --- a/dart/lib/src/noop_client.dart +++ b/dart/lib/src/noop_client.dart @@ -21,9 +21,6 @@ class NoOpSentryClient implements SentryClient { @override SentryOptions options; - @override - String origin; - @override Transport transport; diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index ae8ae11315..178338806f 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -37,14 +37,18 @@ class Transport { final ClockProvider _clock; + /// Use for browser stacktrace + final String origin; + Transport({ + @required String dsn, @required this.compressPayload, @required this.httpClient, - @required ClockProvider clock, @required this.sdk, - @required String dsn, + @required ClockProvider clock, @required this.headersBuilder, @required this.platform, + this.origin, }) : _dsn = Dsn.parse(dsn), _clock = clock ?? getUtcDateTime; diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index e58d66f02f..03d96e37c9 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -339,30 +339,32 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { }); const clientUserContext = User( - id: 'client_user', - username: 'username', - email: 'email@email.com', - ipAddress: '127.0.0.1'); + id: 'client_user', + username: 'username', + email: 'email@email.com', + ipAddress: '127.0.0.1', + ); const eventUserContext = User( - id: 'event_user', - username: 'username', - email: 'email@email.com', - ipAddress: '127.0.0.1', - extras: {'foo': 'bar'}); + id: 'event_user', + username: 'username', + email: 'email@email.com', + ipAddress: '127.0.0.1', + extras: {'foo': 'bar'}, + ); - final client = SentryClient( - SentryOptions( - dsn: testDsn, - httpClient: httpMock, - clock: fakeClockProvider, - compressPayload: false, - environmentAttributes: SentryEvent( - serverName: 'test.server.com', - release: '1.2.3', - environment: 'staging', - ), + final options = SentryOptions( + dsn: testDsn, + httpClient: httpMock, + clock: fakeClockProvider, + compressPayload: false, + environmentAttributes: SentryEvent( + serverName: 'test.server.com', + release: '1.2.3', + environment: 'staging', ), ); + + final client = SentryClient(options); client.userContext = clientUserContext; try { @@ -379,9 +381,13 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { stackTrace: stackTrace, userContext: eventUserContext, ); - await client.captureEvent(eventWithoutContext); + await client.captureEvent(eventWithoutContext, + scope: Scope(options)..user = clientUserContext); expect(loggedUserId, clientUserContext.id); - await client.captureEvent(eventWithContext); + await client.captureEvent( + eventWithContext, + scope: Scope(options)..user = clientUserContext, + ); expect(loggedUserId, eventUserContext.id); } From 477fdac9d70b0156a1564a50797dedefd055eb2b Mon Sep 17 00:00:00 2001 From: rxlabz Date: Wed, 21 Oct 2020 17:47:57 +0200 Subject: [PATCH 05/34] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4917265a24..3cb27583c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Feature: sentry options #116 - Ref: SentryId generates UUID - Ref: Event now is SentryEvent and added GPU +- Ref: added Transport #123 # `package:sentry` changelog From b8fb3227bab48016784a6ac6519ae299da90b62d Mon Sep 17 00:00:00 2001 From: rxlabz Date: Thu, 22 Oct 2020 10:25:20 +0200 Subject: [PATCH 06/34] fix merge conflicts --- dart/lib/sentry.dart | 1 - dart/lib/src/client.dart | 3 ++- dart/lib/src/transport/transport.dart | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/dart/lib/sentry.dart b/dart/lib/sentry.dart index 222edeac9c..85a27fd53e 100644 --- a/dart/lib/sentry.dart +++ b/dart/lib/sentry.dart @@ -8,6 +8,5 @@ export 'src/protocol.dart'; export 'src/scope.dart'; export 'src/sentry.dart'; export 'src/sentry_options.dart'; -export 'src/sentry_options.dart'; export 'src/transport/transport.dart'; export 'src/version.dart'; diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 56192748ad..9dafe5f0bb 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -120,7 +120,8 @@ abstract class SentryClient { return event; } - Event _applyScope({@required Event event, @required Scope scope}) { + SentryEvent _applyScope( + {@required SentryEvent event, @required Scope scope}) { if (scope != null) { // Merge the scope transaction. if (event.transaction == null) { diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 178338806f..d2a1fa4f9d 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -3,10 +3,10 @@ import 'dart:convert'; import 'package:http/http.dart'; import 'package:meta/meta.dart'; -import 'package:sentry/sentry.dart'; import 'package:sentry/src/utils.dart'; import '../protocol.dart'; +import '../sentry_options.dart'; import 'body_encoder_browser.dart' if (dart.library.io) 'body_encoder.dart'; typedef BodyEncoder = List Function( @@ -17,9 +17,6 @@ typedef BodyEncoder = List Function( typedef HeadersBuilder = Map Function(String authHeader); -/// Used to provide timestamp for logging. -typedef ClockProvider = DateTime Function(); - /// A transport is in charge of sending the event to the Sentry server. class Transport { final Client httpClient; From 0944c6852ae16d3e6319baf6c8a571209894e13c Mon Sep 17 00:00:00 2001 From: rxlabz Date: Thu, 22 Oct 2020 11:44:46 +0200 Subject: [PATCH 07/34] move the headers definition strategy to transport --- dart/lib/src/browser_client.dart | 1 - dart/lib/src/client.dart | 13 ------------- dart/lib/src/io_client.dart | 12 ------------ dart/lib/src/transport/header_builder.dart | 17 +++++++++++++++++ .../src/transport/header_builder_browser.dart | 13 +++++++++++++ dart/lib/src/transport/transport.dart | 11 +++++------ 6 files changed, 35 insertions(+), 32 deletions(-) create mode 100644 dart/lib/src/transport/header_builder.dart create mode 100644 dart/lib/src/transport/header_builder_browser.dart diff --git a/dart/lib/src/browser_client.dart b/dart/lib/src/browser_client.dart index 42957f4653..39b576152b 100644 --- a/dart/lib/src/browser_client.dart +++ b/dart/lib/src/browser_client.dart @@ -52,7 +52,6 @@ class SentryBrowserClient extends SentryClient { clock: options.clock, compressPayload: false, sdk: Sdk(name: browserSdkName, version: sdkVersion), - headersBuilder: SentryClient.buildHeaders, platform: platform, origin: origin, ), diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 9dafe5f0bb..470b113994 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -163,17 +163,4 @@ abstract class SentryClient { @override String toString() => '$SentryClient("${options.dsn}")'; - - @protected - static Map buildHeaders(String authHeader) { - final headers = { - 'Content-Type': 'application/json', - }; - - if (authHeader != null) { - headers['X-Sentry-Auth'] = authHeader; - } - - return headers; - } } diff --git a/dart/lib/src/io_client.dart b/dart/lib/src/io_client.dart index 827904268a..ad3901c881 100644 --- a/dart/lib/src/io_client.dart +++ b/dart/lib/src/io_client.dart @@ -31,19 +31,7 @@ class SentryIOClient extends SentryClient { clock: options.clock, sdk: sdk, dsn: options.dsn, - headersBuilder: buildHeaders, platform: platform, ), ); - - @protected - static Map buildHeaders(String authHeader) { - final headers = SentryClient.buildHeaders(authHeader); - - // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why - // for web it use browser user agent - headers['User-Agent'] = sdk.identifier; - - return headers; - } } diff --git a/dart/lib/src/transport/header_builder.dart b/dart/lib/src/transport/header_builder.dart new file mode 100644 index 0000000000..fbd9b614ec --- /dev/null +++ b/dart/lib/src/transport/header_builder.dart @@ -0,0 +1,17 @@ +import '../protocol.dart'; + +Map buildHeaders(String authHeader, {Sdk sdk}) { + final headers = { + 'Content-Type': 'application/json', + }; + + if (authHeader != null) { + headers['X-Sentry-Auth'] = authHeader; + } + + // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why + // for web it use browser user agent + headers['User-Agent'] = sdk.identifier; + + return headers; +} diff --git a/dart/lib/src/transport/header_builder_browser.dart b/dart/lib/src/transport/header_builder_browser.dart new file mode 100644 index 0000000000..03a9a234ab --- /dev/null +++ b/dart/lib/src/transport/header_builder_browser.dart @@ -0,0 +1,13 @@ +import '../protocol.dart'; + +Map buildHeaders(String authHeader, {Sdk sdk}) { + final headers = { + 'Content-Type': 'application/json', + }; + + if (authHeader != null) { + headers['X-Sentry-Auth'] = authHeader; + } + + return headers; +} diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index d2a1fa4f9d..72d8d9905a 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -8,6 +8,7 @@ import 'package:sentry/src/utils.dart'; import '../protocol.dart'; import '../sentry_options.dart'; import 'body_encoder_browser.dart' if (dart.library.io) 'body_encoder.dart'; +import 'header_builder_browser.dart' if (dart.library.io) 'header_builder.dart'; typedef BodyEncoder = List Function( Map data, @@ -15,8 +16,6 @@ typedef BodyEncoder = List Function( bool compressPayload, }); -typedef HeadersBuilder = Map Function(String authHeader); - /// A transport is in charge of sending the event to the Sentry server. class Transport { final Client httpClient; @@ -25,8 +24,6 @@ class Transport { final Sdk sdk; - final HeadersBuilder headersBuilder; - final bool compressPayload; /// Used by sentry to differentiate browser from io environment @@ -43,7 +40,6 @@ class Transport { @required this.httpClient, @required this.sdk, @required ClockProvider clock, - @required this.headersBuilder, @required this.platform, this.origin, }) : _dsn = Dsn.parse(dsn), @@ -77,7 +73,9 @@ class Transport { (dsnUri.scheme == 'https' && dsnUri.port != 443)) ? ':${dsnUri.port}' : ''; + final pathLength = dsnUri.pathSegments.length; + String apiPath; if (pathLength > 1) { // some paths would present before the projectID in the dsnUri @@ -91,6 +89,7 @@ class Transport { Future send(Map data) async { final now = _clock(); + var authHeader = 'Sentry sentry_version=6, sentry_client=$clientId, ' 'sentry_timestamp=${now.millisecondsSinceEpoch}, sentry_key=$publicKey'; if (secretKey != null) { @@ -99,7 +98,7 @@ class Transport { mergeAttributes(_getContext(now), into: data); - final headers = headersBuilder(authHeader); + final headers = buildHeaders(authHeader, sdk: sdk); final body = bodyEncoder(data, headers, compressPayload: compressPayload); From 46829fc4f6f2f03d7da8708f576826e2501b091c Mon Sep 17 00:00:00 2001 From: rxlabz Date: Thu, 22 Oct 2020 13:22:58 +0200 Subject: [PATCH 08/34] - Instantiate Transport from options - move the postUri logic to Dsn --- dart/lib/src/browser_client.dart | 13 ++-- dart/lib/src/io_client.dart | 9 +-- dart/lib/src/protocol/dsn.dart | 31 ++++++++++ dart/lib/src/transport/transport.dart | 88 ++++++--------------------- dart/test/test_utils.dart | 44 +++++++------- 5 files changed, 81 insertions(+), 104 deletions(-) diff --git a/dart/lib/src/browser_client.dart b/dart/lib/src/browser_client.dart index 39b576152b..dcef75aab1 100644 --- a/dart/lib/src/browser_client.dart +++ b/dart/lib/src/browser_client.dart @@ -42,15 +42,14 @@ class SentryBrowserClient extends SentryClient { ); } - SentryBrowserClient._(SentryOptions options, - {String origin, @required String platform}) - : super.base( + SentryBrowserClient._( + SentryOptions options, { + String origin, + @required String platform, + }) : super.base( options, transport: Transport( - dsn: options.dsn, - httpClient: options.httpClient, - clock: options.clock, - compressPayload: false, + options: options, sdk: Sdk(name: browserSdkName, version: sdkVersion), platform: platform, origin: origin, diff --git a/dart/lib/src/io_client.dart b/dart/lib/src/io_client.dart index ad3901c881..37f86c85ee 100644 --- a/dart/lib/src/io_client.dart +++ b/dart/lib/src/io_client.dart @@ -25,13 +25,6 @@ class SentryIOClient extends SentryClient { SentryIOClient._(SentryOptions options, {@required String platform}) : super.base( options, - transport: Transport( - compressPayload: options.compressPayload, - httpClient: options.httpClient, - clock: options.clock, - sdk: sdk, - dsn: options.dsn, - platform: platform, - ), + transport: Transport(options: options, sdk: sdk, platform: platform), ); } diff --git a/dart/lib/src/protocol/dsn.dart b/dart/lib/src/protocol/dsn.dart index 35dc93c6dd..55c242100d 100644 --- a/dart/lib/src/protocol/dsn.dart +++ b/dart/lib/src/protocol/dsn.dart @@ -24,6 +24,26 @@ class Dsn { /// The DSN URI. final Uri uri; + String get postUri { + final port = uri.hasPort && + ((uri.scheme == 'http' && uri.port != 80) || + (uri.scheme == 'https' && uri.port != 443)) + ? ':${uri.port}' + : ''; + + final pathLength = uri.pathSegments.length; + + String apiPath; + if (pathLength > 1) { + // some paths would present before the projectID in the uri + apiPath = + (uri.pathSegments.sublist(0, pathLength - 1) + ['api']).join('/'); + } else { + apiPath = 'api'; + } + return '${uri.scheme}://${uri.host}$port/$apiPath/$projectId/store/'; + } + static Dsn parse(String dsn) { final uri = Uri.parse(dsn); final userInfo = uri.userInfo.split(':'); @@ -45,4 +65,15 @@ class Dsn { uri: uri, ); } + + String buildAuthHeader({int timestamp, String clientId}) { + var header = 'Sentry sentry_version=6, sentry_client=$clientId, ' + 'sentry_timestamp=$timestamp, sentry_key=${publicKey}'; + + if (secretKey != null) { + header += ', sentry_secret=${secretKey}'; + } + + return header; + } } diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 72d8d9905a..c130078ca1 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:http/http.dart'; import 'package:meta/meta.dart'; import 'package:sentry/src/utils.dart'; @@ -18,92 +17,45 @@ typedef BodyEncoder = List Function( /// A transport is in charge of sending the event to the Sentry server. class Transport { - final Client httpClient; + final SentryOptions _options; - final Dsn _dsn; - - final Sdk sdk; + @visibleForTesting + final Dsn dsn; - final bool compressPayload; + /// Use for browser stacktrace + final String origin; /// Used by sentry to differentiate browser from io environment final String platform; - final ClockProvider _clock; - - /// Use for browser stacktrace - final String origin; + final Sdk sdk; Transport({ - @required String dsn, - @required this.compressPayload, - @required this.httpClient, + @required SentryOptions options, @required this.sdk, - @required ClockProvider clock, @required this.platform, this.origin, - }) : _dsn = Dsn.parse(dsn), - _clock = clock ?? getUtcDateTime; - - /// The DSN URI. - @visibleForTesting - Uri get dsnUri => _dsn.uri; - - /// The Sentry.io public key for the project. - @visibleForTesting - // ignore: invalid_use_of_visible_for_testing_member - String get publicKey => _dsn.publicKey; - - /// The Sentry.io secret key for the project. - @visibleForTesting - // ignore: invalid_use_of_visible_for_testing_member - String get secretKey => _dsn.secretKey; - - /// The ID issued by Sentry.io to your project. - /// - /// Attached to the event payload. - String get projectId => _dsn.projectId; - - String get clientId => sdk.identifier; - - @visibleForTesting - String get postUri { - final port = dsnUri.hasPort && - ((dsnUri.scheme == 'http' && dsnUri.port != 80) || - (dsnUri.scheme == 'https' && dsnUri.port != 443)) - ? ':${dsnUri.port}' - : ''; - - final pathLength = dsnUri.pathSegments.length; - - String apiPath; - if (pathLength > 1) { - // some paths would present before the projectID in the dsnUri - apiPath = - (dsnUri.pathSegments.sublist(0, pathLength - 1) + ['api']).join('/'); - } else { - apiPath = 'api'; - } - return '${dsnUri.scheme}://${dsnUri.host}$port/$apiPath/$projectId/store/'; - } + }) : _options = options, + dsn = Dsn.parse(options.dsn); Future send(Map data) async { - final now = _clock(); + final now = _options.clock(); - var authHeader = 'Sentry sentry_version=6, sentry_client=$clientId, ' - 'sentry_timestamp=${now.millisecondsSinceEpoch}, sentry_key=$publicKey'; - if (secretKey != null) { - authHeader += ', sentry_secret=$secretKey'; - } + var authHeader = dsn.buildAuthHeader( + timestamp: now.millisecondsSinceEpoch, clientId: sdk.identifier); mergeAttributes(_getContext(now), into: data); final headers = buildHeaders(authHeader, sdk: sdk); - final body = bodyEncoder(data, headers, compressPayload: compressPayload); + final body = bodyEncoder( + data, + headers, + compressPayload: _options.compressPayload, + ); - final response = await httpClient.post( - postUri, + final response = await _options.httpClient.post( + dsn.postUri, headers: headers, body: body, ); @@ -117,7 +69,7 @@ class Transport { } Map _getContext(DateTime now) => { - 'project': projectId, + 'project': dsn.projectId, 'timestamp': formatDateAsIso8601WithSecondPrecision(now), 'platform': platform, }; diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index 03d96e37c9..6f5e2a8087 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -91,7 +91,7 @@ Future testCaptureException( expect('$sentryId', 'testeventid'); } - expect(postUri, client.transport.postUri); + expect(postUri, client.transport.dsn.postUri); testHeaders( headers, @@ -182,46 +182,48 @@ Future testCaptureException( void runTest({Codec, List> gzip, bool isWeb = false}) { test('can parse DSN', () async { final client = SentryClient(SentryOptions(dsn: testDsn)); - expect(client.transport.dsnUri, Uri.parse(testDsn)); - expect(client.transport.postUri, 'https://sentry.example.com/api/1/store/'); - expect(client.transport.publicKey, 'public'); - expect(client.transport.secretKey, 'secret'); - expect(client.transport.projectId, '1'); + expect(client.transport.dsn.uri, Uri.parse(testDsn)); + expect(client.transport.dsn.postUri, + 'https://sentry.example.com/api/1/store/'); + expect(client.transport.dsn.publicKey, 'public'); + expect(client.transport.dsn.secretKey, 'secret'); + expect(client.transport.dsn.projectId, '1'); await client.close(); }); test('can parse DSN without secret', () async { final client = SentryClient(SentryOptions(dsn: _testDsnWithoutSecret)); - expect(client.transport.dsnUri, Uri.parse(_testDsnWithoutSecret)); - expect(client.transport.postUri, 'https://sentry.example.com/api/1/store/'); - expect(client.transport.publicKey, 'public'); - expect(client.transport.secretKey, null); - expect(client.transport.projectId, '1'); + expect(client.transport.dsn.uri, Uri.parse(_testDsnWithoutSecret)); + expect(client.transport.dsn.postUri, + 'https://sentry.example.com/api/1/store/'); + expect(client.transport.dsn.publicKey, 'public'); + expect(client.transport.dsn.secretKey, null); + expect(client.transport.dsn.projectId, '1'); await client.close(); }); test('can parse DSN with path', () async { final client = SentryClient(SentryOptions(dsn: _testDsnWithPath)); - expect(client.transport.dsnUri, Uri.parse(_testDsnWithPath)); + expect(client.transport.dsn.uri, Uri.parse(_testDsnWithPath)); expect( - client.transport.postUri, + client.transport.dsn.postUri, 'https://sentry.example.com/path/api/1/store/', ); - expect(client.transport.publicKey, 'public'); - expect(client.transport.secretKey, 'secret'); - expect(client.transport.projectId, '1'); + expect(client.transport.dsn.publicKey, 'public'); + expect(client.transport.dsn.secretKey, 'secret'); + expect(client.transport.dsn.projectId, '1'); await client.close(); }); test('can parse DSN with port', () async { final client = SentryClient(SentryOptions(dsn: _testDsnWithPort)); - expect(client.transport.dsnUri, Uri.parse(_testDsnWithPort)); + expect(client.transport.dsn.uri, Uri.parse(_testDsnWithPort)); expect( - client.transport.postUri, + client.transport.dsn.postUri, 'https://sentry.example.com:8888/api/1/store/', ); - expect(client.transport.publicKey, 'public'); - expect(client.transport.secretKey, 'secret'); - expect(client.transport.projectId, '1'); + expect(client.transport.dsn.publicKey, 'public'); + expect(client.transport.dsn.secretKey, 'secret'); + expect(client.transport.dsn.projectId, '1'); await client.close(); }); test('sends client auth header without secret', () async { From 01d4e195e1e60db96746df49e3808f90278c9454 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Thu, 22 Oct 2020 13:55:22 +0200 Subject: [PATCH 09/34] handle event dropping in event processors --- dart/lib/src/client.dart | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 470b113994..4fbaa0cd2f 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -94,11 +94,7 @@ abstract class SentryClient { Scope scope, }) { final event = SentryEvent( - message: Message( - formatted, - template: template, - params: params, - ), + message: Message(formatted, template: template, params: params), level: level, timestamp: options.clock(), ); @@ -115,7 +111,18 @@ abstract class SentryClient { List eventProcessors, }) { for (final processor in eventProcessors) { - event = processor(event, hint); + try { + event = processor(event, hint); + } catch (err) { + options.logger( + SentryLevel.error, + 'An exception occurred while processing event by a processor : $err', + ); + } + if (event == null) { + options.logger(SentryLevel.debug, 'Event was dropped by a processor'); + break; + } } return event; } From fbd001e005b247c563587b7ddf3beac00bd34593 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Thu, 22 Oct 2020 14:03:45 +0200 Subject: [PATCH 10/34] fix client._applyScope : merge event and scope tags and extra --- dart/lib/src/client.dart | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 4fbaa0cd2f..07d35a70d6 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -127,8 +127,10 @@ abstract class SentryClient { return event; } - SentryEvent _applyScope( - {@required SentryEvent event, @required Scope scope}) { + SentryEvent _applyScope({ + @required SentryEvent event, + @required Scope scope, + }) { if (scope != null) { // Merge the scope transaction. if (event.transaction == null) { @@ -150,15 +152,12 @@ abstract class SentryClient { event = event.copyWith(breadcrumbs: scope.breadcrumbs); } + // TODO add tests // Merge the scope tags. - if (event.tags == null) { - event = event.copyWith(tags: scope.tags); - } + event = event.copyWith(tags: scope.tags..addAll(event.tags ?? {})); // Merge the scope extra. - if (event.extra == null) { - event = event.copyWith(extra: scope.extra); - } + event = event.copyWith(extra: scope.extra..addAll(event.extra ?? {})); // Merge the scope level. if (event.level == null) { From 4d3953110c030ef8bed039cce13be6f2830c217b Mon Sep 17 00:00:00 2001 From: rxlabz Date: Thu, 22 Oct 2020 14:14:27 +0200 Subject: [PATCH 11/34] remove client.toString() --- dart/lib/src/client.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 07d35a70d6..0d36cc7120 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -166,7 +166,4 @@ abstract class SentryClient { } return event; } - - @override - String toString() => '$SentryClient("${options.dsn}")'; } From 34fb52842258eac71ebdafda108befca1021a156 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Thu, 22 Oct 2020 14:20:27 +0200 Subject: [PATCH 12/34] fix a test --- dart/lib/src/client.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 0d36cc7120..3694039acb 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -154,10 +154,14 @@ abstract class SentryClient { // TODO add tests // Merge the scope tags. - event = event.copyWith(tags: scope.tags..addAll(event.tags ?? {})); + event = event.copyWith( + tags: scope.tags.map((key, value) => MapEntry(key, value)) + ..addAll(event.tags ?? {})); // Merge the scope extra. - event = event.copyWith(extra: scope.extra..addAll(event.extra ?? {})); + event = event.copyWith( + extra: scope.extra.map((key, value) => MapEntry(key, value)) + ..addAll(event.extra ?? {})); // Merge the scope level. if (event.level == null) { From 57798f518bb52106316f2b4ff7546caa39e3151c Mon Sep 17 00:00:00 2001 From: rxlabz Date: Thu, 22 Oct 2020 15:47:24 +0200 Subject: [PATCH 13/34] handle the event serialization in the transport layer --- dart/lib/src/client.dart | 19 +------------ dart/lib/src/transport/transport.dart | 41 ++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 192bdf85d2..3f60d72cae 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -9,7 +9,6 @@ import 'client_stub.dart' import 'protocol.dart'; import 'stack_trace.dart'; import 'transport/transport.dart'; -import 'utils.dart'; /// Logs crash reports and events to the Sentry.io service. abstract class SentryClient { @@ -53,23 +52,7 @@ abstract class SentryClient { event = _applyScope(event: event, scope: scope); - final data = { - 'event_id': event.eventId.toString(), - }; - - if (options.environmentAttributes != null) { - mergeAttributes(options.environmentAttributes.toJson(), into: data); - } - - mergeAttributes( - event.toJson( - stackFrameFilter: stackFrameFilter, - origin: transport.origin, - ), - into: data, - ); - - return transport.send(data); + return transport.send(event, stackFrameFilter: stackFrameFilter); } /// Reports the [throwable] and optionally its [stackTrace] to Sentry.io. diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index c130078ca1..4fb00d616c 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -6,6 +6,7 @@ import 'package:sentry/src/utils.dart'; import '../protocol.dart'; import '../sentry_options.dart'; +import '../stack_trace.dart'; import 'body_encoder_browser.dart' if (dart.library.io) 'body_encoder.dart'; import 'header_builder_browser.dart' if (dart.library.io) 'header_builder.dart'; @@ -38,16 +39,22 @@ class Transport { }) : _options = options, dsn = Dsn.parse(options.dsn); - Future send(Map data) async { + Future send( + SentryEvent event, { + StackFrameFilter stackFrameFilter, + }) async { final now = _options.clock(); var authHeader = dsn.buildAuthHeader( timestamp: now.millisecondsSinceEpoch, clientId: sdk.identifier); - - mergeAttributes(_getContext(now), into: data); - final headers = buildHeaders(authHeader, sdk: sdk); + final data = _getEventData( + event, + timeStamp: now, + stackFrameFilter: stackFrameFilter, + ); + final body = bodyEncoder( data, headers, @@ -68,6 +75,32 @@ class Transport { return eventId != null ? SentryId.fromId(eventId) : SentryId.empty(); } + Map _getEventData( + SentryEvent event, { + DateTime timeStamp, + StackFrameFilter stackFrameFilter, + }) { + final data = { + 'event_id': event.eventId.toString(), + }; + + if (_options.environmentAttributes != null) { + mergeAttributes(_options.environmentAttributes.toJson(), into: data); + } + + mergeAttributes( + event.toJson( + stackFrameFilter: stackFrameFilter, + origin: origin, + ), + into: data, + ); + + mergeAttributes(_getContext(timeStamp), into: data); + + return data; + } + Map _getContext(DateTime now) => { 'project': dsn.projectId, 'timestamp': formatDateAsIso8601WithSecondPrecision(now), From 1aa05d69558505e9b99cd24c744d38e752e9c378 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Thu, 22 Oct 2020 17:08:18 +0200 Subject: [PATCH 14/34] remove stackFrameFilter references --- dart/lib/src/transport/transport.dart | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 4fb00d616c..4f4dfe5be4 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -6,7 +6,6 @@ import 'package:sentry/src/utils.dart'; import '../protocol.dart'; import '../sentry_options.dart'; -import '../stack_trace.dart'; import 'body_encoder_browser.dart' if (dart.library.io) 'body_encoder.dart'; import 'header_builder_browser.dart' if (dart.library.io) 'header_builder.dart'; @@ -39,10 +38,7 @@ class Transport { }) : _options = options, dsn = Dsn.parse(options.dsn); - Future send( - SentryEvent event, { - StackFrameFilter stackFrameFilter, - }) async { + Future send(SentryEvent event) async { final now = _options.clock(); var authHeader = dsn.buildAuthHeader( @@ -52,7 +48,6 @@ class Transport { final data = _getEventData( event, timeStamp: now, - stackFrameFilter: stackFrameFilter, ); final body = bodyEncoder( @@ -78,7 +73,6 @@ class Transport { Map _getEventData( SentryEvent event, { DateTime timeStamp, - StackFrameFilter stackFrameFilter, }) { final data = { 'event_id': event.eventId.toString(), @@ -89,10 +83,7 @@ class Transport { } mergeAttributes( - event.toJson( - stackFrameFilter: stackFrameFilter, - origin: origin, - ), + event.toJson(origin: origin), into: data, ); From cb5e30955726a1b6ee3aba0fff602ca48c7b48a9 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Thu, 22 Oct 2020 19:06:42 +0200 Subject: [PATCH 15/34] add a CredentialBuilder --- dart/lib/src/protocol/dsn.dart | 13 --------- dart/lib/src/transport/transport.dart | 40 +++++++++++++++++++++++++-- dart/test/test_utils.dart | 9 +++--- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/dart/lib/src/protocol/dsn.dart b/dart/lib/src/protocol/dsn.dart index 55c242100d..23c5b3edf2 100644 --- a/dart/lib/src/protocol/dsn.dart +++ b/dart/lib/src/protocol/dsn.dart @@ -9,11 +9,9 @@ class Dsn { }); /// The Sentry.io public key for the project. - @visibleForTesting final String publicKey; /// The Sentry.io secret key for the project. - @visibleForTesting final String secretKey; /// The ID issued by Sentry.io to your project. @@ -65,15 +63,4 @@ class Dsn { uri: uri, ); } - - String buildAuthHeader({int timestamp, String clientId}) { - var header = 'Sentry sentry_version=6, sentry_client=$clientId, ' - 'sentry_timestamp=$timestamp, sentry_key=${publicKey}'; - - if (secretKey != null) { - header += ', sentry_secret=${secretKey}'; - } - - return header; - } } diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 4f4dfe5be4..2f94673857 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -30,19 +30,25 @@ class Transport { final Sdk sdk; + CredentialBuilder _credentialBuilder; + Transport({ @required SentryOptions options, @required this.sdk, @required this.platform, this.origin, }) : _options = options, - dsn = Dsn.parse(options.dsn); + dsn = Dsn.parse(options.dsn) { + _credentialBuilder = CredentialBuilder( + dsn: Dsn.parse(options.dsn), + clientId: sdk.identifier, + ); + } Future send(SentryEvent event) async { final now = _options.clock(); - var authHeader = dsn.buildAuthHeader( - timestamp: now.millisecondsSinceEpoch, clientId: sdk.identifier); + var authHeader = _credentialBuilder.build(now.millisecondsSinceEpoch); final headers = buildHeaders(authHeader, sdk: sdk); final data = _getEventData( @@ -98,3 +104,31 @@ class Transport { 'platform': platform, }; } + +class CredentialBuilder { + final String authHeader; + + CredentialBuilder({@required Dsn dsn, String clientId}) + : authHeader = buildAuthHeader( + publicKey: dsn.publicKey, + secretKey: dsn.secretKey, + clientId: clientId, + ); + + String build(int timestamp) => '$authHeader, sentry_timestamp=$timestamp'; + + static String buildAuthHeader({ + String publicKey, + String secretKey, + String clientId, + }) { + var header = 'Sentry sentry_version=6, sentry_client=$clientId, ' + 'sentry_key=${publicKey}'; + + if (secretKey != null) { + header += ', sentry_secret=${secretKey}'; + } + + return header; + } +} diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index 6f5e2a8087..2f7f2017b8 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -29,15 +29,16 @@ void testHeaders( 'Content-Type': 'application/json', 'X-Sentry-Auth': 'Sentry sentry_version=6, ' 'sentry_client=$sdkName/$sdkVersion, ' - 'sentry_timestamp=${fakeClockProvider().millisecondsSinceEpoch}, ' - 'sentry_key=public' + 'sentry_key=public, ' }; if (withSecret) { - expectedHeaders['X-Sentry-Auth'] += ', ' - 'sentry_secret=secret'; + expectedHeaders['X-Sentry-Auth'] += 'sentry_secret=secret, '; } + expectedHeaders['X-Sentry-Auth'] += + 'sentry_timestamp=${fakeClockProvider().millisecondsSinceEpoch}'; + if (withUserAgent) { expectedHeaders['User-Agent'] = '$sdkName/$sdkVersion'; } From e563d2c5efa10ae4b67d863f0cde3a9edf8fa70c Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 11:02:01 +0200 Subject: [PATCH 16/34] - headers configuration - fix scope.level precedence --- dart/lib/src/client.dart | 8 ++- dart/lib/src/transport/header_builder.dart | 11 ++-- .../src/transport/header_builder_browser.dart | 10 +-- dart/lib/src/transport/transport.dart | 65 +++++++++---------- 4 files changed, 44 insertions(+), 50 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 837c198265..2505f6e217 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -47,6 +47,11 @@ abstract class SentryClient { event = _applyScope(event: event, scope: scope); + event = event.copyWith( + /*timestamp: event.timestamp ?? options.clock(),*/ + platform: transport.platform, + ); + return transport.send(event); } @@ -79,6 +84,7 @@ abstract class SentryClient { level: level, timestamp: options.clock(), ); + return captureEvent(event, scope: scope, hint: hint); } @@ -145,7 +151,7 @@ abstract class SentryClient { ..addAll(event.extra ?? {})); // Merge the scope level. - if (event.level == null) { + if (scope.level == null) { event = event.copyWith(level: scope.level); } } diff --git a/dart/lib/src/transport/header_builder.dart b/dart/lib/src/transport/header_builder.dart index fbd9b614ec..15998007c7 100644 --- a/dart/lib/src/transport/header_builder.dart +++ b/dart/lib/src/transport/header_builder.dart @@ -1,16 +1,13 @@ import '../protocol.dart'; -Map buildHeaders(String authHeader, {Sdk sdk}) { +Map buildHeaders(/*String authHeader, */ {Sdk sdk}) { final headers = { 'Content-Type': 'application/json', + // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why + // for web it use browser user agent + 'User-Agent': sdk.identifier }; - if (authHeader != null) { - headers['X-Sentry-Auth'] = authHeader; - } - - // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why - // for web it use browser user agent headers['User-Agent'] = sdk.identifier; return headers; diff --git a/dart/lib/src/transport/header_builder_browser.dart b/dart/lib/src/transport/header_builder_browser.dart index 03a9a234ab..4560ab0035 100644 --- a/dart/lib/src/transport/header_builder_browser.dart +++ b/dart/lib/src/transport/header_builder_browser.dart @@ -1,13 +1,7 @@ import '../protocol.dart'; -Map buildHeaders(String authHeader, {Sdk sdk}) { - final headers = { - 'Content-Type': 'application/json', - }; - - if (authHeader != null) { - headers['X-Sentry-Auth'] = authHeader; - } +Map buildHeaders(/*String authHeader, */ {Sdk sdk}) { + final headers = {'Content-Type': 'application/json'}; return headers; } diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 2f94673857..4051153e87 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -32,39 +32,34 @@ class Transport { CredentialBuilder _credentialBuilder; + final Map _headers; + Transport({ @required SentryOptions options, @required this.sdk, @required this.platform, this.origin, }) : _options = options, - dsn = Dsn.parse(options.dsn) { + dsn = Dsn.parse(options.dsn), + _headers = buildHeaders(sdk: sdk) { _credentialBuilder = CredentialBuilder( - dsn: Dsn.parse(options.dsn), - clientId: sdk.identifier, - ); + dsn: Dsn.parse(options.dsn), + clientId: sdk.identifier, + clock: options.clock); } Future send(SentryEvent event) async { - final now = _options.clock(); - - var authHeader = _credentialBuilder.build(now.millisecondsSinceEpoch); - final headers = buildHeaders(authHeader, sdk: sdk); - - final data = _getEventData( - event, - timeStamp: now, - ); + final data = _getEventData(event, timeStamp: _options.clock()); final body = bodyEncoder( data, - headers, + _headers, compressPayload: _options.compressPayload, ); final response = await _options.httpClient.post( dsn.postUri, - headers: headers, + headers: _credentialBuilder.configure(_headers), body: body, ); @@ -80,43 +75,36 @@ class Transport { SentryEvent event, { DateTime timeStamp, }) { - final data = { - 'event_id': event.eventId.toString(), - }; + final data = event.toJson(origin: origin); + // TODO add this attributes to event in client if (_options.environmentAttributes != null) { mergeAttributes(_options.environmentAttributes.toJson(), into: data); } - mergeAttributes( - event.toJson(origin: origin), - into: data, - ); - - mergeAttributes(_getContext(timeStamp), into: data); + // TODO add this attributes to event in client + mergeAttributes(_getContext(), into: data); return data; } - Map _getContext(DateTime now) => { - 'project': dsn.projectId, - 'timestamp': formatDateAsIso8601WithSecondPrecision(now), - 'platform': platform, - }; + Map _getContext() => {'project': dsn.projectId}; } class CredentialBuilder { - final String authHeader; + final String _authHeader; + + final ClockProvider clock; + + int get timestamp => clock().millisecondsSinceEpoch; - CredentialBuilder({@required Dsn dsn, String clientId}) - : authHeader = buildAuthHeader( + CredentialBuilder({@required Dsn dsn, String clientId, @required this.clock}) + : _authHeader = buildAuthHeader( publicKey: dsn.publicKey, secretKey: dsn.secretKey, clientId: clientId, ); - String build(int timestamp) => '$authHeader, sentry_timestamp=$timestamp'; - static String buildAuthHeader({ String publicKey, String secretKey, @@ -131,4 +119,13 @@ class CredentialBuilder { return header; } + + Map configure(Map headers) { + return headers + ..addAll( + { + 'X-Sentry-Auth': '$_authHeader, sentry_timestamp=${timestamp}' + }, + ); + } } From 71b53c89236c4676d63322aa42f7f8c39f19d8e4 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 11:03:48 +0200 Subject: [PATCH 17/34] - fix scope.level precedence --- dart/lib/src/client.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 2505f6e217..b384a6f7e9 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -151,7 +151,7 @@ abstract class SentryClient { ..addAll(event.extra ?? {})); // Merge the scope level. - if (scope.level == null) { + if (scope.level != null) { event = event.copyWith(level: scope.level); } } From be3f0a2e00a5f3a74a042d229186ce1bdf3d0325 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 12:27:18 +0200 Subject: [PATCH 18/34] buildHeaders and fix timestamp test --- dart/lib/src/client.dart | 2 +- dart/lib/src/transport/header_builder.dart | 12 +++--------- .../src/transport/header_builder_browser.dart | 9 ++------- dart/lib/src/transport/transport.dart | 16 +++++++--------- dart/test/test_utils.dart | 1 + 5 files changed, 14 insertions(+), 26 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index b384a6f7e9..6b1b0e32b7 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -48,7 +48,7 @@ abstract class SentryClient { event = _applyScope(event: event, scope: scope); event = event.copyWith( - /*timestamp: event.timestamp ?? options.clock(),*/ + /*timestamp: options.clock(),*/ platform: transport.platform, ); diff --git a/dart/lib/src/transport/header_builder.dart b/dart/lib/src/transport/header_builder.dart index 15998007c7..76b304dfff 100644 --- a/dart/lib/src/transport/header_builder.dart +++ b/dart/lib/src/transport/header_builder.dart @@ -1,14 +1,8 @@ -import '../protocol.dart'; - -Map buildHeaders(/*String authHeader, */ {Sdk sdk}) { - final headers = { +Map buildHeaders({String sdkIdentifier}) { + return { 'Content-Type': 'application/json', // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why // for web it use browser user agent - 'User-Agent': sdk.identifier + 'User-Agent': sdkIdentifier }; - - headers['User-Agent'] = sdk.identifier; - - return headers; } diff --git a/dart/lib/src/transport/header_builder_browser.dart b/dart/lib/src/transport/header_builder_browser.dart index 4560ab0035..639e8d730b 100644 --- a/dart/lib/src/transport/header_builder_browser.dart +++ b/dart/lib/src/transport/header_builder_browser.dart @@ -1,7 +1,2 @@ -import '../protocol.dart'; - -Map buildHeaders(/*String authHeader, */ {Sdk sdk}) { - final headers = {'Content-Type': 'application/json'}; - - return headers; -} +Map buildHeaders({String sdkIdentifier}) => + {'Content-Type': 'application/json'}; diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 4051153e87..842edb1c47 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -41,15 +41,16 @@ class Transport { this.origin, }) : _options = options, dsn = Dsn.parse(options.dsn), - _headers = buildHeaders(sdk: sdk) { + _headers = buildHeaders(sdkIdentifier: sdk.identifier) { _credentialBuilder = CredentialBuilder( - dsn: Dsn.parse(options.dsn), - clientId: sdk.identifier, - clock: options.clock); + dsn: Dsn.parse(options.dsn), + clientId: sdk.identifier, + clock: options.clock, + ); } Future send(SentryEvent event) async { - final data = _getEventData(event, timeStamp: _options.clock()); + final data = _getEventData(event); final body = bodyEncoder( data, @@ -71,10 +72,7 @@ class Transport { return eventId != null ? SentryId.fromId(eventId) : SentryId.empty(); } - Map _getEventData( - SentryEvent event, { - DateTime timeStamp, - }) { + Map _getEventData(SentryEvent event) { final data = event.toJson(origin: origin); // TODO add this attributes to event in client diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index 2f7f2017b8..26b2f1f1dc 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -81,6 +81,7 @@ Future testCaptureException( serverName: 'test.server.com', release: '1.2.3', environment: 'staging', + timestamp: fakeClockProvider(), ), ), ); From 43b798c087b0086955ad0acedfc5982ff3b28f4a Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 13:12:16 +0200 Subject: [PATCH 19/34] - add a isWeb helper to define the event.platform - remove projectId from sended data --- dart/lib/src/client.dart | 5 +--- dart/lib/src/protocol/sentry_event.dart | 2 +- dart/lib/src/transport/header_builder.dart | 8 ------- .../src/transport/header_builder_browser.dart | 2 -- dart/lib/src/transport/transport.dart | 18 +++++++++------ dart/lib/src/utils.dart | 3 +++ dart/test/event_test.dart | 23 +++++++++++-------- dart/test/test_utils.dart | 2 -- 8 files changed, 30 insertions(+), 33 deletions(-) delete mode 100644 dart/lib/src/transport/header_builder.dart delete mode 100644 dart/lib/src/transport/header_builder_browser.dart diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 6b1b0e32b7..ad00a1bd86 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -47,10 +47,7 @@ abstract class SentryClient { event = _applyScope(event: event, scope: scope); - event = event.copyWith( - /*timestamp: options.clock(),*/ - platform: transport.platform, - ); + event = event.copyWith(platform: transport.platform); return transport.send(event); } diff --git a/dart/lib/src/protocol/sentry_event.dart b/dart/lib/src/protocol/sentry_event.dart index 25452363b1..68bb09dc46 100644 --- a/dart/lib/src/protocol/sentry_event.dart +++ b/dart/lib/src/protocol/sentry_event.dart @@ -33,7 +33,7 @@ class SentryEvent { this.breadcrumbs, this.sdk, }) : eventId = eventId ?? SentryId.newId(), - platform = platform ?? sdkPlatform, + platform = platform ?? (isWeb ? browserPlatform : sdkPlatform), timestamp = timestamp ?? getUtcDateTime(); /// Refers to the default fingerprinting algorithm. diff --git a/dart/lib/src/transport/header_builder.dart b/dart/lib/src/transport/header_builder.dart deleted file mode 100644 index 76b304dfff..0000000000 --- a/dart/lib/src/transport/header_builder.dart +++ /dev/null @@ -1,8 +0,0 @@ -Map buildHeaders({String sdkIdentifier}) { - return { - 'Content-Type': 'application/json', - // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why - // for web it use browser user agent - 'User-Agent': sdkIdentifier - }; -} diff --git a/dart/lib/src/transport/header_builder_browser.dart b/dart/lib/src/transport/header_builder_browser.dart deleted file mode 100644 index 639e8d730b..0000000000 --- a/dart/lib/src/transport/header_builder_browser.dart +++ /dev/null @@ -1,2 +0,0 @@ -Map buildHeaders({String sdkIdentifier}) => - {'Content-Type': 'application/json'}; diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 842edb1c47..f1728c4fd6 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -7,7 +7,6 @@ import 'package:sentry/src/utils.dart'; import '../protocol.dart'; import '../sentry_options.dart'; import 'body_encoder_browser.dart' if (dart.library.io) 'body_encoder.dart'; -import 'header_builder_browser.dart' if (dart.library.io) 'header_builder.dart'; typedef BodyEncoder = List Function( Map data, @@ -41,7 +40,7 @@ class Transport { this.origin, }) : _options = options, dsn = Dsn.parse(options.dsn), - _headers = buildHeaders(sdkIdentifier: sdk.identifier) { + _headers = _buildHeaders(sdkIdentifier: sdk.identifier) { _credentialBuilder = CredentialBuilder( dsn: Dsn.parse(options.dsn), clientId: sdk.identifier, @@ -80,13 +79,8 @@ class Transport { mergeAttributes(_options.environmentAttributes.toJson(), into: data); } - // TODO add this attributes to event in client - mergeAttributes(_getContext(), into: data); - return data; } - - Map _getContext() => {'project': dsn.projectId}; } class CredentialBuilder { @@ -127,3 +121,13 @@ class CredentialBuilder { ); } } + +Map _buildHeaders({String sdkIdentifier}) { + final headers = {'Content-Type': 'application/json'}; + // NOTE(lejard_h) overriding user agent on VM and Flutter not sure why + // for web it use browser user agent + if (!isWeb) { + headers['User-Agent'] = sdkIdentifier; + } + return headers; +} diff --git a/dart/lib/src/utils.dart b/dart/lib/src/utils.dart index d8ce547bbe..9cc4952dda 100644 --- a/dart/lib/src/utils.dart +++ b/dart/lib/src/utils.dart @@ -39,3 +39,6 @@ String formatDateAsIso8601WithSecondPrecision(DateTime date) { } return iso; } + +/// helper to detect a browser context +const isWeb = identical(1.0, 1); diff --git a/dart/test/event_test.dart b/dart/test/event_test.dart index 81abef84c8..37e7aa1914 100644 --- a/dart/test/event_test.dart +++ b/dart/test/event_test.dart @@ -4,6 +4,7 @@ import 'package:sentry/sentry.dart'; import 'package:sentry/src/stack_trace.dart'; +import 'package:sentry/src/utils.dart'; import 'package:test/test.dart'; void main() { @@ -26,15 +27,19 @@ void main() { }); test('$Sdk serializes', () { final event = SentryEvent( - eventId: SentryId.empty(), - timestamp: DateTime.utc(2019), - sdk: Sdk( - name: 'sentry.dart.flutter', - version: '4.3.2', - integrations: ['integration'], - packages: [Package('npm:@sentry/javascript', '1.3.4')])); + eventId: SentryId.empty(), + timestamp: DateTime.utc(2019), + sdk: Sdk( + name: 'sentry.dart.flutter', + version: '4.3.2', + integrations: ['integration'], + packages: [ + Package('npm:@sentry/javascript', '1.3.4'), + ], + ), + ); expect(event.toJson(), { - 'platform': 'dart', + 'platform': isWeb ? 'javascript' : 'dart', 'event_id': '00000000000000000000000000000000', 'timestamp': '2019-01-01T00:00:00', 'sdk': { @@ -89,7 +94,7 @@ void main() { breadcrumbs: breadcrumbs, ).toJson(), { - 'platform': 'dart', + 'platform': isWeb ? 'javascript' : 'dart', 'event_id': '00000000000000000000000000000000', 'timestamp': '2019-01-01T00:00:00', 'sdk': {'version': sdkVersion, 'name': 'sentry.dart'}, diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index 26b2f1f1dc..e174e50915 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -143,7 +143,6 @@ Future testCaptureException( expect(topFrame['function'], 'Object.wrapException'); expect(data, { - 'project': '1', 'event_id': sentryId.toString(), 'timestamp': '2017-01-02T00:00:00', 'platform': 'javascript', @@ -161,7 +160,6 @@ Future testCaptureException( expect(topFrame['function'], 'testCaptureException'); expect(data, { - 'project': '1', 'event_id': sentryId.toString(), 'timestamp': '2017-01-02T00:00:00', 'platform': 'dart', From 19dc91d210eb86892efdfd68c507d57c90a1ecfe Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Fri, 23 Oct 2020 13:35:52 +0200 Subject: [PATCH 20/34] feat: apply sample rate --- dart/lib/src/client.dart | 30 +++++++++++++++++++-- dart/lib/src/sentry_options.dart | 12 +++++++-- dart/test/mocks.dart | 2 ++ dart/test/sentry_client_test.dart | 44 +++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 dart/test/sentry_client_test.dart diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 6b1b0e32b7..ac950ce6d2 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math'; import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; @@ -17,13 +18,16 @@ abstract class SentryClient { /// `dart:html` is available, otherwise it will throw an unsupported error. factory SentryClient(SentryOptions options) => createSentryClient(options); - SentryClient.base(this.options, {@required this.transport}); + SentryClient.base(this.options, {@required this.transport}) { + _random = options.sampleRate == null ? null : Random(); + } @protected SentryOptions options; + // TODO: this should be removed and used options.transport @visibleForTesting - final Transport transport; + Transport transport; /// Information about the current user. /// @@ -37,6 +41,8 @@ abstract class SentryClient { /// * https://docs.sentry.io/learn/context/#capturing-the-user User userContext; + Random _random; + /// Reports an [event] to Sentry.io. Future captureEvent( SentryEvent event, { @@ -45,6 +51,11 @@ abstract class SentryClient { }) async { event = _processEvent(event, eventProcessors: options.eventProcessors); + // dropped by sampling or event processors + if (event == null) { + return Future.value(SentryId.empty()); + } + event = _applyScope(event: event, scope: scope); event = event.copyWith( @@ -97,6 +108,14 @@ abstract class SentryClient { dynamic hint, List eventProcessors, }) { + if (_sampleRate()) { + options.logger( + SentryLevel.debug, + 'Event ${event.eventId.toString()} was dropped due to sampling decision.', + ); + return null; + } + for (final processor in eventProcessors) { try { event = processor(event, hint); @@ -157,4 +176,11 @@ abstract class SentryClient { } return event; } + + bool _sampleRate() { + if (options.sampleRate != null && _random != null) { + return (options.sampleRate < _random.nextDouble()); + } + return true; + } } diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 58a8ccb725..1393faf258 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -102,8 +102,8 @@ class SentryOptions { /// Configures the sample rate as a percentage of events to be sent in the range of 0.0 to 1.0. if /// 1.0 is set it means that 100% of events are sent. If set to 0.1 only 10% of events will be - /// sent. Events are picked randomly. Default is 1.0 (disabled) - double sampleRate = 1.0; + /// sent. Events are picked randomly. Default is null (disabled) + double sampleRate; /// A list of string prefixes of module names that do not belong to the app, but rather third-party /// packages. Modules considered not to be part of the app will be hidden from stack traces by @@ -119,6 +119,14 @@ class SentryOptions { List get inAppIncludes => List.unmodifiable(_inAppIncludes); // TODO: transport, transportGate, connectionTimeoutMillis, readTimeoutMillis, hostnameVerifier, sslSocketFactory, proxy + Transport _transport; + + Transport get transport => _transport; + + set transport(Transport transport) { + // TODO: NoOp transport + _transport = transport; + } /// Sets the distribution. Think about it together with release and environment String dist; diff --git a/dart/test/mocks.dart b/dart/test/mocks.dart index e50a8f0abf..57ca1da64d 100644 --- a/dart/test/mocks.dart +++ b/dart/test/mocks.dart @@ -4,6 +4,8 @@ import 'package:sentry/src/protocol.dart'; class MockSentryClient extends Mock implements SentryClient {} +class MockTransport extends Mock implements Transport {} + final fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; final fakeException = Exception('Error'); diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart new file mode 100644 index 0000000000..c679dd5bf7 --- /dev/null +++ b/dart/test/sentry_client_test.dart @@ -0,0 +1,44 @@ +import 'package:mockito/mockito.dart'; +import 'package:sentry/sentry.dart'; +import 'package:test/test.dart'; + +import 'mocks.dart'; + +void main() { + group('SentryClient sampling', () { + SentryOptions options; + Transport transport; + + setUp(() { + options = SentryOptions(dsn: fakeDsn); + transport = MockTransport(); + }); + + test('captures event, sample rate is 100% enabled', () { + options.sampleRate = 1.0; + final client = SentryClient(options); + client.transport = transport; + client.captureEvent(fakeEvent); + + verify(client.transport.send(any)).called(1); + }); + + test('do not capture event, sample rate is 0% disabled', () { + options.sampleRate = 0.0; + final client = SentryClient(options); + client.transport = transport; + client.captureEvent(fakeEvent); + + verifyNever(client.transport.send(any)); + }); + + test('do not capture event, sample rate is null', () { + options.sampleRate = null; + final client = SentryClient(options); + client.transport = transport; + client.captureEvent(fakeEvent); + + verifyNever(client.transport.send(any)); + }); + }); +} From cec1f2174421d86cb290120468c8a962e30224c9 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Fri, 23 Oct 2020 13:36:23 +0200 Subject: [PATCH 21/34] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40815ea5a1..441bb07705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - Ref: Hint is passed across Sentry static class, Hub and Client #124 - Ref: Remove stackFrameFilter in favor of beforeSendCallback #125 - Ref: added Transport #123 +- Feat: apply sample rate # `package:sentry` changelog From 909d4f9aab6775250dfaf803eaf9dba59c31bf65 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 13:40:11 +0200 Subject: [PATCH 22/34] - add a isWeb helper to define the event.platform and sdkName - remove projectId from sended data --- dart/lib/src/browser_client.dart | 3 +-- dart/lib/src/io_client.dart | 9 +++++---- dart/lib/src/protocol/sentry_event.dart | 5 +++-- dart/lib/src/transport/transport.dart | 8 ++++---- dart/lib/src/version.dart | 6 +++++- dart/test/event_test.dart | 2 +- dart/test/test_utils.dart | 2 +- flutter/example/lib/main.dart | 3 ++- 8 files changed, 22 insertions(+), 16 deletions(-) diff --git a/dart/lib/src/browser_client.dart b/dart/lib/src/browser_client.dart index dcef75aab1..18fd8c9479 100644 --- a/dart/lib/src/browser_client.dart +++ b/dart/lib/src/browser_client.dart @@ -9,7 +9,6 @@ import 'package:http/browser_client.dart'; import 'package:meta/meta.dart'; import 'client.dart'; -import 'protocol.dart'; import 'sentry_options.dart'; import 'transport/transport.dart'; import 'version.dart'; @@ -50,7 +49,7 @@ class SentryBrowserClient extends SentryClient { options, transport: Transport( options: options, - sdk: Sdk(name: browserSdkName, version: sdkVersion), + sdkIdentifier: '${browserSdkName}/${sdkVersion}', platform: platform, origin: origin, ), diff --git a/dart/lib/src/io_client.dart b/dart/lib/src/io_client.dart index 37f86c85ee..f485067589 100644 --- a/dart/lib/src/io_client.dart +++ b/dart/lib/src/io_client.dart @@ -9,7 +9,6 @@ import 'package:sentry/sentry.dart'; import 'package:sentry/src/transport/transport.dart'; import 'client.dart'; -import 'protocol.dart'; SentryClient createSentryClient(SentryOptions options) => SentryIOClient(options); @@ -20,11 +19,13 @@ class SentryIOClient extends SentryClient { factory SentryIOClient(SentryOptions options) => SentryIOClient._(options, platform: sdkPlatform); - static const sdk = Sdk(name: sdkName, version: sdkVersion); - SentryIOClient._(SentryOptions options, {@required String platform}) : super.base( options, - transport: Transport(options: options, sdk: sdk, platform: platform), + transport: Transport( + options: options, + sdkIdentifier: '${sdkName}/${sdkVersion}', + platform: platform, + ), ); } diff --git a/dart/lib/src/protocol/sentry_event.dart b/dart/lib/src/protocol/sentry_event.dart index 68bb09dc46..07f4175c78 100644 --- a/dart/lib/src/protocol/sentry_event.dart +++ b/dart/lib/src/protocol/sentry_event.dart @@ -13,6 +13,7 @@ class SentryEvent { SentryId eventId, DateTime timestamp, String platform, + Sdk sdk, this.logger, this.serverName, this.release, @@ -31,10 +32,10 @@ class SentryEvent { this.userContext, this.contexts, this.breadcrumbs, - this.sdk, }) : eventId = eventId ?? SentryId.newId(), platform = platform ?? (isWeb ? browserPlatform : sdkPlatform), - timestamp = timestamp ?? getUtcDateTime(); + timestamp = timestamp ?? getUtcDateTime(), + sdk = sdk ?? Sdk(name: sdkName, version: sdkVersion); /// Refers to the default fingerprinting algorithm. /// diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index f1728c4fd6..f7a2fe1aaa 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -27,7 +27,7 @@ class Transport { /// Used by sentry to differentiate browser from io environment final String platform; - final Sdk sdk; + final String sdkIdentifier; CredentialBuilder _credentialBuilder; @@ -35,15 +35,15 @@ class Transport { Transport({ @required SentryOptions options, - @required this.sdk, + @required this.sdkIdentifier, @required this.platform, this.origin, }) : _options = options, dsn = Dsn.parse(options.dsn), - _headers = _buildHeaders(sdkIdentifier: sdk.identifier) { + _headers = _buildHeaders(sdkIdentifier: sdkIdentifier) { _credentialBuilder = CredentialBuilder( dsn: Dsn.parse(options.dsn), - clientId: sdk.identifier, + clientId: sdkIdentifier, clock: options.clock, ); } diff --git a/dart/lib/src/version.dart b/dart/lib/src/version.dart index a495d59cb1..6fed83fef9 100644 --- a/dart/lib/src/version.dart +++ b/dart/lib/src/version.dart @@ -8,11 +8,15 @@ /// This library contains Sentry.io SDK constants used by this package. library version; +import 'utils.dart'; + /// The SDK version reported to Sentry.io in the submitted events. const String sdkVersion = '4.0.0'; +String get sdkName => isWeb ? browserSdkName : ioSdkName; + /// The default SDK name reported to Sentry.io in the submitted events. -const String sdkName = 'sentry.dart'; +const String ioSdkName = 'sentry.dart'; /// The SDK name for web projects reported to Sentry.io in the submitted events. const String browserSdkName = 'sentry.dart.browser'; diff --git a/dart/test/event_test.dart b/dart/test/event_test.dart index 37e7aa1914..d945db9a9d 100644 --- a/dart/test/event_test.dart +++ b/dart/test/event_test.dart @@ -97,7 +97,7 @@ void main() { 'platform': isWeb ? 'javascript' : 'dart', 'event_id': '00000000000000000000000000000000', 'timestamp': '2019-01-01T00:00:00', - 'sdk': {'version': sdkVersion, 'name': 'sentry.dart'}, + 'sdk': {'version': sdkVersion, 'name': sdkName}, 'message': { 'formatted': 'test-message 1 2', 'message': 'test-message %d %d', diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index e174e50915..8602a284b8 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -146,7 +146,7 @@ Future testCaptureException( 'event_id': sentryId.toString(), 'timestamp': '2017-01-02T00:00:00', 'platform': 'javascript', - 'sdk': {'version': sdkVersion, 'name': 'sentry.dart'}, + 'sdk': {'version': sdkVersion, 'name': sdkName}, 'server_name': 'test.server.com', 'release': '1.2.3', 'environment': 'staging', diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index bb64f97a47..7c9dc8f024 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -40,7 +40,8 @@ Future main() async { stackTrace: stackTrace, // release is required on Web to match the source maps release: _release, - sdk: const Sdk(name: sdkName, version: sdkVersion), + + // sdk: const Sdk(name: sdkName, version: sdkVersion), ); await _sentry.captureEvent(event); }); From 8b600dc6b825c37092d5ef1e144473661fab4dab Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 14:45:44 +0200 Subject: [PATCH 23/34] - add a isWeb helper to define the event.platform and sdkName - remove projectId from sended data - move transport to options --- dart/lib/src/browser_client.dart | 12 +----- dart/lib/src/client.dart | 20 ++++++---- dart/lib/src/io_client.dart | 14 ++----- dart/lib/src/noop_client.dart | 4 -- dart/lib/src/sentry_options.dart | 14 ++++++- dart/lib/src/transport/noop_transport.dart | 17 ++++++++ dart/lib/src/transport/transport.dart | 4 -- dart/lib/src/version.dart | 7 +++- dart/test/test_utils.dart | 46 +++++++++++----------- 9 files changed, 75 insertions(+), 63 deletions(-) create mode 100644 dart/lib/src/transport/noop_transport.dart diff --git a/dart/lib/src/browser_client.dart b/dart/lib/src/browser_client.dart index 18fd8c9479..1d6e8be862 100644 --- a/dart/lib/src/browser_client.dart +++ b/dart/lib/src/browser_client.dart @@ -10,7 +10,6 @@ import 'package:meta/meta.dart'; import 'client.dart'; import 'sentry_options.dart'; -import 'transport/transport.dart'; import 'version.dart'; SentryClient createSentryClient(SentryOptions options) => @@ -32,7 +31,6 @@ class SentryBrowserClient extends SentryClient { options.httpClient ??= BrowserClient(); // origin is necessary for sentry to resolve stacktrace - origin ??= '${window.location.origin}/'; return SentryBrowserClient._( options, @@ -45,13 +43,5 @@ class SentryBrowserClient extends SentryClient { SentryOptions options, { String origin, @required String platform, - }) : super.base( - options, - transport: Transport( - options: options, - sdkIdentifier: '${browserSdkName}/${sdkVersion}', - platform: platform, - origin: origin, - ), - ); + }) : super.base(options, origin: '${window.location.origin}/'); } diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index ad00a1bd86..6d0a1a0e85 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -2,12 +2,12 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; +import 'package:sentry/src/transport/noop_transport.dart'; import 'client_stub.dart' if (dart.library.html) 'browser_client.dart' if (dart.library.io) 'io_client.dart'; import 'protocol.dart'; -import 'transport/transport.dart'; /// Logs crash reports and events to the Sentry.io service. abstract class SentryClient { @@ -17,13 +17,19 @@ abstract class SentryClient { /// `dart:html` is available, otherwise it will throw an unsupported error. factory SentryClient(SentryOptions options) => createSentryClient(options); - SentryClient.base(this.options, {@required this.transport}); + SentryClient.base(this.options, {String origin}) { + if (options.transport is NoOpTransport) { + options.transport = Transport( + options: options, + sdkIdentifier: '${sdkName}/${sdkVersion}', + origin: origin, + ); + } + } @protected - SentryOptions options; - @visibleForTesting - final Transport transport; + SentryOptions options; /// Information about the current user. /// @@ -47,9 +53,9 @@ abstract class SentryClient { event = _applyScope(event: event, scope: scope); - event = event.copyWith(platform: transport.platform); + event = event.copyWith(platform: sdkPlatform); - return transport.send(event); + return options.transport.send(event); } /// Reports the [throwable] and optionally its [stackTrace] to Sentry.io. diff --git a/dart/lib/src/io_client.dart b/dart/lib/src/io_client.dart index f485067589..08c1e67f5a 100644 --- a/dart/lib/src/io_client.dart +++ b/dart/lib/src/io_client.dart @@ -5,10 +5,9 @@ import 'package:meta/meta.dart'; /// A pure Dart client for Sentry.io crash reporting. -import 'package:sentry/sentry.dart'; -import 'package:sentry/src/transport/transport.dart'; - import 'client.dart'; +import 'sentry_options.dart'; +import 'version.dart'; SentryClient createSentryClient(SentryOptions options) => SentryIOClient(options); @@ -20,12 +19,5 @@ class SentryIOClient extends SentryClient { SentryIOClient._(options, platform: sdkPlatform); SentryIOClient._(SentryOptions options, {@required String platform}) - : super.base( - options, - transport: Transport( - options: options, - sdkIdentifier: '${sdkName}/${sdkVersion}', - platform: platform, - ), - ); + : super.base(options); } diff --git a/dart/lib/src/noop_client.dart b/dart/lib/src/noop_client.dart index 4657bdc48d..ab61083deb 100644 --- a/dart/lib/src/noop_client.dart +++ b/dart/lib/src/noop_client.dart @@ -4,7 +4,6 @@ import 'client.dart'; import 'protocol.dart'; import 'scope.dart'; import 'sentry_options.dart'; -import 'transport/transport.dart'; class NoOpSentryClient implements SentryClient { NoOpSentryClient._(); @@ -21,9 +20,6 @@ class NoOpSentryClient implements SentryClient { @override SentryOptions options; - @override - Transport transport; - @override Future captureEvent( SentryEvent event, { diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 58a8ccb725..1223d358b0 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -1,5 +1,7 @@ import 'package:http/http.dart'; import 'package:sentry/sentry.dart'; +import 'package:sentry/src/transport/noop_transport.dart'; + import 'diagnostic_logger.dart'; import 'hub.dart'; import 'protocol.dart'; @@ -118,7 +120,14 @@ class SentryOptions { List get inAppIncludes => List.unmodifiable(_inAppIncludes); - // TODO: transport, transportGate, connectionTimeoutMillis, readTimeoutMillis, hostnameVerifier, sslSocketFactory, proxy + Transport _transport = NoOpTransport(); + + Transport get transport => _transport; + + set transport(Transport transport) => + _transport = transport ?? NoOpTransport(); + + // TODO: transportGate, connectionTimeoutMillis, readTimeoutMillis, hostnameVerifier, sslSocketFactory, proxy /// Sets the distribution. Think about it together with release and environment String dist; @@ -139,8 +148,9 @@ class SentryOptions { this.environmentAttributes, this.compressPayload, this.httpClient, + Transport transport, ClockProvider clock = getUtcDateTime, - }) { + }) : _transport = transport ?? NoOpTransport() { _clock = clock; } diff --git a/dart/lib/src/transport/noop_transport.dart b/dart/lib/src/transport/noop_transport.dart new file mode 100644 index 0000000000..95b29832d9 --- /dev/null +++ b/dart/lib/src/transport/noop_transport.dart @@ -0,0 +1,17 @@ +import 'dart:async'; + +import 'package:sentry/sentry.dart'; + +class NoOpTransport implements Transport { + @override + Dsn get dsn => null; + + @override + String get origin => null; + + @override + String get sdkIdentifier => null; + + @override + Future send(SentryEvent event) => Future.value(SentryId.empty()); +} diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index f7a2fe1aaa..90271c23ca 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -24,9 +24,6 @@ class Transport { /// Use for browser stacktrace final String origin; - /// Used by sentry to differentiate browser from io environment - final String platform; - final String sdkIdentifier; CredentialBuilder _credentialBuilder; @@ -36,7 +33,6 @@ class Transport { Transport({ @required SentryOptions options, @required this.sdkIdentifier, - @required this.platform, this.origin, }) : _options = options, dsn = Dsn.parse(options.dsn), diff --git a/dart/lib/src/version.dart b/dart/lib/src/version.dart index 6fed83fef9..5b3341d885 100644 --- a/dart/lib/src/version.dart +++ b/dart/lib/src/version.dart @@ -24,7 +24,12 @@ const String browserSdkName = 'sentry.dart.browser'; /// The name of the SDK platform reported to Sentry.io in the submitted events. /// /// Used for IO version. -const String sdkPlatform = 'dart'; +String get sdkPlatform => isWeb ? browserPlatform : ioSdkPlatform; + +/// The name of the SDK platform reported to Sentry.io in the submitted events. +/// +/// Used for IO version. +const String ioSdkPlatform = 'dart'; /// Used to report browser Stacktrace to sentry. const String browserPlatform = 'javascript'; diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index 8602a284b8..ae98f682b6 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -93,14 +93,14 @@ Future testCaptureException( expect('$sentryId', 'testeventid'); } - expect(postUri, client.transport.dsn.postUri); + expect(postUri, client.options.transport.dsn.postUri); testHeaders( headers, fakeClockProvider, compressPayload: compressPayload, withUserAgent: !isWeb, - sdkName: isWeb ? browserSdkName : sdkName, + sdkName: sdkName, ); Map data; @@ -182,48 +182,48 @@ Future testCaptureException( void runTest({Codec, List> gzip, bool isWeb = false}) { test('can parse DSN', () async { final client = SentryClient(SentryOptions(dsn: testDsn)); - expect(client.transport.dsn.uri, Uri.parse(testDsn)); - expect(client.transport.dsn.postUri, + expect(client.options.transport.dsn.uri, Uri.parse(testDsn)); + expect(client.options.transport.dsn.postUri, 'https://sentry.example.com/api/1/store/'); - expect(client.transport.dsn.publicKey, 'public'); - expect(client.transport.dsn.secretKey, 'secret'); - expect(client.transport.dsn.projectId, '1'); + expect(client.options.transport.dsn.publicKey, 'public'); + expect(client.options.transport.dsn.secretKey, 'secret'); + expect(client.options.transport.dsn.projectId, '1'); await client.close(); }); test('can parse DSN without secret', () async { final client = SentryClient(SentryOptions(dsn: _testDsnWithoutSecret)); - expect(client.transport.dsn.uri, Uri.parse(_testDsnWithoutSecret)); - expect(client.transport.dsn.postUri, + expect(client.options.transport.dsn.uri, Uri.parse(_testDsnWithoutSecret)); + expect(client.options.transport.dsn.postUri, 'https://sentry.example.com/api/1/store/'); - expect(client.transport.dsn.publicKey, 'public'); - expect(client.transport.dsn.secretKey, null); - expect(client.transport.dsn.projectId, '1'); + expect(client.options.transport.dsn.publicKey, 'public'); + expect(client.options.transport.dsn.secretKey, null); + expect(client.options.transport.dsn.projectId, '1'); await client.close(); }); test('can parse DSN with path', () async { final client = SentryClient(SentryOptions(dsn: _testDsnWithPath)); - expect(client.transport.dsn.uri, Uri.parse(_testDsnWithPath)); + expect(client.options.transport.dsn.uri, Uri.parse(_testDsnWithPath)); expect( - client.transport.dsn.postUri, + client.options.transport.dsn.postUri, 'https://sentry.example.com/path/api/1/store/', ); - expect(client.transport.dsn.publicKey, 'public'); - expect(client.transport.dsn.secretKey, 'secret'); - expect(client.transport.dsn.projectId, '1'); + expect(client.options.transport.dsn.publicKey, 'public'); + expect(client.options.transport.dsn.secretKey, 'secret'); + expect(client.options.transport.dsn.projectId, '1'); await client.close(); }); test('can parse DSN with port', () async { final client = SentryClient(SentryOptions(dsn: _testDsnWithPort)); - expect(client.transport.dsn.uri, Uri.parse(_testDsnWithPort)); + expect(client.options.transport.dsn.uri, Uri.parse(_testDsnWithPort)); expect( - client.transport.dsn.postUri, + client.options.transport.dsn.postUri, 'https://sentry.example.com:8888/api/1/store/', ); - expect(client.transport.dsn.publicKey, 'public'); - expect(client.transport.dsn.secretKey, 'secret'); - expect(client.transport.dsn.projectId, '1'); + expect(client.options.transport.dsn.publicKey, 'public'); + expect(client.options.transport.dsn.secretKey, 'secret'); + expect(client.options.transport.dsn.projectId, '1'); await client.close(); }); test('sends client auth header without secret', () async { @@ -268,7 +268,7 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { withUserAgent: !isWeb, compressPayload: false, withSecret: false, - sdkName: isWeb ? browserSdkName : sdkName, + sdkName: sdkName, ); await client.close(); From 5493e19556d9931308206e230df3e14d5ca68dbc Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Fri, 23 Oct 2020 15:36:14 +0200 Subject: [PATCH 24/34] fix --- dart/lib/src/client.dart | 2 +- dart/test/sentry_client_test.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index ac950ce6d2..f9cf1fc68e 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -181,6 +181,6 @@ abstract class SentryClient { if (options.sampleRate != null && _random != null) { return (options.sampleRate < _random.nextDouble()); } - return true; + return false; } } diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart index c679dd5bf7..b378046ad9 100644 --- a/dart/test/sentry_client_test.dart +++ b/dart/test/sentry_client_test.dart @@ -32,13 +32,13 @@ void main() { verifyNever(client.transport.send(any)); }); - test('do not capture event, sample rate is null', () { + test('captures event, sample rate is null, disabled', () { options.sampleRate = null; final client = SentryClient(options); client.transport = transport; client.captureEvent(fakeEvent); - verifyNever(client.transport.send(any)); + verify(client.transport.send(any)).called(1); }); }); } From 621d6c42d7f5a34365f6742f69f98082c973c44c Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 15:40:37 +0200 Subject: [PATCH 25/34] update tests --- dart/lib/src/client.dart | 1 - dart/test/sentry_client_test.dart | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 4eecd83605..06db6143d2 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -29,7 +29,6 @@ abstract class SentryClient { } } - @protected @visibleForTesting SentryOptions options; diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart index c679dd5bf7..791dbe1f31 100644 --- a/dart/test/sentry_client_test.dart +++ b/dart/test/sentry_client_test.dart @@ -17,28 +17,28 @@ void main() { test('captures event, sample rate is 100% enabled', () { options.sampleRate = 1.0; final client = SentryClient(options); - client.transport = transport; + client.options.transport = transport; client.captureEvent(fakeEvent); - verify(client.transport.send(any)).called(1); + verify(client.options.transport.send(any)).called(1); }); test('do not capture event, sample rate is 0% disabled', () { options.sampleRate = 0.0; final client = SentryClient(options); - client.transport = transport; + client.options.transport = transport; client.captureEvent(fakeEvent); - verifyNever(client.transport.send(any)); + verifyNever(client.options.transport.send(any)); }); test('do not capture event, sample rate is null', () { options.sampleRate = null; final client = SentryClient(options); - client.transport = transport; + client.options.transport = transport; client.captureEvent(fakeEvent); - verifyNever(client.transport.send(any)); + verifyNever(client.options.transport.send(any)); }); }); } From 88526e2942d13f4782bc0018d4eea6aca8f87f23 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 15:48:08 +0200 Subject: [PATCH 26/34] remove the platform arg from Client ctor --- dart/lib/src/browser_client.dart | 10 +--------- dart/lib/src/io_client.dart | 9 ++------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/dart/lib/src/browser_client.dart b/dart/lib/src/browser_client.dart index 1d6e8be862..4d1f27d58b 100644 --- a/dart/lib/src/browser_client.dart +++ b/dart/lib/src/browser_client.dart @@ -6,11 +6,9 @@ import 'dart:html' show window; import 'package:http/browser_client.dart'; -import 'package:meta/meta.dart'; import 'client.dart'; import 'sentry_options.dart'; -import 'version.dart'; SentryClient createSentryClient(SentryOptions options) => SentryBrowserClient(options); @@ -31,17 +29,11 @@ class SentryBrowserClient extends SentryClient { options.httpClient ??= BrowserClient(); // origin is necessary for sentry to resolve stacktrace - - return SentryBrowserClient._( - options, - origin: origin, - platform: browserPlatform, - ); + return SentryBrowserClient._(options, origin: origin); } SentryBrowserClient._( SentryOptions options, { String origin, - @required String platform, }) : super.base(options, origin: '${window.location.origin}/'); } diff --git a/dart/lib/src/io_client.dart b/dart/lib/src/io_client.dart index 08c1e67f5a..015f5c5736 100644 --- a/dart/lib/src/io_client.dart +++ b/dart/lib/src/io_client.dart @@ -2,12 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:meta/meta.dart'; - /// A pure Dart client for Sentry.io crash reporting. import 'client.dart'; import 'sentry_options.dart'; -import 'version.dart'; SentryClient createSentryClient(SentryOptions options) => SentryIOClient(options); @@ -15,9 +12,7 @@ SentryClient createSentryClient(SentryOptions options) => /// Logs crash reports and events to the Sentry.io service. class SentryIOClient extends SentryClient { /// Instantiates a client using [SentryOptions] - factory SentryIOClient(SentryOptions options) => - SentryIOClient._(options, platform: sdkPlatform); + factory SentryIOClient(SentryOptions options) => SentryIOClient._(options); - SentryIOClient._(SentryOptions options, {@required String platform}) - : super.base(options); + SentryIOClient._(SentryOptions options) : super.base(options); } From 796b633fbb4e776a3731d0e2dc6b90dbfb272e24 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 16:01:23 +0200 Subject: [PATCH 27/34] set client.options private --- dart/lib/src/client.dart | 46 +++++++----------- dart/test/sentry_client_test.dart | 12 ++--- dart/test/test_utils.dart | 81 ++++++++++++++++--------------- 3 files changed, 65 insertions(+), 74 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index d6cb934ba8..b9919ee31e 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -18,31 +18,19 @@ abstract class SentryClient { /// `dart:html` is available, otherwise it will throw an unsupported error. factory SentryClient(SentryOptions options) => createSentryClient(options); - SentryClient.base(this.options, {String origin}) { - _random = options.sampleRate == null ? null : Random(); - if (options.transport is NoOpTransport) { - options.transport = Transport( - options: options, + SentryClient.base(this._options, {String origin}) { + _random = _options.sampleRate == null ? null : Random(); + if (_options.transport is NoOpTransport) { + _options.transport = Transport( + options: _options, sdkIdentifier: '${sdkName}/${sdkVersion}', origin: origin, ); } } - @visibleForTesting - SentryOptions options; - - /// Information about the current user. - /// - /// This information is sent with every logged event. If the value - /// of this field is updated, all subsequent events will carry the - /// new information. - /// - /// [Event.userContext] overrides the [User] context set here. - /// - /// See also: - /// * https://docs.sentry.io/learn/context/#capturing-the-user - User userContext; + //@visibleForTesting + SentryOptions _options; Random _random; @@ -52,7 +40,7 @@ abstract class SentryClient { Scope scope, dynamic hint, }) async { - event = _processEvent(event, eventProcessors: options.eventProcessors); + event = _processEvent(event, eventProcessors: _options.eventProcessors); // dropped by sampling or event processors if (event == null) { @@ -63,7 +51,7 @@ abstract class SentryClient { event = event.copyWith(platform: sdkPlatform); - return options.transport.send(event); + return _options.transport.send(event); } /// Reports the [throwable] and optionally its [stackTrace] to Sentry.io. @@ -76,7 +64,7 @@ abstract class SentryClient { final event = SentryEvent( exception: throwable, stackTrace: stackTrace, - timestamp: options.clock(), + timestamp: _options.clock(), ); return captureEvent(event, scope: scope, hint: hint); } @@ -93,14 +81,14 @@ abstract class SentryClient { final event = SentryEvent( message: Message(formatted, template: template, params: params), level: level, - timestamp: options.clock(), + timestamp: _options.clock(), ); return captureEvent(event, scope: scope, hint: hint); } void close() { - options.httpClient?.close(); + _options.httpClient?.close(); } SentryEvent _processEvent( @@ -109,7 +97,7 @@ abstract class SentryClient { List eventProcessors, }) { if (_sampleRate()) { - options.logger( + _options.logger( SentryLevel.debug, 'Event ${event.eventId.toString()} was dropped due to sampling decision.', ); @@ -120,13 +108,13 @@ abstract class SentryClient { try { event = processor(event, hint); } catch (err) { - options.logger( + _options.logger( SentryLevel.error, 'An exception occurred while processing event by a processor : $err', ); } if (event == null) { - options.logger(SentryLevel.debug, 'Event was dropped by a processor'); + _options.logger(SentryLevel.debug, 'Event was dropped by a processor'); break; } } @@ -178,8 +166,8 @@ abstract class SentryClient { } bool _sampleRate() { - if (options.sampleRate != null && _random != null) { - return (options.sampleRate < _random.nextDouble()); + if (_options.sampleRate != null && _random != null) { + return (_options.sampleRate < _random.nextDouble()); } return false; } diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart index d780836c64..80a7843855 100644 --- a/dart/test/sentry_client_test.dart +++ b/dart/test/sentry_client_test.dart @@ -17,28 +17,28 @@ void main() { test('captures event, sample rate is 100% enabled', () { options.sampleRate = 1.0; final client = SentryClient(options); - client.options.transport = transport; + options.transport = transport; client.captureEvent(fakeEvent); - verify(client.options.transport.send(any)).called(1); + verify(transport.send(any)).called(1); }); test('do not capture event, sample rate is 0% disabled', () { options.sampleRate = 0.0; final client = SentryClient(options); - client.options.transport = transport; + options.transport = transport; client.captureEvent(fakeEvent); - verifyNever(client.options.transport.send(any)); + verifyNever(transport.send(any)); }); test('captures event, sample rate is null, disabled', () { options.sampleRate = null; final client = SentryClient(options); - client.options.transport = transport; + options.transport = transport; client.captureEvent(fakeEvent); - verify(client.options.transport.send(any)).called(1); + verify(transport.send(any)).called(1); }); }); } diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index ae98f682b6..191c2e77df 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -70,22 +70,22 @@ Future testCaptureException( fail('Unexpected request on ${request.method} ${request.url} in HttpMock'); }); - var sentryId = SentryId.empty(); - final client = SentryClient( - SentryOptions( - dsn: testDsn, - httpClient: httpMock, - clock: fakeClockProvider, - compressPayload: compressPayload, - environmentAttributes: SentryEvent( - serverName: 'test.server.com', - release: '1.2.3', - environment: 'staging', - timestamp: fakeClockProvider(), - ), + final options = SentryOptions( + dsn: testDsn, + httpClient: httpMock, + clock: fakeClockProvider, + compressPayload: compressPayload, + environmentAttributes: SentryEvent( + serverName: 'test.server.com', + release: '1.2.3', + environment: 'staging', + timestamp: fakeClockProvider(), ), ); + var sentryId = SentryId.empty(); + final client = SentryClient(options); + try { throw ArgumentError('Test error'); } catch (error, stackTrace) { @@ -93,7 +93,7 @@ Future testCaptureException( expect('$sentryId', 'testeventid'); } - expect(postUri, client.options.transport.dsn.postUri); + expect(postUri, options.transport.dsn.postUri); testHeaders( headers, @@ -181,49 +181,53 @@ Future testCaptureException( void runTest({Codec, List> gzip, bool isWeb = false}) { test('can parse DSN', () async { - final client = SentryClient(SentryOptions(dsn: testDsn)); - expect(client.options.transport.dsn.uri, Uri.parse(testDsn)); - expect(client.options.transport.dsn.postUri, + final options = SentryOptions(dsn: testDsn); + final client = SentryClient(options); + expect(options.transport.dsn.uri, Uri.parse(testDsn)); + expect(options.transport.dsn.postUri, 'https://sentry.example.com/api/1/store/'); - expect(client.options.transport.dsn.publicKey, 'public'); - expect(client.options.transport.dsn.secretKey, 'secret'); - expect(client.options.transport.dsn.projectId, '1'); + expect(options.transport.dsn.publicKey, 'public'); + expect(options.transport.dsn.secretKey, 'secret'); + expect(options.transport.dsn.projectId, '1'); await client.close(); }); test('can parse DSN without secret', () async { - final client = SentryClient(SentryOptions(dsn: _testDsnWithoutSecret)); - expect(client.options.transport.dsn.uri, Uri.parse(_testDsnWithoutSecret)); - expect(client.options.transport.dsn.postUri, + final options = SentryOptions(dsn: _testDsnWithoutSecret); + final client = SentryClient(options); + expect(options.transport.dsn.uri, Uri.parse(_testDsnWithoutSecret)); + expect(options.transport.dsn.postUri, 'https://sentry.example.com/api/1/store/'); - expect(client.options.transport.dsn.publicKey, 'public'); - expect(client.options.transport.dsn.secretKey, null); - expect(client.options.transport.dsn.projectId, '1'); + expect(options.transport.dsn.publicKey, 'public'); + expect(options.transport.dsn.secretKey, null); + expect(options.transport.dsn.projectId, '1'); await client.close(); }); test('can parse DSN with path', () async { - final client = SentryClient(SentryOptions(dsn: _testDsnWithPath)); - expect(client.options.transport.dsn.uri, Uri.parse(_testDsnWithPath)); + final options = SentryOptions(dsn: _testDsnWithPath); + final client = SentryClient(options); + expect(options.transport.dsn.uri, Uri.parse(_testDsnWithPath)); expect( - client.options.transport.dsn.postUri, + options.transport.dsn.postUri, 'https://sentry.example.com/path/api/1/store/', ); - expect(client.options.transport.dsn.publicKey, 'public'); - expect(client.options.transport.dsn.secretKey, 'secret'); - expect(client.options.transport.dsn.projectId, '1'); + expect(options.transport.dsn.publicKey, 'public'); + expect(options.transport.dsn.secretKey, 'secret'); + expect(options.transport.dsn.projectId, '1'); await client.close(); }); test('can parse DSN with port', () async { - final client = SentryClient(SentryOptions(dsn: _testDsnWithPort)); - expect(client.options.transport.dsn.uri, Uri.parse(_testDsnWithPort)); + final options = SentryOptions(dsn: _testDsnWithPort); + final client = SentryClient(options); + expect(options.transport.dsn.uri, Uri.parse(_testDsnWithPort)); expect( - client.options.transport.dsn.postUri, + options.transport.dsn.postUri, 'https://sentry.example.com:8888/api/1/store/', ); - expect(client.options.transport.dsn.publicKey, 'public'); - expect(client.options.transport.dsn.secretKey, 'secret'); - expect(client.options.transport.dsn.projectId, '1'); + expect(options.transport.dsn.publicKey, 'public'); + expect(options.transport.dsn.secretKey, 'secret'); + expect(options.transport.dsn.projectId, '1'); await client.close(); }); test('sends client auth header without secret', () async { @@ -367,7 +371,6 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { ); final client = SentryClient(options); - client.userContext = clientUserContext; try { throw ArgumentError('Test error'); From fb8b9e092a4ad3505bb6304c8a147eb501913893 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 16:04:10 +0200 Subject: [PATCH 28/34] remove event.platform redefinition --- dart/lib/src/client.dart | 3 --- dart/lib/src/protocol/sentry_event.dart | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index b9919ee31e..7fb6bfa5f6 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -29,7 +29,6 @@ abstract class SentryClient { } } - //@visibleForTesting SentryOptions _options; Random _random; @@ -49,8 +48,6 @@ abstract class SentryClient { event = _applyScope(event: event, scope: scope); - event = event.copyWith(platform: sdkPlatform); - return _options.transport.send(event); } diff --git a/dart/lib/src/protocol/sentry_event.dart b/dart/lib/src/protocol/sentry_event.dart index 07f4175c78..a648668d0a 100644 --- a/dart/lib/src/protocol/sentry_event.dart +++ b/dart/lib/src/protocol/sentry_event.dart @@ -33,7 +33,7 @@ class SentryEvent { this.contexts, this.breadcrumbs, }) : eventId = eventId ?? SentryId.newId(), - platform = platform ?? (isWeb ? browserPlatform : sdkPlatform), + platform = platform ?? sdkPlatform, timestamp = timestamp ?? getUtcDateTime(), sdk = sdk ?? Sdk(name: sdkName, version: sdkVersion); From 1dbcba4eb97a9bcc1d6b54f44f0c3de09daa784d Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 16:10:54 +0200 Subject: [PATCH 29/34] set options.sdk if null --- dart/lib/src/browser_client.dart | 14 ++++++++------ dart/lib/src/client.dart | 2 +- dart/lib/src/io_client.dart | 7 ++++++- dart/lib/src/sentry_options.dart | 4 ++-- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/dart/lib/src/browser_client.dart b/dart/lib/src/browser_client.dart index 4d1f27d58b..26a8240d50 100644 --- a/dart/lib/src/browser_client.dart +++ b/dart/lib/src/browser_client.dart @@ -8,7 +8,9 @@ import 'dart:html' show window; import 'package:http/browser_client.dart'; import 'client.dart'; +import 'protocol.dart'; import 'sentry_options.dart'; +import 'version.dart'; SentryClient createSentryClient(SentryOptions options) => SentryBrowserClient(options); @@ -25,15 +27,15 @@ class SentryBrowserClient extends SentryClient { /// /// If [httpClient] is provided, it is used instead of the default client to /// make HTTP calls to Sentry.io. This is useful in tests. - factory SentryBrowserClient(SentryOptions options, {String origin}) { + factory SentryBrowserClient(SentryOptions options) { options.httpClient ??= BrowserClient(); + options.sdk ??= Sdk(name: sdkName, version: sdkVersion); + // origin is necessary for sentry to resolve stacktrace - return SentryBrowserClient._(options, origin: origin); + return SentryBrowserClient._(options); } - SentryBrowserClient._( - SentryOptions options, { - String origin, - }) : super.base(options, origin: '${window.location.origin}/'); + SentryBrowserClient._(SentryOptions options) + : super.base(options, origin: '${window.location.origin}/'); } diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 7fb6bfa5f6..3bbe066e12 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -23,7 +23,7 @@ abstract class SentryClient { if (_options.transport is NoOpTransport) { _options.transport = Transport( options: _options, - sdkIdentifier: '${sdkName}/${sdkVersion}', + sdkIdentifier: _options.sdk.identifier, origin: origin, ); } diff --git a/dart/lib/src/io_client.dart b/dart/lib/src/io_client.dart index 015f5c5736..8511553c06 100644 --- a/dart/lib/src/io_client.dart +++ b/dart/lib/src/io_client.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:sentry/sentry.dart'; + /// A pure Dart client for Sentry.io crash reporting. import 'client.dart'; import 'sentry_options.dart'; @@ -12,7 +14,10 @@ SentryClient createSentryClient(SentryOptions options) => /// Logs crash reports and events to the Sentry.io service. class SentryIOClient extends SentryClient { /// Instantiates a client using [SentryOptions] - factory SentryIOClient(SentryOptions options) => SentryIOClient._(options); + factory SentryIOClient(SentryOptions options) { + options.sdk ??= Sdk(name: sdkName, version: sdkVersion); + return SentryIOClient._(options); + } SentryIOClient._(SentryOptions options) : super.base(options); } diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 3713215456..83064ed6f7 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -135,8 +135,8 @@ class SentryOptions { /// The server name used in the Sentry messages. String serverName; - /// SdkVersion object that contains the Sentry Client Name and its version - Sdk sdkVersion; + /// Sdk object that contains the Sentry Client Name and its version + Sdk sdk; // TODO: Scope observers, enableScopeSync From 8b56bcedb35458c197a4d850e52e74c0a07816ce Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 16:19:55 +0200 Subject: [PATCH 30/34] remove fields from Transport Api --- dart/lib/src/noop_client.dart | 7 ------- dart/lib/src/transport/noop_transport.dart | 6 ------ dart/lib/src/transport/transport.dart | 11 +++++------ 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/dart/lib/src/noop_client.dart b/dart/lib/src/noop_client.dart index ab61083deb..412495fec0 100644 --- a/dart/lib/src/noop_client.dart +++ b/dart/lib/src/noop_client.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'client.dart'; import 'protocol.dart'; import 'scope.dart'; -import 'sentry_options.dart'; class NoOpSentryClient implements SentryClient { NoOpSentryClient._(); @@ -14,12 +13,6 @@ class NoOpSentryClient implements SentryClient { return _instance; } - @override - User userContext; - - @override - SentryOptions options; - @override Future captureEvent( SentryEvent event, { diff --git a/dart/lib/src/transport/noop_transport.dart b/dart/lib/src/transport/noop_transport.dart index 95b29832d9..1488b89258 100644 --- a/dart/lib/src/transport/noop_transport.dart +++ b/dart/lib/src/transport/noop_transport.dart @@ -6,12 +6,6 @@ class NoOpTransport implements Transport { @override Dsn get dsn => null; - @override - String get origin => null; - - @override - String get sdkIdentifier => null; - @override Future send(SentryEvent event) => Future.value(SentryId.empty()); } diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 90271c23ca..934208a989 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -22,9 +22,7 @@ class Transport { final Dsn dsn; /// Use for browser stacktrace - final String origin; - - final String sdkIdentifier; + final String _origin; CredentialBuilder _credentialBuilder; @@ -32,9 +30,10 @@ class Transport { Transport({ @required SentryOptions options, - @required this.sdkIdentifier, - this.origin, + @required String sdkIdentifier, + String origin, }) : _options = options, + _origin = origin, dsn = Dsn.parse(options.dsn), _headers = _buildHeaders(sdkIdentifier: sdkIdentifier) { _credentialBuilder = CredentialBuilder( @@ -68,7 +67,7 @@ class Transport { } Map _getEventData(SentryEvent event) { - final data = event.toJson(origin: origin); + final data = event.toJson(origin: _origin); // TODO add this attributes to event in client if (_options.environmentAttributes != null) { From daeb29e9804d2f25f25a1c3ea6490a89cb285f1c Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 16:25:00 +0200 Subject: [PATCH 31/34] private version consts --- dart/lib/src/version.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dart/lib/src/version.dart b/dart/lib/src/version.dart index 5b3341d885..820cd11bdf 100644 --- a/dart/lib/src/version.dart +++ b/dart/lib/src/version.dart @@ -13,23 +13,23 @@ import 'utils.dart'; /// The SDK version reported to Sentry.io in the submitted events. const String sdkVersion = '4.0.0'; -String get sdkName => isWeb ? browserSdkName : ioSdkName; +String get sdkName => isWeb ? _browserSdkName : _ioSdkName; /// The default SDK name reported to Sentry.io in the submitted events. -const String ioSdkName = 'sentry.dart'; +const String _ioSdkName = 'sentry.dart'; /// The SDK name for web projects reported to Sentry.io in the submitted events. -const String browserSdkName = 'sentry.dart.browser'; +const String _browserSdkName = 'sentry.dart.browser'; /// The name of the SDK platform reported to Sentry.io in the submitted events. /// /// Used for IO version. -String get sdkPlatform => isWeb ? browserPlatform : ioSdkPlatform; +String get sdkPlatform => isWeb ? _browserPlatform : _ioSdkPlatform; /// The name of the SDK platform reported to Sentry.io in the submitted events. /// /// Used for IO version. -const String ioSdkPlatform = 'dart'; +const String _ioSdkPlatform = 'dart'; /// Used to report browser Stacktrace to sentry. -const String browserPlatform = 'javascript'; +const String _browserPlatform = 'javascript'; From 098d58c8d4889162d2a5b8c1496b5f2ca342c4e5 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 16:32:39 +0200 Subject: [PATCH 32/34] remove transport from the Client ctor --- dart/lib/src/sentry_options.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 83064ed6f7..046850aa66 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -148,9 +148,8 @@ class SentryOptions { this.environmentAttributes, this.compressPayload, this.httpClient, - Transport transport, ClockProvider clock = getUtcDateTime, - }) : _transport = transport ?? NoOpTransport() { + }) { _clock = clock; } From 2833a940d7fabbed1bf2cdbcf7ce7ab02c3bd245 Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 17:21:27 +0200 Subject: [PATCH 33/34] remove options.environmentAttributes --- dart/lib/src/client.dart | 6 ++++ dart/lib/src/sentry_options.dart | 10 ------- dart/lib/src/transport/transport.dart | 13 +-------- dart/test/test_utils.dart | 41 +++++++++++---------------- 4 files changed, 23 insertions(+), 47 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 3bbe066e12..c06ed179b1 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -48,6 +48,12 @@ abstract class SentryClient { event = _applyScope(event: event, scope: scope); + event = event.copyWith( + serverName: _options.serverName, + environment: _options.environment, + release: _options.release, + ); + return _options.transport.send(event); } diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index 046850aa66..8adadbe9d7 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -16,15 +16,6 @@ class SentryOptions { /// just not send any events. String dsn; - /// Contains [Event] attributes that are automatically mixed into all events - /// captured through this client. - /// - /// This event is designed to contain static values that do not change from - /// event to event, such as local operating system version, the version of - /// Dart/Flutter SDK, etc. These attributes have lower precedence than those - /// supplied in the even passed to [capture]. - SentryEvent environmentAttributes; - /// If [compressPayload] is `true` the outgoing HTTP payloads are compressed /// using gzip. Otherwise, the payloads are sent in plain UTF8-encoded JSON /// text. If not specified, the compression is enabled by default. @@ -145,7 +136,6 @@ class SentryOptions { // TODO: those ctor params could be set on Sentry._setDefaultConfiguration or instantiate by default here SentryOptions({ this.dsn, - this.environmentAttributes, this.compressPayload, this.httpClient, ClockProvider clock = getUtcDateTime, diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 934208a989..728b960486 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -44,7 +44,7 @@ class Transport { } Future send(SentryEvent event) async { - final data = _getEventData(event); + final data = event.toJson(origin: _origin); final body = bodyEncoder( data, @@ -65,17 +65,6 @@ class Transport { final eventId = json.decode(response.body)['id']; return eventId != null ? SentryId.fromId(eventId) : SentryId.empty(); } - - Map _getEventData(SentryEvent event) { - final data = event.toJson(origin: _origin); - - // TODO add this attributes to event in client - if (_options.environmentAttributes != null) { - mergeAttributes(_options.environmentAttributes.toJson(), into: data); - } - - return data; - } } class CredentialBuilder { diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index 191c2e77df..f438ca1a8e 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -75,13 +75,10 @@ Future testCaptureException( httpClient: httpMock, clock: fakeClockProvider, compressPayload: compressPayload, - environmentAttributes: SentryEvent( - serverName: 'test.server.com', - release: '1.2.3', - environment: 'staging', - timestamp: fakeClockProvider(), - ), - ); + ) + ..serverName = 'test.server.com' + ..release = '1.2.3' + ..environment = 'staging'; var sentryId = SentryId.empty(); final client = SentryClient(options); @@ -250,12 +247,10 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { httpClient: httpMock, clock: fakeClockProvider, compressPayload: false, - environmentAttributes: SentryEvent( - serverName: 'test.server.com', - release: '1.2.3', - environment: 'staging', - ), - ), + ) + ..serverName = 'test.server.com' + ..release = '1.2.3' + ..environment = 'staging', ); try { @@ -307,12 +302,10 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { httpClient: httpMock, clock: fakeClockProvider, compressPayload: false, - environmentAttributes: SentryEvent( - serverName: 'test.server.com', - release: '1.2.3', - environment: 'staging', - ), - ), + ) + ..serverName = 'test.server.com' + ..release = '1.2.3' + ..environment = 'staging', ); try { @@ -363,12 +356,10 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { httpClient: httpMock, clock: fakeClockProvider, compressPayload: false, - environmentAttributes: SentryEvent( - serverName: 'test.server.com', - release: '1.2.3', - environment: 'staging', - ), - ); + ) + ..serverName = 'test.server.com' + ..release = '1.2.3' + ..environment = 'staging'; final client = SentryClient(options); From 35671d49b06ac551041bb1343d8bd519f923a16a Mon Sep 17 00:00:00 2001 From: rxlabz Date: Fri, 23 Oct 2020 18:00:14 +0200 Subject: [PATCH 34/34] event processing : platform --- dart/lib/src/client.dart | 12 ++++-------- dart/lib/src/protocol/sentry_event.dart | 3 +-- dart/lib/src/transport/transport.dart | 11 ++++------- dart/test/event_test.dart | 2 ++ 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index c06ed179b1..9f100fda14 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -21,11 +21,7 @@ abstract class SentryClient { SentryClient.base(this._options, {String origin}) { _random = _options.sampleRate == null ? null : Random(); if (_options.transport is NoOpTransport) { - _options.transport = Transport( - options: _options, - sdkIdentifier: _options.sdk.identifier, - origin: origin, - ); + _options.transport = Transport(options: _options, origin: origin); } } @@ -48,10 +44,12 @@ abstract class SentryClient { event = _applyScope(event: event, scope: scope); + // TODO create eventProcessors ? event = event.copyWith( serverName: _options.serverName, environment: _options.environment, release: _options.release, + platform: event.platform ?? sdkPlatform, ); return _options.transport.send(event); @@ -90,9 +88,7 @@ abstract class SentryClient { return captureEvent(event, scope: scope, hint: hint); } - void close() { - _options.httpClient?.close(); - } + void close() => _options.httpClient?.close(); SentryEvent _processEvent( SentryEvent event, { diff --git a/dart/lib/src/protocol/sentry_event.dart b/dart/lib/src/protocol/sentry_event.dart index a648668d0a..e721a485fc 100644 --- a/dart/lib/src/protocol/sentry_event.dart +++ b/dart/lib/src/protocol/sentry_event.dart @@ -12,8 +12,8 @@ class SentryEvent { SentryEvent({ SentryId eventId, DateTime timestamp, - String platform, Sdk sdk, + this.platform, this.logger, this.serverName, this.release, @@ -33,7 +33,6 @@ class SentryEvent { this.contexts, this.breadcrumbs, }) : eventId = eventId ?? SentryId.newId(), - platform = platform ?? sdkPlatform, timestamp = timestamp ?? getUtcDateTime(), sdk = sdk ?? Sdk(name: sdkName, version: sdkVersion); diff --git a/dart/lib/src/transport/transport.dart b/dart/lib/src/transport/transport.dart index 728b960486..3fcb51f62b 100644 --- a/dart/lib/src/transport/transport.dart +++ b/dart/lib/src/transport/transport.dart @@ -28,17 +28,14 @@ class Transport { final Map _headers; - Transport({ - @required SentryOptions options, - @required String sdkIdentifier, - String origin, - }) : _options = options, + Transport({@required SentryOptions options, String origin}) + : _options = options, _origin = origin, dsn = Dsn.parse(options.dsn), - _headers = _buildHeaders(sdkIdentifier: sdkIdentifier) { + _headers = _buildHeaders(sdkIdentifier: options.sdk.identifier) { _credentialBuilder = CredentialBuilder( dsn: Dsn.parse(options.dsn), - clientId: sdkIdentifier, + clientId: options.sdk.identifier, clock: options.clock, ); } diff --git a/dart/test/event_test.dart b/dart/test/event_test.dart index d945db9a9d..9d4d76c804 100644 --- a/dart/test/event_test.dart +++ b/dart/test/event_test.dart @@ -29,6 +29,7 @@ void main() { final event = SentryEvent( eventId: SentryId.empty(), timestamp: DateTime.utc(2019), + platform: sdkPlatform, sdk: Sdk( name: 'sentry.dart.flutter', version: '4.3.2', @@ -72,6 +73,7 @@ void main() { SentryEvent( eventId: SentryId.empty(), timestamp: timestamp, + platform: sdkPlatform, message: Message( 'test-message 1 2', template: 'test-message %d %d',