crash_reporting.dart 7.29 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7 8 9 10 11 12 13 14 15 16 17 18 19 20
import 'dart:async';

import 'package:file/file.dart';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';

import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/os.dart';
import '../base/platform.dart';
import '../project.dart';
import 'github_template.dart';
import 'reporting.dart';
21 22

/// Tells crash backend that the error is from the Flutter CLI.
23
const String _kProductId = 'Flutter_Tools';
24 25

/// Tells crash backend that this is a Dart error as opposed to, say, Java.
26
const String _kDartTypeId = 'DartError';
27 28

/// Crash backend host.
29
const String _kCrashServerHost = 'clients2.google.com';
30 31

/// Path to the crash servlet.
32
const String _kCrashEndpointPath = '/cr/report';
33 34 35

/// The field corresponding to the multipart/form-data file attachment where
/// crash backend expects to find the Dart stack trace.
36
const String _kStackTraceFileField = 'DartError';
37 38 39 40 41

/// The name of the file attached as [_kStackTraceFileField].
///
/// The precise value is not important. It is ignored by the crash back end, but
/// it must be supplied in the request.
42
const String _kStackTraceFilename = 'stacktrace_file';
43

44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
class CrashDetails {
  CrashDetails({
    @required this.command,
    @required this.error,
    @required this.stackTrace,
    @required this.doctorText,
  });

  final String command;
  final dynamic error;
  final StackTrace stackTrace;
  final String doctorText;
}

/// Reports information about the crash to the user.
class CrashReporter {
  CrashReporter({
    @required FileSystem fileSystem,
    @required Logger logger,
    @required FlutterProjectFactory flutterProjectFactory,
    @required HttpClient client,
  }) : _fileSystem = fileSystem,
       _logger = logger,
       _flutterProjectFactory = flutterProjectFactory,
       _client = client;

  final FileSystem _fileSystem;
  final Logger _logger;
  final FlutterProjectFactory _flutterProjectFactory;
  final HttpClient _client;

  /// Prints instructions for filing a bug about the crash.
  Future<void> informUser(CrashDetails details, File crashFile) async {
    _logger.printError('A crash report has been written to ${crashFile.path}.');
    _logger.printStatus('This crash may already be reported. Check GitHub for similar crashes.', emphasis: true);

    final String similarIssuesURL = GitHubTemplateCreator.toolCrashSimilarIssuesURL(details.error.toString());
    _logger.printStatus('$similarIssuesURL\n', wrap: false);
    _logger.printStatus('To report your crash to the Flutter team, first read the guide to filing a bug.', emphasis: true);
    _logger.printStatus('https://flutter.dev/docs/resources/bug-reports\n', wrap: false);

    _logger.printStatus('Create a new GitHub issue by pasting this link into your browser and completing the issue template. Thank you!', emphasis: true);

    final GitHubTemplateCreator gitHubTemplateCreator = GitHubTemplateCreator(
      fileSystem: _fileSystem,
      logger: _logger,
      flutterProjectFactory: _flutterProjectFactory,
      client: _client,
    );

    final String gitHubTemplateURL = await gitHubTemplateCreator.toolCrashIssueTemplateGitHubURL(
      details.command,
      details.error,
      details.stackTrace,
      details.doctorText,
    );
    _logger.printStatus('$gitHubTemplateURL\n', wrap: false);
  }
}

104
/// Sends crash reports to Google.
105
///
106 107 108 109 110
/// To override the behavior of this class, define a
/// `FLUTTER_CRASH_SERVER_BASE_URL` environment variable that points to a custom
/// crash reporting server. This is useful if your development environment is
/// behind a firewall and unable to send crash reports to Google, or when you
/// wish to use your own server for collecting crash reports from Flutter Tools.
111
class CrashReportSender {
112
  CrashReportSender({
113
    http.Client client,
114 115 116 117
    @required Usage usage,
    @required Platform platform,
    @required Logger logger,
    @required OperatingSystemUtils operatingSystemUtils,
118
  }) : _client = client ?? http.Client(),
119 120 121 122
      _usage = usage,
      _platform = platform,
      _logger = logger,
      _operatingSystemUtils = operatingSystemUtils;
123

124 125 126 127 128
  final http.Client _client;
  final Usage _usage;
  final Platform _platform;
  final Logger _logger;
  final OperatingSystemUtils _operatingSystemUtils;
129

130 131
  bool _crashReportSent = false;

132
  Uri get _baseUrl {
133
    final String overrideUrl = _platform.environment['FLUTTER_CRASH_SERVER_BASE_URL'];
134 135 136 137

    if (overrideUrl != null) {
      return Uri.parse(overrideUrl);
    }
138
    return Uri(
139 140 141 142 143 144 145
      scheme: 'https',
      host: _kCrashServerHost,
      port: 443,
      path: _kCrashEndpointPath,
    );
  }

146 147 148
  /// Sends one crash report.
  ///
  /// The report is populated from data in [error] and [stackTrace].
149
  Future<void> sendReport({
150
    @required dynamic error,
151
    @required StackTrace stackTrace,
152
    @required String Function() getFlutterVersion,
153
    @required String command,
154
  }) async {
155 156 157 158
    // Only send one crash report per run.
    if (_crashReportSent) {
      return;
    }
159
    try {
160 161 162 163
      final String flutterVersion = getFlutterVersion();

      // We don't need to report exceptions happening on user branches
      if (_usage.suppressAnalytics || RegExp(r'^\[user-branch\]\/').hasMatch(flutterVersion)) {
164
        return;
165
      }
166

167
      _logger.printTrace('Sending crash report to Google.');
168

169
      final Uri uri = _baseUrl.replace(
170 171 172 173 174 175
        queryParameters: <String, String>{
          'product': _kProductId,
          'version': flutterVersion,
        },
      );

176
      final http.MultipartRequest req = http.MultipartRequest('POST', uri);
177
      req.fields['uuid'] = _usage.clientId;
178 179
      req.fields['product'] = _kProductId;
      req.fields['version'] = flutterVersion;
180 181
      req.fields['osName'] = _platform.operatingSystem;
      req.fields['osVersion'] = _operatingSystemUtils.name; // this actually includes version
182 183
      req.fields['type'] = _kDartTypeId;
      req.fields['error_runtime_type'] = '${error.runtimeType}';
184
      req.fields['error_message'] = '$error';
185
      req.fields['comments'] = command;
186

187
      req.files.add(http.MultipartFile.fromString(
188
        _kStackTraceFileField,
189
        stackTrace.toString(),
190 191 192
        filename: _kStackTraceFilename,
      ));

193 194 195 196 197 198 199 200 201 202 203
      final http.StreamedResponse resp = await _client.send(req);

      if (resp.statusCode == HttpStatus.ok) {
        final String reportId = await http.ByteStream(resp.stream)
          .bytesToString();
        _logger.printTrace('Crash report sent (report ID: $reportId)');
        _crashReportSent = true;
      } else {
        _logger.printError('Failed to send crash report. Server responded with HTTP status code ${resp.statusCode}');
      }

204 205 206
    // Catch all exceptions to print the message that makes clear that the
    // crash logger crashed.
    } catch (sendError, sendStackTrace) { // ignore: avoid_catches_without_on_clauses
207
      if (sendError is SocketException || sendError is HttpException || sendError is http.ClientException) {
208
        _logger.printError('Failed to send crash report due to a network error: $sendError');
209 210
      } else {
        // If the sender itself crashes, just print. We did our best.
211
        _logger.printError('Crash report sender itself crashed: $sendError\n$sendStackTrace');
212 213 214 215
      }
    }
  }
}