afcfbbeba8
(Part of https://github.com/dart-lang/sdk/issues/63288) This change migrates the packages owned by the developer experience team to use the new constructor declaration syntax, described in https://github.com/dart-lang/language/blob/main/accepted/future-releases/primary-constructors/feature-specification.md#abbreviations-of-in-body-constructor-declarations. This change was performed in an automated fashion, by (a) bumping the packages' SDK constraints to `3.13.0-0`, (b) enabling the lints `unnecessary_type_name_in_constructor` and `unnecessary_const_in_enum_constructor`, (c) fixing the resulting lint failures using `dart fix`, and then (d) reformatting the affected files. To ease code review, I've reverted unrelated formatting changes. Since this change requires bumping SDK constaints to `3.13.0-0`, it was only performed on packages that are *not* published on pub. (Packages that *are* published on pub should remain on lower language versions until at least after the stable version of 3.13 is released, so that we don't block users on the stable channel from receiving updates to those packages.) Change-Id: Ibb4daebafd239da58251e838ea6a3f336a6a6964 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505046 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
212 lines
6.2 KiB
Dart
212 lines
6.2 KiB
Dart
// 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 'dart:math' as math;
|
|
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:meta/meta.dart';
|
|
import 'package:stack_trace/stack_trace.dart';
|
|
|
|
import 'src/pii_regexp.dart';
|
|
import 'src/utils.dart';
|
|
|
|
/// Tells crash backend that this is a Dart error (as opposed to, say, Java).
|
|
const String _dartTypeId = 'DartError';
|
|
|
|
/// Crash backend host.
|
|
const String _crashServerHost = 'clients2.google.com';
|
|
|
|
/// Path to the staging crash servlet.
|
|
const String _crashEndpointPathStaging = '/cr/staging_report';
|
|
|
|
/// Path to the prod crash servlet.
|
|
const String _crashEndpointPathProd = '/cr/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 {
|
|
final Uri _baseUri;
|
|
|
|
static const int _maxReportsToSend = 1000;
|
|
|
|
final String crashProductId;
|
|
final EnablementCallback shouldSend;
|
|
final http.Client _httpClient;
|
|
final Stopwatch _processStopwatch = Stopwatch()..start();
|
|
|
|
final ThrottlingBucket _throttle = ThrottlingBucket(10, Duration(minutes: 1));
|
|
int _reportsSent = 0;
|
|
int _skippedReports = 0;
|
|
|
|
new _(
|
|
this.crashProductId,
|
|
this.shouldSend, {
|
|
http.Client? httpClient,
|
|
String endpointPath = _crashEndpointPathStaging,
|
|
}) : _httpClient = httpClient ?? http.Client(),
|
|
_baseUri = Uri(
|
|
scheme: 'https',
|
|
host: _crashServerHost,
|
|
path: endpointPath,
|
|
);
|
|
|
|
/// Create a new [CrashReportSender] connected to the staging endpoint.
|
|
new staging(
|
|
String crashProductId,
|
|
EnablementCallback shouldSend, {
|
|
http.Client? httpClient,
|
|
}) : this._(
|
|
crashProductId,
|
|
shouldSend,
|
|
httpClient: httpClient,
|
|
endpointPath: _crashEndpointPathStaging,
|
|
);
|
|
|
|
/// Create a new [CrashReportSender] connected to the prod endpoint.
|
|
new prod(
|
|
String crashProductId,
|
|
EnablementCallback shouldSend, {
|
|
http.Client? httpClient,
|
|
}) : this._(
|
|
crashProductId,
|
|
shouldSend,
|
|
httpClient: httpClient,
|
|
endpointPath: _crashEndpointPathProd,
|
|
);
|
|
|
|
/// Sends one crash report.
|
|
///
|
|
/// The report is populated from data in [error] and [stackTrace].
|
|
///
|
|
/// Additional context about the crash can optionally be passed in via
|
|
/// [comment]. Note that this field should not include PII.
|
|
Future sendReport(
|
|
dynamic error,
|
|
StackTrace stackTrace, {
|
|
List<CrashReportAttachment> attachments = const [],
|
|
String? comment,
|
|
}) async {
|
|
if (!shouldSend()) {
|
|
return;
|
|
}
|
|
|
|
// Check if we've sent too many reports recently.
|
|
if (!_throttle.removeDrop()) {
|
|
_skippedReports++;
|
|
return;
|
|
}
|
|
|
|
// Don't send too many total reports to crash reporting.
|
|
if (_reportsSent >= _maxReportsToSend) {
|
|
return;
|
|
}
|
|
|
|
_reportsSent++;
|
|
|
|
// Calculate the 'weight' of the this report; we increase the weight of a
|
|
// report if we had throttled previous reports.
|
|
int weight = math.min(_skippedReports + 1, 10000);
|
|
_skippedReports = 0;
|
|
|
|
try {
|
|
final String dartVersion = Platform.version.split(' ').first;
|
|
|
|
final Uri uri = _baseUri.replace(
|
|
queryParameters: <String, String>{
|
|
'product': crashProductId,
|
|
'version': dartVersion,
|
|
},
|
|
);
|
|
|
|
final http.MultipartRequest req = http.MultipartRequest('POST', uri);
|
|
|
|
Map<String, String> fields = req.fields;
|
|
fields['product'] = crashProductId;
|
|
fields['version'] = dartVersion;
|
|
fields['osName'] = Platform.operatingSystem;
|
|
fields['osVersion'] = Platform.operatingSystemVersion;
|
|
fields['type'] = _dartTypeId;
|
|
fields['error_runtime_type'] = '${error.runtimeType}';
|
|
fields['error_message'] = filterPiiFromErrorMessage('$error');
|
|
|
|
// Optional comments.
|
|
if (comment != null) {
|
|
fields['comments'] = comment;
|
|
}
|
|
|
|
// The uptime of the process before it crashed (in milliseconds).
|
|
fields['ptime'] = _processStopwatch.elapsedMilliseconds.toString();
|
|
|
|
// Send the amount to weight this report.
|
|
if (weight > 1) {
|
|
fields['weight'] = weight.toString();
|
|
}
|
|
|
|
final Chain chain = Chain.forTrace(stackTrace);
|
|
req.files.add(
|
|
http.MultipartFile.fromString(
|
|
_stackTraceFileField,
|
|
chain.terse.toString(),
|
|
filename: _stackTraceFilename,
|
|
),
|
|
);
|
|
|
|
for (var attachment in attachments) {
|
|
req.files.add(
|
|
http.MultipartFile.fromString(
|
|
attachment._field,
|
|
attachment._value,
|
|
filename: attachment._field,
|
|
),
|
|
);
|
|
}
|
|
|
|
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';
|
|
}
|
|
}
|
|
|
|
@visibleForTesting
|
|
int get reportsSent => _reportsSent;
|
|
|
|
/// Closes the client and cleans up any resources associated with it. This
|
|
/// will close the associated [http.Client].
|
|
void dispose() {
|
|
_httpClient.close();
|
|
}
|
|
}
|
|
|
|
/// The additional attachment to be added to a crash report.
|
|
class CrashReportAttachment {
|
|
final String _field;
|
|
final String _value;
|
|
|
|
new string({required this._field, required this._value});
|
|
}
|
|
|
|
/// A typedef to allow crash reporting to query as to whether it should send a
|
|
/// crash report.
|
|
typedef EnablementCallback = bool Function();
|