| Index: pkg/telemetry/lib/crash_reporting.dart
|
| diff --git a/pkg/telemetry/lib/crash_reporting.dart b/pkg/telemetry/lib/crash_reporting.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..37d6b755949198f8ec031b7bb89affbb5441f16d
|
| --- /dev/null
|
| +++ b/pkg/telemetry/lib/crash_reporting.dart
|
| @@ -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();
|
| + }
|
| +}
|
|
|