diff --git a/CHANGELOG.md b/CHANGELOG.md index 6493302ba7..9763bc1a82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - BREAKING CHANGE: `package:http` min version bumped to 0.12.0 #104 - BREAKING CHANGE: replace the `package:usage` by `package:uuid` #94 - BREAKING CHANGE: `Event.message` must now be an instance of `Message` +- BREAKING CHANGE: SentryClient must now be initialized with a SentryOptions #118 - By default no logger it set #63 - Added missing Contexts to Event.copyWith() #62 - remove the `package:args` dependency #94 diff --git a/dart/lib/src/browser_client.dart b/dart/lib/src/browser_client.dart index 5ca60a0323..918b8f9d35 100644 --- a/dart/lib/src/browser_client.dart +++ b/dart/lib/src/browser_client.dart @@ -7,29 +7,15 @@ import 'dart:convert'; import 'dart:html' show window; import 'package:http/browser_client.dart'; -import 'package:http/http.dart'; -import 'package:meta/meta.dart'; import 'client.dart'; import 'protocol.dart'; +import 'sentry_options.dart'; import 'utils.dart'; import 'version.dart'; -SentryClient createSentryClient({ - @required String dsn, - Event environmentAttributes, - bool compressPayload, - Client httpClient, - dynamic clock, - UuidGenerator uuidGenerator, -}) => - SentryBrowserClient( - dsn: dsn, - environmentAttributes: environmentAttributes, - httpClient: httpClient, - clock: clock, - uuidGenerator: uuidGenerator, - ); +SentryClient createSentryClient(SentryOptions options) => + SentryBrowserClient(options); /// Logs crash reports and events to the Sentry.io service. class SentryBrowserClient extends SentryClient { @@ -53,49 +39,27 @@ class SentryBrowserClient extends SentryClient { /// If [uuidGenerator] is provided, it is used to generate the "event_id" /// field instead of the built-in random UUID v4 generator. This is useful in /// tests. - factory SentryBrowserClient({ - @required String dsn, - Event environmentAttributes, - Client httpClient, - dynamic clock, - UuidGenerator uuidGenerator, - String origin, - }) { - httpClient ??= BrowserClient(); - clock ??= getUtcDateTime; - uuidGenerator ??= generateUuidV4WithoutDashes; + factory SentryBrowserClient(SentryOptions options, {String origin}) { + options.httpClient ??= BrowserClient(); + options.clock ??= getUtcDateTime; + options.uuidGenerator ??= generateUuidV4WithoutDashes; // origin is necessary for sentry to resolve stacktrace origin ??= '${window.location.origin}/'; return SentryBrowserClient._( - httpClient: httpClient, - clock: clock, - uuidGenerator: uuidGenerator, - environmentAttributes: environmentAttributes, - dsn: dsn, + options, origin: origin, platform: browserPlatform, ); } - SentryBrowserClient._({ - Client httpClient, - dynamic clock, - UuidGenerator uuidGenerator, - Event environmentAttributes, - String dsn, - String platform, - String origin, - }) : super.base( - httpClient: httpClient, - clock: clock, - uuidGenerator: uuidGenerator, - environmentAttributes: environmentAttributes, - dsn: dsn, - platform: platform, + SentryBrowserClient._(SentryOptions options, {String origin, String platform}) + : super.base( + options, origin: origin, sdk: Sdk(name: browserSdkName, version: sdkVersion), + platform: platform, ); @override diff --git a/dart/lib/src/client.dart b/dart/lib/src/client.dart index 6f19a952ed..93061c6065 100644 --- a/dart/lib/src/client.dart +++ b/dart/lib/src/client.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:http/http.dart'; import 'package:meta/meta.dart'; import 'package:sentry/sentry.dart'; @@ -22,60 +21,30 @@ abstract class SentryClient { /// /// Creates an `SentryIOClient` if `dart:io` is available and a `SentryBrowserClient` if /// `dart:html` is available, otherwise it will throw an unsupported error. - factory SentryClient({ - @required String dsn, - Event environmentAttributes, - bool compressPayload, - Client httpClient, - dynamic clock, - UuidGenerator uuidGenerator, - }) => - createSentryClient( - dsn: dsn, - environmentAttributes: environmentAttributes, - httpClient: httpClient, - clock: clock, - uuidGenerator: uuidGenerator, - compressPayload: compressPayload, - ); - - SentryClient.base({ - this.httpClient, - dynamic clock, - UuidGenerator uuidGenerator, - String dsn, - this.environmentAttributes, + factory SentryClient(SentryOptions options) => createSentryClient(options); + + SentryClient.base( + this.options, { String platform, this.origin, Sdk sdk, - }) : _dsn = Dsn.parse(dsn), - _uuidGenerator = uuidGenerator ?? generateUuidV4WithoutDashes, + }) : _dsn = Dsn.parse(options.dsn), _platform = platform ?? sdkPlatform, sdk = sdk ?? Sdk(name: sdkName, version: sdkVersion) { - if (clock == null) { - _clock = getUtcDateTime; + if (options.clock == null) { + options.clock = getUtcDateTime; } else { - _clock = (clock is ClockProvider ? clock : clock.get) as ClockProvider; + options.clock = (options.clock is ClockProvider + ? options.clock + : options.clock.get) as ClockProvider; } } - @protected - final Client httpClient; - - ClockProvider _clock; - final UuidGenerator _uuidGenerator; - - /// 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]. - final Event environmentAttributes; - final Dsn _dsn; + @protected + SentryOptions options; + /// The DSN URI. @visibleForTesting Uri get dsnUri => _dsn.uri; @@ -108,7 +77,7 @@ abstract class SentryClient { User userContext; /// Use for browser stacktrace - final String origin; + String origin; /// Used by sentry to differentiate browser from io environment final String _platform; @@ -142,7 +111,7 @@ abstract class SentryClient { StackFrameFilter stackFrameFilter, Scope scope, }) 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) { @@ -153,12 +122,12 @@ abstract class SentryClient { final data = { 'project': projectId, - 'event_id': _uuidGenerator(), + 'event_id': options.uuidGenerator(), 'timestamp': formatDateAsIso8601WithSecondPrecision(now), }; - if (environmentAttributes != null) { - mergeAttributes(environmentAttributes.toJson(), into: data); + if (options.environmentAttributes != null) { + mergeAttributes(options.environmentAttributes.toJson(), into: data); } // Merge the user context. @@ -178,17 +147,13 @@ abstract class SentryClient { final body = bodyEncoder(data, headers); - final response = await httpClient.post( + final response = await options.httpClient.post( postUri, headers: headers, body: body, ); if (response.statusCode != 200) { - /*var errorMessage = 'Sentry.io responded with HTTP ${response.statusCode}'; - if (response.headers['x-sentry-error'] != null) { - errorMessage += ': ${response.headers['x-sentry-error']}'; - }*/ return SentryId.empty(); } @@ -222,7 +187,7 @@ abstract class SentryClient { } Future close() async { - httpClient.close(); + options.httpClient?.close(); } @override diff --git a/dart/lib/src/client_stub.dart b/dart/lib/src/client_stub.dart index 882268db74..67fdfbda69 100644 --- a/dart/lib/src/client_stub.dart +++ b/dart/lib/src/client_stub.dart @@ -2,21 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:http/http.dart'; -import 'package:meta/meta.dart'; - import 'client.dart'; -import 'protocol.dart'; -import 'utils.dart'; +import 'sentry_options.dart'; /// Implemented in `browser_client.dart` and `io_client.dart`. -SentryClient createSentryClient({ - @required String dsn, - Event environmentAttributes, - bool compressPayload, - Client httpClient, - dynamic clock, - UuidGenerator uuidGenerator, -}) => +SentryClient createSentryClient(SentryOptions options) => throw UnsupportedError( 'Cannot create a client without dart:html or dart:io.'); diff --git a/dart/lib/src/hub.dart b/dart/lib/src/hub.dart index 30df85fc68..feac240ae2 100644 --- a/dart/lib/src/hub.dart +++ b/dart/lib/src/hub.dart @@ -11,15 +11,8 @@ typedef ScopeCallback = void Function(Scope); /// SDK API contract which combines a client and scope management class Hub { - static SentryClient _getClient({SentryOptions fromOptions}) { - return SentryClient( - dsn: fromOptions.dsn, - environmentAttributes: fromOptions.environmentAttributes, - compressPayload: fromOptions.compressPayload, - httpClient: fromOptions.httpClient, - clock: fromOptions.clock, - uuidGenerator: fromOptions.uuidGenerator, - ); + static SentryClient _getClient(SentryOptions options) { + return SentryClient(options); } final ListQueue<_StackItem> _stack = ListQueue(); @@ -36,7 +29,7 @@ class Hub { } Hub._(SentryOptions options) : _options = options { - _stack.add(_StackItem(_getClient(fromOptions: options), Scope(_options))); + _stack.add(_StackItem(_getClient(_options), Scope(_options))); _isEnabled = true; } diff --git a/dart/lib/src/io_client.dart b/dart/lib/src/io_client.dart index f45ecc57d2..b46f54839e 100644 --- a/dart/lib/src/io_client.dart +++ b/dart/lib/src/io_client.dart @@ -6,30 +6,13 @@ import 'dart:convert'; import 'dart:io'; -import 'package:http/http.dart'; -import 'package:meta/meta.dart'; +import 'package:sentry/sentry.dart'; import 'client.dart'; import 'protocol.dart'; -import 'utils.dart'; -import 'version.dart'; -SentryClient createSentryClient({ - @required String dsn, - Event environmentAttributes, - bool compressPayload, - Client httpClient, - dynamic clock, - UuidGenerator uuidGenerator, -}) => - SentryIOClient( - dsn: dsn, - environmentAttributes: environmentAttributes, - compressPayload: compressPayload, - httpClient: httpClient, - clock: clock, - uuidGenerator: uuidGenerator, - ); +SentryClient createSentryClient(SentryOptions options) => + SentryIOClient(options); /// Logs crash reports and events to the Sentry.io service. class SentryIOClient extends SentryClient { @@ -57,52 +40,9 @@ class SentryIOClient extends SentryClient { /// If [uuidGenerator] is provided, it is used to generate the "event_id" /// field instead of the built-in random UUID v4 generator. This is useful in /// tests. - factory SentryIOClient({ - @required String dsn, - Event environmentAttributes, - bool compressPayload, - Client httpClient, - dynamic clock, - UuidGenerator uuidGenerator, - }) { - httpClient ??= Client(); - clock ??= getUtcDateTime; - uuidGenerator ??= generateUuidV4WithoutDashes; - compressPayload ??= true; + factory SentryIOClient(SentryOptions options) => SentryIOClient._(options); - return SentryIOClient._( - httpClient: httpClient, - clock: clock, - uuidGenerator: uuidGenerator, - environmentAttributes: environmentAttributes, - dsn: dsn, - compressPayload: compressPayload, - platform: sdkPlatform, - ); - } - - SentryIOClient._({ - Client httpClient, - dynamic clock, - UuidGenerator uuidGenerator, - Event environmentAttributes, - String dsn, - this.compressPayload = true, - String platform, - String origin, - }) : super.base( - httpClient: httpClient, - clock: clock, - uuidGenerator: uuidGenerator, - environmentAttributes: environmentAttributes, - dsn: dsn, - platform: platform, - origin: origin, - sdk: Sdk(name: sdkName, version: sdkVersion), - ); - - /// Whether to compress payloads sent to Sentry.io. - final bool compressPayload; + SentryIOClient._(SentryOptions options) : super.base(options); @override Map buildHeaders(String authHeader) { @@ -123,7 +63,7 @@ class SentryIOClient extends SentryClient { // [SentryIOClient] implement gzip compression // gzip compression is not available on browser var body = utf8.encode(json.encode(data)); - if (compressPayload) { + if (options.compressPayload) { headers['Content-Encoding'] = 'gzip'; body = gzip.encode(body); } diff --git a/dart/lib/src/noop_client.dart b/dart/lib/src/noop_client.dart index 3f7f4c9119..8f7f3a4fcc 100644 --- a/dart/lib/src/noop_client.dart +++ b/dart/lib/src/noop_client.dart @@ -1,10 +1,9 @@ import 'dart:async'; -import 'package:http/src/client.dart'; - import 'client.dart'; import 'protocol.dart'; import 'scope.dart'; +import 'sentry_options.dart'; class NoOpSentryClient implements SentryClient { NoOpSentryClient._(); @@ -18,6 +17,12 @@ class NoOpSentryClient implements SentryClient { @override User userContext; + @override + SentryOptions options; + + @override + String origin; + @override List bodyEncoder( Map data, @@ -57,15 +62,6 @@ class NoOpSentryClient implements SentryClient { @override Uri get dsnUri => null; - @override - Event get environmentAttributes => null; - - @override - Client get httpClient => null; - - @override - String get origin => null; - @override String get postUri => null; diff --git a/dart/test/sentry_browser_test.dart b/dart/test/sentry_browser_test.dart index 0531f7ec6b..e4c4711ce4 100644 --- a/dart/test/sentry_browser_test.dart +++ b/dart/test/sentry_browser_test.dart @@ -11,7 +11,7 @@ import 'test_utils.dart'; void main() { group('SentryBrowserClient', () { test('SentryClient constructor build browser client', () { - final client = SentryClient(dsn: testDsn); + final client = SentryClient(SentryOptions(dsn: testDsn)); expect(client is SentryBrowserClient, isTrue); }); diff --git a/dart/test/sentry_io_test.dart b/dart/test/sentry_io_test.dart index cc19693602..10a5b70e00 100644 --- a/dart/test/sentry_io_test.dart +++ b/dart/test/sentry_io_test.dart @@ -13,7 +13,7 @@ import 'test_utils.dart'; void main() { group(SentryIOClient, () { test('SentryClient constructor build io client', () { - final client = SentryClient(dsn: testDsn); + final client = SentryClient(SentryOptions(dsn: testDsn)); expect(client is SentryIOClient, isTrue); }); diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index 5a34b86ee5..13107a7ded 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -70,15 +70,17 @@ Future testCaptureException( }); final client = SentryClient( - dsn: testDsn, - httpClient: httpMock, - clock: fakeClockProvider, - uuidGenerator: () => 'X' * 32, - compressPayload: compressPayload, - environmentAttributes: const Event( - serverName: 'test.server.com', - release: '1.2.3', - environment: 'staging', + SentryOptions( + dsn: testDsn, + httpClient: httpMock, + clock: fakeClockProvider, + uuidGenerator: () => 'X' * 32, + compressPayload: compressPayload, + environmentAttributes: const Event( + serverName: 'test.server.com', + release: '1.2.3', + environment: 'staging', + ), ), ); @@ -176,7 +178,7 @@ Future testCaptureException( void runTest({Codec, List> gzip, bool isWeb = false}) { test('can parse DSN', () async { - final client = SentryClient(dsn: testDsn); + 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'); @@ -186,7 +188,7 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { }); test('can parse DSN without secret', () async { - final client = SentryClient(dsn: _testDsnWithoutSecret); + 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'); @@ -196,7 +198,7 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { }); test('can parse DSN with path', () async { - final client = SentryClient(dsn: _testDsnWithPath); + 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'); @@ -205,7 +207,7 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { await client.close(); }); test('can parse DSN with port', () async { - final client = SentryClient(dsn: _testDsnWithPort); + 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'); @@ -228,15 +230,17 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { }); final client = SentryClient( - dsn: _testDsnWithoutSecret, - httpClient: httpMock, - clock: fakeClockProvider, - compressPayload: false, - uuidGenerator: () => 'X' * 32, - environmentAttributes: const Event( - serverName: 'test.server.com', - release: '1.2.3', - environment: 'staging', + SentryOptions( + dsn: _testDsnWithoutSecret, + httpClient: httpMock, + clock: fakeClockProvider, + compressPayload: false, + uuidGenerator: () => 'X' * 32, + environmentAttributes: const Event( + serverName: 'test.server.com', + release: '1.2.3', + environment: 'staging', + ), ), ); @@ -284,15 +288,17 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { }); final client = SentryClient( - dsn: testDsn, - httpClient: httpMock, - clock: fakeClockProvider, - uuidGenerator: () => 'X' * 32, - compressPayload: false, - environmentAttributes: const Event( - serverName: 'test.server.com', - release: '1.2.3', - environment: 'staging', + SentryOptions( + dsn: testDsn, + httpClient: httpMock, + clock: fakeClockProvider, + uuidGenerator: () => 'X' * 32, + compressPayload: false, + environmentAttributes: const Event( + serverName: 'test.server.com', + release: '1.2.3', + environment: 'staging', + ), ), ); @@ -338,15 +344,17 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { extras: {'foo': 'bar'}); final client = SentryClient( - dsn: testDsn, - httpClient: httpMock, - clock: fakeClockProvider, - uuidGenerator: () => 'X' * 32, - compressPayload: false, - environmentAttributes: const Event( - serverName: 'test.server.com', - release: '1.2.3', - environment: 'staging', + SentryOptions( + dsn: testDsn, + httpClient: httpMock, + clock: fakeClockProvider, + uuidGenerator: () => 'X' * 32, + compressPayload: false, + environmentAttributes: const Event( + serverName: 'test.server.com', + release: '1.2.3', + environment: 'staging', + ), ), ); client.userContext = clientUserContext; diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 4154ccccfe..94b288ce09 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -11,10 +11,11 @@ import 'package:universal_platform/universal_platform.dart'; const String _release = String.fromEnvironment('SENTRY_RELEASE', defaultValue: 'unknown'); +const String exampleDsn = + 'https://cb0fad6f5d4e42ebb9c956cb0463edc9@o447951.ingest.sentry.io/5428562'; + // NOTE: Add your DSN below to get the events in your Sentry project. -final SentryClient _sentry = SentryClient( - dsn: - 'https://cb0fad6f5d4e42ebb9c956cb0463edc9@o447951.ingest.sentry.io/5428562'); +final SentryClient _sentry = SentryClient(SentryOptions(dsn: exampleDsn)); // Proposed init: // https://github.com/bruno-garcia/badges.bar/blob/2450ed9125f7b73d2baad1fa6d676cc71858116c/lib/src/sentry.dart#L9-L32