crash_reporting.dart 3.92 KB
Newer Older
1 2 3 4 5 6 7 8
// Copyright 2017 The Chromium Authors. 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 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
9
import 'package:stack_trace/stack_trace.dart';
10 11

import 'base/io.dart';
12 13
import 'base/os.dart';
import 'base/platform.dart';
14 15 16 17
import 'globals.dart';
import 'usage.dart';

/// Tells crash backend that the error is from the Flutter CLI.
18
const String _kProductId = 'Flutter_Tools';
19 20

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

/// Crash backend host.
24
const String _kCrashServerHost = 'clients2.google.com';
25 26

/// Path to the crash servlet.
27
const String _kCrashEndpointPath = '/cr/report';
28 29 30

/// The field corresponding to the multipart/form-data file attachment where
/// crash backend expects to find the Dart stack trace.
31
const String _kStackTraceFileField = 'DartError';
32 33 34 35 36

/// 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.
37
const String _kStackTraceFilename = 'stacktrace_file';
38 39 40

/// Sends crash reports to Google.
class CrashReportSender {
41
  static final Uri _baseUri = new Uri(
42 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
      scheme: 'https',
      host: _kCrashServerHost,
      port: 443,
      path: _kCrashEndpointPath,
  );

  static CrashReportSender _instance;

  CrashReportSender._(this._client);

  static CrashReportSender get instance => _instance ?? new CrashReportSender._(new http.Client());

  /// Overrides the default [http.Client] with [client] for testing purposes.
  @visibleForTesting
  static void initializeWith(http.Client client) {
    _instance = new CrashReportSender._(client);
  }

  final http.Client _client;
  final Usage _usage = Usage.instance;

  /// Sends one crash report.
  ///
  /// The report is populated from data in [error] and [stackTrace].
  Future<Null> sendReport({
    @required dynamic error,
68
    @required StackTrace stackTrace,
69
    @required String getFlutterVersion(),
70 71 72 73 74 75 76
  }) async {
    try {
      if (_usage.suppressAnalytics)
        return null;

      printStatus('Sending crash report to Google.');

77
      final String flutterVersion = getFlutterVersion();
78
      final Uri uri = _baseUri.replace(
79 80 81 82 83 84
        queryParameters: <String, String>{
          'product': _kProductId,
          'version': flutterVersion,
        },
      );

85
      final http.MultipartRequest req = new http.MultipartRequest('POST', uri);
86
      req.fields['uuid'] = _usage.clientId;
87 88
      req.fields['product'] = _kProductId;
      req.fields['version'] = flutterVersion;
89
      req.fields['osName'] = platform.operatingSystem;
90
      req.fields['osVersion'] = os.name; // this actually includes version
91 92 93
      req.fields['type'] = _kDartTypeId;
      req.fields['error_runtime_type'] = '${error.runtimeType}';

94
      final String stackTraceWithRelativePaths = new Chain.parse(stackTrace.toString()).terse.toString();
95 96
      req.files.add(new http.MultipartFile.fromString(
        _kStackTraceFileField,
97
        stackTraceWithRelativePaths,
98 99 100
        filename: _kStackTraceFilename,
      ));

101
      final http.StreamedResponse resp = await _client.send(req);
102 103

      if (resp.statusCode == 200) {
104
        final String reportId = await new http.ByteStream(resp.stream)
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
            .bytesToString();
        printStatus('Crash report sent (report ID: $reportId)');
      } else {
        printError('Failed to send crash report. Server responded with HTTP status code ${resp.statusCode}');
      }
    } catch (sendError, sendStackTrace) {
      if (sendError is SocketException) {
        printError('Failed to send crash report due to a network error: $sendError');
      } else {
        // If the sender itself crashes, just print. We did our best.
        printError('Crash report sender itself crashed: $sendError\n$sendStackTrace');
      }
    }
  }
}