mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 08:24:39 +00:00
Add initial version of analytics and crash reporting package.
BUG= R=brianwilkerson@google.com Review-Url: https://codereview.chromium.org/2954733002 .
This commit is contained in:
parent
a8450243eb
commit
8e45759a67
|
@ -91,6 +91,7 @@ source_span:third_party/pkg/source_span/lib
|
|||
stack_trace:third_party/pkg/stack_trace/lib
|
||||
stream_channel:third_party/pkg/stream_channel/lib
|
||||
string_scanner:third_party/pkg/string_scanner/lib
|
||||
telemetry:pkg/telemetry/lib
|
||||
test:third_party/pkg/test/lib
|
||||
test_dart:tools/testing/dart
|
||||
testing:pkg/testing/lib
|
||||
|
|
|
@ -123,6 +123,7 @@ collection/test/equality_test/none: Pass, Fail # Issue 14348
|
|||
compiler/tool/*: SkipByDesign # Only meant to run on vm
|
||||
front_end/tool/*: SkipByDesign # Only meant to run on vm
|
||||
lookup_map/test/version_check_test: SkipByDesign # Only meant to run in vm.
|
||||
telemetry/test/*: SkipByDesign # Only meant to run on vm
|
||||
typed_data/test/typed_buffers_test/01: Fail # Not supporting Int64List, Uint64List.
|
||||
front_end/test/incremental_kernel_generator_test: SkipByDesign # Uses dart:io
|
||||
front_end/test/incremental_resolved_ast_generator_test: SkipByDesign # Uses dart:io
|
||||
|
|
26
pkg/telemetry/LICENSE
Normal file
26
pkg/telemetry/LICENSE
Normal file
|
@ -0,0 +1,26 @@
|
|||
Copyright 2017, the Dart project authors. All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
51
pkg/telemetry/README.md
Normal file
51
pkg/telemetry/README.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
# telemetry
|
||||
|
||||
A library to facilitate reporting analytics and crash reports.
|
||||
|
||||
## Analytics
|
||||
|
||||
This library is designed to allow all Dart SDK tools to easily send analytics
|
||||
information and crash reports. The tools share a common setting to configure
|
||||
sending analytics data. To use this library for a specific tool:
|
||||
|
||||
```
|
||||
import 'package:telemtry/telemtry.dart';
|
||||
import 'package:usage/usage.dart';
|
||||
|
||||
main() async {
|
||||
final String myAppTrackingID = ...;
|
||||
final String myAppName = ...;
|
||||
|
||||
Analytics analytics = createAnalyticsInstance(myAppTrackingID, myAppName);
|
||||
...
|
||||
analytics.sendScreenView('home');
|
||||
...
|
||||
await analytics.waitForLastPing();
|
||||
}
|
||||
```
|
||||
|
||||
The analytics object reads from the correct user configuration file
|
||||
automatically without any additional configuration. Analytics will not be sent
|
||||
if the user has opted-out.
|
||||
|
||||
## Crash reporting
|
||||
|
||||
To use the crash reporting functionality, import `crash_reporting.dart`, and
|
||||
create a new `CrashReportSender` instance:
|
||||
|
||||
```dart
|
||||
import 'package:telemtry/crash_reporting.dart';
|
||||
|
||||
main() {
|
||||
Analytics analytics = ...;
|
||||
CrashReportSender sender = new CrashReportSender(analytics);
|
||||
try {
|
||||
...
|
||||
} catch (e, st) {
|
||||
sender.sendReport(e, st);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Crash reports will only be sent if the cooresponding [Analytics] object is
|
||||
configured to send analytics.
|
10
pkg/telemetry/analysis_options.yaml
Normal file
10
pkg/telemetry/analysis_options.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
analyzer:
|
||||
strong-mode: true
|
||||
linter:
|
||||
rules:
|
||||
- annotate_overrides
|
||||
- empty_constructor_bodies
|
||||
- empty_statements
|
||||
- unawaited_futures
|
||||
- unnecessary_brace_in_string_interps
|
||||
- valid_regexps
|
92
pkg/telemetry/lib/crash_reporting.dart
Normal file
92
pkg/telemetry/lib/crash_reporting.dart
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:stack_trace/stack_trace.dart';
|
||||
import 'package:usage/usage.dart';
|
||||
|
||||
/// Crash backend host.
|
||||
const String _crashServerHost = 'clients2.google.com';
|
||||
|
||||
/// Path to the crash servlet.
|
||||
const String _crashEndpointPath = '/cr/report'; // or, staging_report
|
||||
|
||||
/// The field corresponding to the multipart/form-data file attachment where
|
||||
/// crash backend expects to find the Dart stack trace.
|
||||
const String _stackTraceFileField = 'DartError';
|
||||
|
||||
/// The name of the file attached as [stackTraceFileField].
|
||||
///
|
||||
/// The precise value is not important. It is ignored by the crash back end, but
|
||||
/// it must be supplied in the request.
|
||||
const String _stackTraceFilename = 'stacktrace_file';
|
||||
|
||||
/// Sends crash reports to Google.
|
||||
///
|
||||
/// Clients shouldn't extend, mixin or implement this class.
|
||||
class CrashReportSender {
|
||||
static final Uri _baseUri = new Uri(
|
||||
scheme: 'https', host: _crashServerHost, path: _crashEndpointPath);
|
||||
|
||||
final Analytics analytics;
|
||||
final http.Client _httpClient;
|
||||
|
||||
/// Create a new [CrashReportSender], using the data from the given
|
||||
/// [Analytics] instance.
|
||||
CrashReportSender(this.analytics, {http.Client httpClient})
|
||||
: _httpClient = httpClient ?? new http.Client();
|
||||
|
||||
/// Sends one crash report.
|
||||
///
|
||||
/// The report is populated from data in [error] and [stackTrace].
|
||||
Future sendReport(dynamic error, {StackTrace stackTrace}) async {
|
||||
if (!analytics.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final Uri uri = _baseUri.replace(
|
||||
queryParameters: <String, String>{
|
||||
'product': analytics.trackingId,
|
||||
'version': analytics.applicationVersion,
|
||||
},
|
||||
);
|
||||
|
||||
final http.MultipartRequest req = new http.MultipartRequest('POST', uri);
|
||||
req.fields['uuid'] = analytics.clientId;
|
||||
req.fields['product'] = analytics.trackingId;
|
||||
req.fields['version'] = analytics.applicationVersion;
|
||||
req.fields['osName'] = Platform.operatingSystem;
|
||||
// TODO(devoncarew): Report the operating system version when we're able.
|
||||
//req.fields['osVersion'] = Platform.operatingSystemVersion;
|
||||
req.fields['type'] = 'DartError';
|
||||
req.fields['error_runtime_type'] = '${error.runtimeType}';
|
||||
|
||||
final Chain chain = new Chain.parse(stackTrace.toString());
|
||||
req.files.add(new http.MultipartFile.fromString(
|
||||
_stackTraceFileField, chain.terse.toString(),
|
||||
filename: _stackTraceFilename));
|
||||
|
||||
final http.StreamedResponse resp = await _httpClient.send(req);
|
||||
|
||||
if (resp.statusCode != 200) {
|
||||
throw 'server responded with HTTP status code ${resp.statusCode}';
|
||||
}
|
||||
} on SocketException catch (error) {
|
||||
throw 'network error while sending crash report: $error';
|
||||
} catch (error, stackTrace) {
|
||||
// If the sender itself crashes, just print.
|
||||
throw 'exception while sending crash report: $error\n$stackTrace';
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the client and cleans up any resources associated with it. This
|
||||
/// will close the associated [http.Client].
|
||||
void dispose() {
|
||||
_httpClient.close();
|
||||
}
|
||||
}
|
99
pkg/telemetry/lib/telemetry.dart
Normal file
99
pkg/telemetry/lib/telemetry.dart
Normal file
|
@ -0,0 +1,99 @@
|
|||
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:usage/src/usage_impl.dart';
|
||||
import 'package:usage/src/usage_impl_io.dart';
|
||||
import 'package:usage/src/usage_impl_io.dart' as usage_io show getDartVersion;
|
||||
import 'package:usage/usage.dart';
|
||||
import 'package:usage/usage_io.dart';
|
||||
|
||||
final String _dartDirectoryName = '.dart';
|
||||
final String _settingsFileName = 'analytics.json';
|
||||
|
||||
/// Dart SDK tools with analytics should display this notice.
|
||||
///
|
||||
/// In addition, they should support displaying the analytics' status, and have
|
||||
/// a flag to toggle analytics. This may look something like:
|
||||
///
|
||||
/// `Analytics are currently enabled (and can be disabled with --no-analytics).`
|
||||
final String analyticsNotice =
|
||||
"Dart SDK tools anonymously report feature usage statistics and basic "
|
||||
"crash reports to help improve Dart tools over time. See Google's privacy "
|
||||
"policy: https://www.google.com/intl/en/policies/privacy/.";
|
||||
|
||||
/// Create an [Analytics] instance with the given trackingID and
|
||||
/// applicationName.
|
||||
///
|
||||
/// This analytics instance will share a common enablement state with the rest
|
||||
/// of the Dart SDK tools.
|
||||
Analytics createAnalyticsInstance(String trackingId, String applicationName,
|
||||
{bool disableForSession: false}) {
|
||||
Directory dir = getDartStorageDirectory();
|
||||
if (!dir.existsSync()) {
|
||||
dir.createSync();
|
||||
}
|
||||
|
||||
File file = new File(path.join(dir.path, _settingsFileName));
|
||||
return new _TelemetryAnalytics(
|
||||
trackingId, applicationName, getDartVersion(), file, disableForSession);
|
||||
}
|
||||
|
||||
/// The directory used to store the analytics settings file.
|
||||
///
|
||||
/// Typically, the directory is `~/.dart/` (and the settings file is
|
||||
/// `analytics.json`).
|
||||
Directory getDartStorageDirectory() =>
|
||||
new Directory(path.join(userHomeDir(), _dartDirectoryName));
|
||||
|
||||
/// Return the version of the Dart SDK.
|
||||
String getDartVersion() => usage_io.getDartVersion();
|
||||
|
||||
class _TelemetryAnalytics extends AnalyticsImpl {
|
||||
final bool disableForSession;
|
||||
|
||||
_TelemetryAnalytics(
|
||||
String trackingId,
|
||||
String applicationName,
|
||||
String applicationVersion,
|
||||
File file,
|
||||
this.disableForSession,
|
||||
)
|
||||
: super(
|
||||
trackingId,
|
||||
new IOPersistentProperties.fromFile(file),
|
||||
new IOPostHandler(),
|
||||
applicationName: applicationName,
|
||||
applicationVersion: applicationVersion,
|
||||
) {
|
||||
final String locale = getPlatformLocale();
|
||||
if (locale != null) {
|
||||
setSessionValue('ul', locale);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get enabled {
|
||||
if (disableForSession || _isRunningOnBot()) {
|
||||
return false;
|
||||
}
|
||||
return super.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
bool _isRunningOnBot() {
|
||||
// - https://docs.travis-ci.com/user/environment-variables/
|
||||
// - https://www.appveyor.com/docs/environment-variables/
|
||||
// - CHROME_HEADLESS and BUILDBOT_BUILDERNAME are properties on Chrome infra
|
||||
// bots.
|
||||
return Platform.environment['TRAVIS'] == 'true' ||
|
||||
Platform.environment['BOT'] == 'true' ||
|
||||
Platform.environment['CONTINUOUS_INTEGRATION'] == 'true' ||
|
||||
Platform.environment['CHROME_HEADLESS'] == '1' ||
|
||||
Platform.environment.containsKey('BUILDBOT_BUILDERNAME') ||
|
||||
Platform.environment.containsKey('APPVEYOR') ||
|
||||
Platform.environment.containsKey('CI');
|
||||
}
|
16
pkg/telemetry/pubspec.yaml
Normal file
16
pkg/telemetry/pubspec.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
name: telemetry
|
||||
description: A library to facilitate reporting analytics and crash reports.
|
||||
version: 0.0.1-dev
|
||||
author: Dart Team <misc@dartlang.org>
|
||||
|
||||
environment:
|
||||
sdk: '>=1.0.0 <2.0.0'
|
||||
|
||||
dependencies:
|
||||
http: ^0.11.3+12
|
||||
path: ^1.4.0
|
||||
stack_trace: ^1.7.0
|
||||
usage: ^3.2.0+1
|
||||
|
||||
dev_dependencies:
|
||||
test: ^0.12.0
|
40
pkg/telemetry/test/crash_reporting_test.dart
Normal file
40
pkg/telemetry/test/crash_reporting_test.dart
Normal file
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:convert' show UTF8;
|
||||
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http/testing.dart';
|
||||
import 'package:telemetry/crash_reporting.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:usage/usage.dart';
|
||||
|
||||
void main() {
|
||||
group('crash_reporting', () {
|
||||
MockClient mockClient;
|
||||
|
||||
Request request;
|
||||
|
||||
setUp(() {
|
||||
mockClient = new MockClient((Request r) async {
|
||||
request = r;
|
||||
return new Response('crash-report-001', 200);
|
||||
});
|
||||
});
|
||||
|
||||
test('CrashReportSender', () async {
|
||||
AnalyticsMock analytics = new AnalyticsMock();
|
||||
CrashReportSender sender =
|
||||
new CrashReportSender(analytics, httpClient: mockClient);
|
||||
|
||||
await sender.sendReport('test-error', stackTrace: StackTrace.current);
|
||||
|
||||
String body = UTF8.decode(request.bodyBytes);
|
||||
expect(body, contains('String')); // error.runtimeType
|
||||
expect(body, contains(analytics.trackingId));
|
||||
expect(body, contains('1.0.0'));
|
||||
expect(body, contains(analytics.clientId));
|
||||
});
|
||||
});
|
||||
}
|
31
pkg/telemetry/test/telemetry_test.dart
Normal file
31
pkg/telemetry/test/telemetry_test.dart
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:telemetry/telemetry.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:usage/usage.dart';
|
||||
|
||||
void main() {
|
||||
group('telemetry', () {
|
||||
test('getDartStorageDirectory', () {
|
||||
Directory dir = getDartStorageDirectory();
|
||||
expect(dir, isNotNull);
|
||||
});
|
||||
|
||||
test('getDartVersion', () {
|
||||
expect(getDartVersion(), isNotNull);
|
||||
});
|
||||
|
||||
test('createAnalyticsInstance', () {
|
||||
Analytics analytics = createAnalyticsInstance('UA-0', 'test-app');
|
||||
expect(analytics, isNotNull);
|
||||
expect(analytics.trackingId, 'UA-0');
|
||||
expect(analytics.getSessionValue('an'), 'test-app');
|
||||
expect(analytics.getSessionValue('av'), isNotNull);
|
||||
expect(analytics.clientId, isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue