-
-
Notifications
You must be signed in to change notification settings - Fork 277
Refacto : add a Transport class #123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 25 commits
a10d740
623c904
0dcb613
c97c98b
477fdac
b8fb322
fd92e3f
0944c68
46829fc
01d4e19
fbd001e
4d39531
34fb528
2437fa4
57798f5
789e154
1aa05d6
9895142
cb5e309
e563d2c
71b53c8
be3f0a2
43b798c
19dc91d
cec1f21
909d4f9
8b600dc
4ff58a6
5493e19
621d6c4
e11f653
88526e2
796b633
fb8b9e0
1dbcba4
8b56bce
daeb29e
098d58c
2833a94
35671d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,13 @@ | ||
| import 'dart:async'; | ||
| import 'dart:convert'; | ||
|
|
||
| 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 'utils.dart'; | ||
| import 'version.dart'; | ||
|
|
||
| /// Logs crash reports and events to the Sentry.io service. | ||
| abstract class SentryClient { | ||
|
|
@@ -19,38 +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, { | ||
| String platform, | ||
| this.origin, | ||
| Sdk sdk, | ||
| }) : _dsn = Dsn.parse(options.dsn), | ||
| _platform = platform ?? sdkPlatform, | ||
| sdk = sdk ?? Sdk(name: sdkName, version: sdkVersion); | ||
|
|
||
| final Dsn _dsn; | ||
| SentryClient.base(this.options, {String origin}) { | ||
| if (options.transport is NoOpTransport) { | ||
| options.transport = Transport( | ||
| options: options, | ||
| sdkIdentifier: '${sdkName}/${sdkVersion}', | ||
| origin: origin, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| @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; | ||
| SentryOptions options; | ||
|
||
|
|
||
| /// Information about the current user. | ||
| /// | ||
|
|
@@ -64,88 +43,19 @@ abstract class SentryClient { | |
| /// * https://docs.sentry.io/learn/context/#capturing-the-user | ||
| User userContext; | ||
|
||
|
|
||
| /// 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<SentryId> captureEvent( | ||
| SentryEvent event, { | ||
| Scope scope, | ||
| dynamic hint, | ||
| }) 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 = <String, dynamic>{ | ||
| 'project': projectId, | ||
| 'event_id': event.eventId.toString(), | ||
| 'timestamp': formatDateAsIso8601WithSecondPrecision(event.timestamp), | ||
| }; | ||
| event = _applyScope(event: event, scope: scope); | ||
|
|
||
| if (options.environmentAttributes != null) { | ||
| mergeAttributes(options.environmentAttributes.toJson(), into: data); | ||
| } | ||
| event = event.copyWith(platform: sdkPlatform); | ||
|
|
||
| // Merge the user context. | ||
| if (userContext != null) { | ||
| mergeAttributes(<String, dynamic>{'user': userContext.toJson()}, | ||
| into: data); | ||
| } | ||
|
|
||
| mergeAttributes( | ||
| event.toJson( | ||
| origin: origin, | ||
| ), | ||
| into: data, | ||
| ); | ||
| mergeAttributes(<String, String>{'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 options.transport.send(event); | ||
| } | ||
|
|
||
| /// Reports the [throwable] and optionally its [stackTrace] to Sentry.io. | ||
|
|
@@ -173,63 +83,81 @@ abstract class SentryClient { | |
| dynamic hint, | ||
| }) { | ||
| final event = SentryEvent( | ||
| message: Message( | ||
| formatted, | ||
| template: template, | ||
| params: params, | ||
| ), | ||
| message: Message(formatted, template: template, params: params), | ||
| level: level, | ||
| timestamp: options.clock(), | ||
| ); | ||
|
|
||
| return captureEvent(event, scope: scope, hint: hint); | ||
| } | ||
|
|
||
| void close() { | ||
| options.httpClient?.close(); | ||
| } | ||
|
|
||
| @override | ||
| String toString() => '$SentryClient("$postUri")'; | ||
|
|
||
| @protected | ||
| List<int> bodyEncoder(Map<String, dynamic> data, Map<String, String> headers); | ||
|
|
||
| @protected | ||
| @mustCallSuper | ||
| Map<String, String> buildHeaders(String authHeader) { | ||
| final headers = { | ||
| 'Content-Type': 'application/json', | ||
| }; | ||
|
|
||
| if (authHeader != null) { | ||
| headers['X-Sentry-Auth'] = authHeader; | ||
| SentryEvent _processEvent( | ||
| SentryEvent event, { | ||
| dynamic hint, | ||
| List<EventProcessor> eventProcessors, | ||
| }) { | ||
| for (final processor in eventProcessors) { | ||
| 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 headers; | ||
| return event; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. still missing the execution of the beforeSendCallback -> |
||
| } | ||
| } | ||
|
|
||
| /// 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; | ||
| SentryEvent _applyScope({ | ||
| @required SentryEvent 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); | ||
| } | ||
|
|
||
| // TODO add tests | ||
| // Merge the scope 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.map((key, value) => MapEntry(key, value)) | ||
| ..addAll(event.extra ?? {})); | ||
|
|
||
| // Merge the scope level. | ||
| if (scope.level != null) { | ||
| event = event.copyWith(level: scope.level); | ||
| } | ||
| } | ||
| return event; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this be replaced by
options.sdkVersion.identifier? it looks duplicated nowThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ my bad, I thought
sdkVersionwas just a String, I renamed itsdk, and define it in the ioClient & BrowserClient if null