mirror of
https://github.com/flutter/packages.git
synced 2025-07-01 15:23:25 +08:00
Support for new DSN format (without secretKey) and remove the quiver dependency. (#20)
* Make the secretKey optional for the new DSN format. * Remove the requirement for quiver. * Fixed test issue. Fixed dartfmt issues. * Code review modifications. * dartfmt
This commit is contained in:
1
AUTHORS
1
AUTHORS
@ -4,3 +4,4 @@
|
||||
# Name/Organization <email address>
|
||||
|
||||
Google Inc.
|
||||
Simon Lightfoot <simon@devangels.london>
|
||||
|
@ -11,7 +11,6 @@ import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:quiver/time.dart';
|
||||
import 'package:usage/uuid/uuid.dart';
|
||||
|
||||
import 'src/stack_trace.dart';
|
||||
@ -20,6 +19,9 @@ import 'src/version.dart';
|
||||
|
||||
export 'src/version.dart';
|
||||
|
||||
/// Used to provide timestamp for logging.
|
||||
typedef ClockProvider = DateTime Function();
|
||||
|
||||
/// Logs crash reports and events to the Sentry.io service.
|
||||
class SentryClient {
|
||||
/// Sentry.io client identifier for _this_ client.
|
||||
@ -46,7 +48,9 @@ class SentryClient {
|
||||
/// make HTTP calls to Sentry.io. This is useful in tests.
|
||||
///
|
||||
/// If [clock] is provided, it is used to get time instead of the system
|
||||
/// clock. This is useful in tests.
|
||||
/// clock. This is useful in tests. Should be an implementation of ClockProvider.
|
||||
/// This parameter is dynamic to maintain backwards compatibility with
|
||||
/// previous use of Clock from the Quiver library.
|
||||
///
|
||||
/// 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
|
||||
@ -56,22 +60,21 @@ class SentryClient {
|
||||
Event environmentAttributes,
|
||||
bool compressPayload,
|
||||
Client httpClient,
|
||||
Clock clock,
|
||||
dynamic clock,
|
||||
UuidGenerator uuidGenerator,
|
||||
}) {
|
||||
httpClient ??= new Client();
|
||||
clock ??= const Clock(_getUtcDateTime);
|
||||
clock ??= _getUtcDateTime;
|
||||
uuidGenerator ??= _generateUuidV4WithoutDashes;
|
||||
compressPayload ??= true;
|
||||
|
||||
final ClockProvider clockProvider =
|
||||
clock is ClockProvider ? clock : clock.get;
|
||||
|
||||
final Uri uri = Uri.parse(dsn);
|
||||
final List<String> userInfo = uri.userInfo.split(':');
|
||||
|
||||
assert(() {
|
||||
if (userInfo.length != 2)
|
||||
throw new ArgumentError(
|
||||
'Colon-separated publicKey:secretKey pair not found in the user info field of the DSN URI: $dsn');
|
||||
|
||||
if (uri.pathSegments.isEmpty)
|
||||
throw new ArgumentError(
|
||||
'Project ID not found in the URI path of the DSN URI: $dsn');
|
||||
@ -79,13 +82,13 @@ class SentryClient {
|
||||
return true;
|
||||
}());
|
||||
|
||||
final String publicKey = userInfo.first;
|
||||
final String secretKey = userInfo.last;
|
||||
final String publicKey = userInfo[0];
|
||||
final String secretKey = userInfo.length >= 2 ? userInfo[1] : null;
|
||||
final String projectId = uri.pathSegments.last;
|
||||
|
||||
return new SentryClient._(
|
||||
httpClient: httpClient,
|
||||
clock: clock,
|
||||
clock: clockProvider,
|
||||
uuidGenerator: uuidGenerator,
|
||||
environmentAttributes: environmentAttributes,
|
||||
dsnUri: uri,
|
||||
@ -98,12 +101,12 @@ class SentryClient {
|
||||
|
||||
SentryClient._({
|
||||
@required Client httpClient,
|
||||
@required Clock clock,
|
||||
@required ClockProvider clock,
|
||||
@required UuidGenerator uuidGenerator,
|
||||
@required this.environmentAttributes,
|
||||
@required this.dsnUri,
|
||||
@required this.publicKey,
|
||||
@required this.secretKey,
|
||||
this.secretKey,
|
||||
@required this.compressPayload,
|
||||
@required this.projectId,
|
||||
}) : _httpClient = httpClient,
|
||||
@ -111,7 +114,7 @@ class SentryClient {
|
||||
_uuidGenerator = uuidGenerator;
|
||||
|
||||
final Client _httpClient;
|
||||
final Clock _clock;
|
||||
final ClockProvider _clock;
|
||||
final UuidGenerator _uuidGenerator;
|
||||
|
||||
/// Contains [Event] attributes that are automatically mixed into all events
|
||||
@ -161,21 +164,23 @@ class SentryClient {
|
||||
|
||||
/// Reports an [event] to Sentry.io.
|
||||
Future<SentryResponse> capture({@required Event event}) async {
|
||||
final DateTime now = _clock.now();
|
||||
final DateTime now = _clock();
|
||||
String authHeader = 'Sentry sentry_version=6, sentry_client=$sentryClient, '
|
||||
'sentry_timestamp=${now.millisecondsSinceEpoch}, sentry_key=$publicKey';
|
||||
if (secretKey != null) {
|
||||
authHeader += ', sentry_secret=$secretKey';
|
||||
}
|
||||
|
||||
final Map<String, String> headers = <String, String>{
|
||||
'User-Agent': '$sentryClient',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Sentry-Auth': 'Sentry sentry_version=6, '
|
||||
'sentry_client=$sentryClient, '
|
||||
'sentry_timestamp=${now.millisecondsSinceEpoch}, '
|
||||
'sentry_key=$publicKey, '
|
||||
'sentry_secret=$secretKey',
|
||||
'X-Sentry-Auth': authHeader,
|
||||
};
|
||||
|
||||
final Map<String, dynamic> data = <String, dynamic>{
|
||||
'project': projectId,
|
||||
'event_id': _uuidGenerator(),
|
||||
'timestamp': formatDateAsIso8601WithSecondPrecision(_clock.now()),
|
||||
'timestamp': formatDateAsIso8601WithSecondPrecision(now),
|
||||
'logger': defaultLoggerName,
|
||||
};
|
||||
|
||||
|
@ -10,7 +10,6 @@ environment:
|
||||
dependencies:
|
||||
http: ">=0.11.0 <2.0.0"
|
||||
meta: ">=1.0.0 <2.0.0"
|
||||
quiver: ">=0.25.0 <2.0.0"
|
||||
stack_trace: ">=1.0.0 <2.0.0"
|
||||
usage: ">=3.0.0 <4.0.0"
|
||||
|
||||
|
@ -6,11 +6,11 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
import 'package:quiver/time.dart';
|
||||
import 'package:sentry/sentry.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
const String _testDsn = 'https://public:secret@sentry.example.com/1';
|
||||
const String _testDsnWithoutSecret = 'https://public@sentry.example.com/1';
|
||||
|
||||
void main() {
|
||||
group('$SentryClient', () {
|
||||
@ -24,9 +24,75 @@ void main() {
|
||||
await client.close();
|
||||
});
|
||||
|
||||
test('can parse DSN without secret', () async {
|
||||
final SentryClient client = new SentryClient(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');
|
||||
await client.close();
|
||||
});
|
||||
|
||||
test('sends client auth header without secret', () async {
|
||||
final MockClient httpMock = new MockClient();
|
||||
final ClockProvider fakeClockProvider =
|
||||
() => new DateTime.utc(2017, 1, 2);
|
||||
|
||||
Map<String, String> headers;
|
||||
|
||||
httpMock.answerWith((Invocation invocation) async {
|
||||
if (invocation.memberName == #close) {
|
||||
return null;
|
||||
}
|
||||
if (invocation.memberName == #post) {
|
||||
headers = invocation.namedArguments[#headers];
|
||||
return new Response('{"id": "test-event-id"}', 200);
|
||||
}
|
||||
fail('Unexpected invocation of ${invocation.memberName} in HttpMock');
|
||||
});
|
||||
|
||||
final SentryClient client = new 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',
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
throw new ArgumentError('Test error');
|
||||
} catch (error, stackTrace) {
|
||||
final SentryResponse response = await client.captureException(
|
||||
exception: error, stackTrace: stackTrace);
|
||||
expect(response.isSuccessful, true);
|
||||
expect(response.eventId, 'test-event-id');
|
||||
expect(response.error, null);
|
||||
}
|
||||
|
||||
final Map<String, String> expectedHeaders = <String, String>{
|
||||
'User-Agent': '$sdkName/$sdkVersion',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Sentry-Auth': 'Sentry sentry_version=6, '
|
||||
'sentry_client=${SentryClient.sentryClient}, '
|
||||
'sentry_timestamp=${fakeClockProvider().millisecondsSinceEpoch}, '
|
||||
'sentry_key=public',
|
||||
};
|
||||
|
||||
expect(headers, expectedHeaders);
|
||||
|
||||
await client.close();
|
||||
});
|
||||
|
||||
testCaptureException(bool compressPayload) async {
|
||||
final MockClient httpMock = new MockClient();
|
||||
final Clock fakeClock = new Clock.fixed(new DateTime.utc(2017, 1, 2));
|
||||
final ClockProvider fakeClockProvider =
|
||||
() => new DateTime.utc(2017, 1, 2);
|
||||
|
||||
String postUri;
|
||||
Map<String, String> headers;
|
||||
@ -47,7 +113,7 @@ void main() {
|
||||
final SentryClient client = new SentryClient(
|
||||
dsn: _testDsn,
|
||||
httpClient: httpMock,
|
||||
clock: fakeClock,
|
||||
clock: fakeClockProvider,
|
||||
uuidGenerator: () => 'X' * 32,
|
||||
compressPayload: compressPayload,
|
||||
environmentAttributes: const Event(
|
||||
@ -74,9 +140,7 @@ void main() {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Sentry-Auth': 'Sentry sentry_version=6, '
|
||||
'sentry_client=${SentryClient.sentryClient}, '
|
||||
'sentry_timestamp=${fakeClock
|
||||
.now()
|
||||
.millisecondsSinceEpoch}, '
|
||||
'sentry_timestamp=${fakeClockProvider().millisecondsSinceEpoch}, '
|
||||
'sentry_key=public, '
|
||||
'sentry_secret=secret',
|
||||
};
|
||||
@ -133,7 +197,8 @@ void main() {
|
||||
|
||||
test('reads error message from the x-sentry-error header', () async {
|
||||
final MockClient httpMock = new MockClient();
|
||||
final Clock fakeClock = new Clock.fixed(new DateTime(2017, 1, 2));
|
||||
final ClockProvider fakeClockProvider =
|
||||
() => new DateTime.utc(2017, 1, 2);
|
||||
|
||||
httpMock.answerWith((Invocation invocation) async {
|
||||
if (invocation.memberName == #close) {
|
||||
@ -150,7 +215,7 @@ void main() {
|
||||
final SentryClient client = new SentryClient(
|
||||
dsn: _testDsn,
|
||||
httpClient: httpMock,
|
||||
clock: fakeClock,
|
||||
clock: fakeClockProvider,
|
||||
uuidGenerator: () => 'X' * 32,
|
||||
compressPayload: false,
|
||||
environmentAttributes: const Event(
|
||||
@ -176,7 +241,8 @@ void main() {
|
||||
|
||||
test('$Event userContext overrides client', () async {
|
||||
final MockClient httpMock = new MockClient();
|
||||
final Clock fakeClock = new Clock.fixed(new DateTime(2017, 1, 2));
|
||||
final ClockProvider fakeClockProvider =
|
||||
() => new DateTime.utc(2017, 1, 2);
|
||||
|
||||
String loggedUserId; // used to find out what user context was sent
|
||||
httpMock.answerWith((Invocation invocation) async {
|
||||
@ -211,7 +277,7 @@ void main() {
|
||||
final SentryClient client = new SentryClient(
|
||||
dsn: _testDsn,
|
||||
httpClient: httpMock,
|
||||
clock: fakeClock,
|
||||
clock: fakeClockProvider,
|
||||
uuidGenerator: () => 'X' * 32,
|
||||
compressPayload: false,
|
||||
environmentAttributes: const Event(
|
||||
|
Reference in New Issue
Block a user