diff --git a/.gitignore b/.gitignore
index c57b12a34b3b..3c2252a2439a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +1,15 @@
.DS_Store
.atom/
.idea
+.dart_tool/
.packages
.pub/
-pubspec.lock
-
Podfile.lock
Pods/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
-
GeneratedPluginRegistrant.java
-
+pubspec.lock
+packages/sentry/build/
+packages/sentry/android/
+packages/sentry/ios/
diff --git a/packages/sentry/.idea/modules.xml b/packages/sentry/.idea/modules.xml
new file mode 100644
index 000000000000..96a30c72817d
--- /dev/null
+++ b/packages/sentry/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/sentry/.idea/sentry.iml b/packages/sentry/.idea/sentry.iml
new file mode 100644
index 000000000000..7457fc5904d9
--- /dev/null
+++ b/packages/sentry/.idea/sentry.iml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/sentry/.idea/vcs.xml b/packages/sentry/.idea/vcs.xml
new file mode 100644
index 000000000000..94a25f7f4cb4
--- /dev/null
+++ b/packages/sentry/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/sentry/.travis.yml b/packages/sentry/.travis.yml
new file mode 100644
index 000000000000..b3a6410de40a
--- /dev/null
+++ b/packages/sentry/.travis.yml
@@ -0,0 +1,5 @@
+language: dart
+dart:
+ # - stable # there's no Dart 2 on the stable channel yet
+ - dev
+script: ./tool/presubmit.sh
diff --git a/packages/sentry/AUTHORS b/packages/sentry/AUTHORS
new file mode 100644
index 000000000000..fa93e5ec4e78
--- /dev/null
+++ b/packages/sentry/AUTHORS
@@ -0,0 +1,7 @@
+# Below is a list of people and organizations that have contributed
+# to package:sentry. Names should be added to the list like so:
+#
+# Name/Organization
+
+Google Inc.
+Simon Lightfoot
diff --git a/packages/sentry/CHANGELOG.md b/packages/sentry/CHANGELOG.md
new file mode 100644
index 000000000000..527ec150ece8
--- /dev/null
+++ b/packages/sentry/CHANGELOG.md
@@ -0,0 +1,58 @@
+# package:sentry changelog
+
+## 2.1.1
+
+- Defensively copy internal maps event attributes to
+ avoid shared mutable state (https://github.com/flutter/sentry/commit/044e4c1f43c2d199ed206e5529e2a630c90e4434)
+
+## 2.1.0
+
+- Support DNS format without secret key.
+- Remove dependency on `package:quiver`.
+- The `clock` argument to `SentryClient` constructor _should_ now be
+ `ClockProvider` (but still accepts `Clock` for backwards compatibility).
+
+## 2.0.2
+
+- Add support for user context in Sentry events.
+
+## 2.0.1
+
+- Invert stack frames to be compatible with Sentry's default culprit detection.
+
+## 2.0.0
+
+- Fixed deprecation warnings for Dart 2
+- Refactored tests to work with Dart 2
+
+## 1.0.0
+
+- first and last Dart 1-compatible release (we may fix bugs on a separate branch if there's demand)
+- fix code for Dart 2
+
+## 0.0.6
+
+- use UTC in the `timestamp` field
+
+## 0.0.5
+
+- remove sub-seconds from the timestamp
+
+## 0.0.4
+
+- parse and report async gaps in stack traces
+
+## 0.0.3
+
+- environment attributes
+- auto-generate event_id and timestamp for events
+
+## 0.0.2
+
+- parse and report stack traces
+- use x-sentry-error HTTP response header
+- gzip outgoing payloads by default
+
+## 0.0.1
+
+- basic ability to send exception reports to Sentry.io
diff --git a/packages/sentry/LICENSE b/packages/sentry/LICENSE
new file mode 100644
index 000000000000..6f2d1444dd98
--- /dev/null
+++ b/packages/sentry/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium 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.
\ No newline at end of file
diff --git a/packages/sentry/PATENTS b/packages/sentry/PATENTS
new file mode 100644
index 000000000000..ac39faf67938
--- /dev/null
+++ b/packages/sentry/PATENTS
@@ -0,0 +1,17 @@
+Google hereby grants to you a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this
+section) patent license to make, have made, use, offer to sell, sell,
+import, transfer, and otherwise run, modify and propagate the contents
+of this implementation, where such license applies only to those
+patent claims, both currently owned by Google and acquired in the
+future, licensable by Google that are necessarily infringed by this
+implementation. This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation. If you or your agent or exclusive licensee institute
+or order or agree to the institution of patent litigation or any other
+patent enforcement activity against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that this
+implementation constitutes direct or contributory patent infringement,
+or inducement of patent infringement, then any patent rights granted
+to you under this License for this implementation shall terminate as
+of the date such litigation is filed.
\ No newline at end of file
diff --git a/packages/sentry/README.md b/packages/sentry/README.md
new file mode 100644
index 000000000000..1bd7d9579eca
--- /dev/null
+++ b/packages/sentry/README.md
@@ -0,0 +1,60 @@
+# Sentry.io client for Dart
+
+[![Build Status](https://travis-ci.org/flutter/sentry.svg?branch=master)](https://travis-ci.org/flutter/sentry)
+
+Use this library in your Dart programs (Flutter, command-line and (TBD) AngularDart) to report errors thrown by your
+program to https://sentry.io error tracking service.
+
+## Versions
+
+`>=0.0.0 <2.0.0` is the range of versions compatible with Dart 1.
+
+`>=2.0.0 <3.0.0` is the range of versions compatible with Dart 2.
+
+## Usage
+
+Sign up for a Sentry.io account and get a DSN at http://sentry.io.
+
+Add `sentry` dependency to your `pubspec.yaml`:
+
+```yaml
+dependencies:
+ sentry: any
+```
+
+In your Dart code, import `package:sentry/sentry.dart` and create a `SentryClient` using the DSN issued by Sentry.io:
+
+```dart
+import 'package:sentry/sentry.dart';
+
+final SentryClient sentry = new SentryClient(dsn: YOUR_DSN);
+```
+
+In an exception handler, call `captureException()`:
+
+```dart
+main() async {
+ try {
+ doSomethingThatMightThrowAnError();
+ } catch(error, stackTrace) {
+ await sentry.captureException(
+ exception: error,
+ stackTrace: stackTrace,
+ );
+ }
+}
+```
+
+## Tips for catching errors
+
+- use a `try/catch` block
+- create a `Zone` with an error handler, e.g. using [runZoned][run_zoned]
+- in Flutter, use [FlutterError.onError][flutter_error]
+- use `Isolate.current.addErrorListener` to capture uncaught errors in the root zone
+
+[run_zoned]: https://api.dartlang.org/stable/dart-async/runZoned.html
+[flutter_error]: https://docs.flutter.io/flutter/foundation/FlutterError/onError.html
+
+## Found a bug?
+
+Please file it at https://github.com/flutter/flutter/issues/new
diff --git a/packages/sentry/bin/test.dart b/packages/sentry/bin/test.dart
new file mode 100644
index 000000000000..bbeadbe1fcd0
--- /dev/null
+++ b/packages/sentry/bin/test.dart
@@ -0,0 +1,51 @@
+// 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 'dart:io';
+
+import 'package:sentry/sentry.dart';
+
+/// Sends a test exception report to Sentry.io using this Dart client.
+Future main(List rawArgs) async {
+ if (rawArgs.length != 1) {
+ stderr.writeln(
+ 'Expected exactly one argument, which is the DSN issued by Sentry.io to your project.');
+ exit(1);
+ }
+
+ final String dsn = rawArgs.single;
+ final SentryClient client = new SentryClient(dsn: dsn);
+
+ try {
+ await foo();
+ } catch (error, stackTrace) {
+ print('Reporting the following stack trace: ');
+ print(stackTrace);
+ final SentryResponse response = await client.captureException(
+ exception: error,
+ stackTrace: stackTrace,
+ );
+
+ if (response.isSuccessful) {
+ print('SUCCESS\nid: ${response.eventId}');
+ } else {
+ print('FAILURE: ${response.error}');
+ }
+ } finally {
+ await client.close();
+ }
+}
+
+Future foo() async {
+ await bar();
+}
+
+Future bar() async {
+ await baz();
+}
+
+Future baz() async {
+ throw new StateError('This is a test error');
+}
diff --git a/packages/sentry/lib/sentry.dart b/packages/sentry/lib/sentry.dart
new file mode 100644
index 000000000000..ccf20f884b83
--- /dev/null
+++ b/packages/sentry/lib/sentry.dart
@@ -0,0 +1,489 @@
+// 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.
+
+/// A pure Dart client for Sentry.io crash reporting.
+library sentry;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:http/http.dart';
+import 'package:meta/meta.dart';
+import 'package:usage/uuid/uuid.dart';
+
+import 'src/stack_trace.dart';
+import 'src/utils.dart';
+import 'src/version.dart';
+
+export 'src/version.dart';
+
+/// Used to provide timestamp for logging.
+typedef ClockProvider = DateTime Function();
+
+/// Logs crash reports and events to the Sentry.io service.
+class SentryClient {
+ /// Sentry.io client identifier for _this_ client.
+ @visibleForTesting
+ static const String sentryClient = '$sdkName/$sdkVersion';
+
+ /// The default logger name used if no other value is supplied.
+ static const String defaultLoggerName = 'SentryClient';
+
+ /// Instantiates a client using [dsn] issued to your project by Sentry.io as
+ /// the endpoint for submitting events.
+ ///
+ /// [environmentAttributes] contain event attributes that do not change over
+ /// the course of a program's lifecycle. These attributes will be added to
+ /// all events captured via this client. The following attributes often fall
+ /// under this category: [Event.loggerName], [Event.serverName],
+ /// [Event.release], [Event.environment].
+ ///
+ /// If [compressPayload] is `true` the outgoing HTTP payloads are compressed
+ /// using gzip. Otherwise, the payloads are sent in plain UTF8-encoded JSON
+ /// text. If not specified, the compression is enabled by default.
+ ///
+ /// If [httpClient] is provided, it is used instead of the default client to
+ /// make HTTP calls to Sentry.io. This is useful in tests.
+ ///
+ /// If [clock] is provided, it is used to get time instead of the system
+ /// clock. This is useful in tests. Should be an implementation of [ClockProvider].
+ /// This parameter is dynamic to maintain backwards compatibility with
+ /// previous use of [Clock](https://pub.dartlang.org/documentation/quiver/latest/quiver.time/Clock-class.html)
+ /// from [`package:quiver`](https://pub.dartlang.org/packages/quiver).
+ ///
+ /// If [uuidGenerator] is provided, it is used to generate the "event_id"
+ /// field instead of the built-in random UUID v4 generator. This is useful in
+ /// tests.
+ factory SentryClient({
+ @required String dsn,
+ Event environmentAttributes,
+ bool compressPayload,
+ Client httpClient,
+ dynamic clock,
+ UuidGenerator uuidGenerator,
+ }) {
+ httpClient ??= new Client();
+ clock ??= _getUtcDateTime;
+ uuidGenerator ??= _generateUuidV4WithoutDashes;
+ compressPayload ??= true;
+
+ final ClockProvider clockProvider =
+ clock is ClockProvider ? clock : clock.get;
+
+ final Uri uri = Uri.parse(dsn);
+ final List userInfo = uri.userInfo.split(':');
+
+ assert(() {
+ if (uri.pathSegments.isEmpty)
+ throw new ArgumentError(
+ 'Project ID not found in the URI path of the DSN URI: $dsn');
+
+ return true;
+ }());
+
+ final String publicKey = userInfo[0];
+ final String secretKey = userInfo.length >= 2 ? userInfo[1] : null;
+ final String projectId = uri.pathSegments.last;
+
+ return new SentryClient._(
+ httpClient: httpClient,
+ clock: clockProvider,
+ uuidGenerator: uuidGenerator,
+ environmentAttributes: environmentAttributes,
+ dsnUri: uri,
+ publicKey: publicKey,
+ secretKey: secretKey,
+ projectId: projectId,
+ compressPayload: compressPayload,
+ );
+ }
+
+ SentryClient._({
+ @required Client httpClient,
+ @required ClockProvider clock,
+ @required UuidGenerator uuidGenerator,
+ @required this.environmentAttributes,
+ @required this.dsnUri,
+ @required this.publicKey,
+ this.secretKey,
+ @required this.compressPayload,
+ @required this.projectId,
+ }) : _httpClient = httpClient,
+ _clock = clock,
+ _uuidGenerator = uuidGenerator;
+
+ final Client _httpClient;
+ final ClockProvider _clock;
+ final UuidGenerator _uuidGenerator;
+
+ /// Contains [Event] attributes that are automatically mixed into all events
+ /// captured through this client.
+ ///
+ /// This event is designed to contain static values that do not change from
+ /// event to event, such as local operating system version, the version of
+ /// Dart/Flutter SDK, etc. These attributes have lower precedence than those
+ /// supplied in the even passed to [capture].
+ final Event environmentAttributes;
+
+ /// Whether to compress payloads sent to Sentry.io.
+ final bool compressPayload;
+
+ /// The DSN URI.
+ @visibleForTesting
+ final Uri dsnUri;
+
+ /// The Sentry.io public key for the project.
+ @visibleForTesting
+ final String publicKey;
+
+ /// The Sentry.io secret key for the project.
+ @visibleForTesting
+ final String secretKey;
+
+ /// The ID issued by Sentry.io to your project.
+ ///
+ /// Attached to the event payload.
+ final String projectId;
+
+ /// Information about the current user.
+ ///
+ /// This information is sent with every logged event. If the value
+ /// of this field is updated, all subsequent events will carry the
+ /// new information.
+ ///
+ /// [Event.userContext] overrides the [User] context set here.
+ ///
+ /// See also:
+ /// * https://docs.sentry.io/learn/context/#capturing-the-user
+ User userContext;
+
+ @visibleForTesting
+ String get postUri =>
+ '${dsnUri.scheme}://${dsnUri.host}/api/$projectId/store/';
+
+ /// Reports an [event] to Sentry.io.
+ Future capture({@required Event event}) async {
+ final DateTime now = _clock();
+ String authHeader = 'Sentry sentry_version=6, sentry_client=$sentryClient, '
+ 'sentry_timestamp=${now.millisecondsSinceEpoch}, sentry_key=$publicKey';
+ if (secretKey != null) {
+ authHeader += ', sentry_secret=$secretKey';
+ }
+
+ final Map headers = {
+ 'User-Agent': '$sentryClient',
+ 'Content-Type': 'application/json',
+ 'X-Sentry-Auth': authHeader,
+ };
+
+ final Map data = {
+ 'project': projectId,
+ 'event_id': _uuidGenerator(),
+ 'timestamp': formatDateAsIso8601WithSecondPrecision(now),
+ 'logger': defaultLoggerName,
+ };
+
+ if (environmentAttributes != null)
+ mergeAttributes(environmentAttributes.toJson(), into: data);
+
+ // Merge the user context.
+ if (userContext != null) {
+ mergeAttributes({'user': userContext.toJson()}, into: data);
+ }
+ mergeAttributes(event.toJson(), into: data);
+
+ List body = utf8.encode(json.encode(data));
+ if (compressPayload) {
+ headers['Content-Encoding'] = 'gzip';
+ body = GZIP.encode(body);
+ }
+
+ final Response response =
+ await _httpClient.post(postUri, headers: headers, body: body);
+
+ if (response.statusCode != 200) {
+ String errorMessage =
+ 'Sentry.io responded with HTTP ${response.statusCode}';
+ if (response.headers['x-sentry-error'] != null)
+ errorMessage += ': ${response.headers['x-sentry-error']}';
+ return new SentryResponse.failure(errorMessage);
+ }
+
+ final String eventId = json.decode(response.body)['id'];
+ return new SentryResponse.success(eventId: eventId);
+ }
+
+ /// Reports the [exception] and optionally its [stackTrace] to Sentry.io.
+ Future captureException({
+ @required dynamic exception,
+ dynamic stackTrace,
+ }) {
+ final Event event = new Event(
+ exception: exception,
+ stackTrace: stackTrace,
+ );
+ return capture(event: event);
+ }
+
+ Future close() async {
+ _httpClient.close();
+ }
+
+ @override
+ String toString() => '$SentryClient("$postUri")';
+}
+
+/// A response from Sentry.io.
+///
+/// If [isSuccessful] the [eventId] field will contain the ID assigned to the
+/// captured event by the Sentry.io backend. Otherwise, the [error] field will
+/// contain the description of the error.
+@immutable
+class SentryResponse {
+ const SentryResponse.success({@required this.eventId})
+ : isSuccessful = true,
+ error = null;
+
+ const SentryResponse.failure(this.error)
+ : isSuccessful = false,
+ eventId = null;
+
+ /// Whether event was submitted successfully.
+ final bool isSuccessful;
+
+ /// The ID Sentry.io assigned to the submitted event for future reference.
+ final String eventId;
+
+ /// Error message, if the response is not successful.
+ final String error;
+}
+
+typedef UuidGenerator = String Function();
+
+String _generateUuidV4WithoutDashes() =>
+ new Uuid().generateV4().replaceAll('-', '');
+
+/// Severity of the logged [Event].
+@immutable
+class SeverityLevel {
+ static const fatal = const SeverityLevel._('fatal');
+ static const error = const SeverityLevel._('error');
+ static const warning = const SeverityLevel._('warning');
+ static const info = const SeverityLevel._('info');
+ static const debug = const SeverityLevel._('debug');
+
+ const SeverityLevel._(this.name);
+
+ /// API name of the level as it is encoded in the JSON protocol.
+ final String name;
+}
+
+/// Sentry does not take a timezone and instead expects the date-time to be
+/// submitted in UTC timezone.
+DateTime _getUtcDateTime() => new DateTime.now().toUtc();
+
+/// An event to be reported to Sentry.io.
+@immutable
+class Event {
+ /// Refers to the default fingerprinting algorithm.
+ ///
+ /// You do not need to specify this value unless you supplement the default
+ /// fingerprint with custom fingerprints.
+ static const String defaultFingerprint = '{{ default }}';
+
+ /// Creates an event.
+ const Event({
+ this.loggerName,
+ this.serverName,
+ this.release,
+ this.environment,
+ this.message,
+ this.exception,
+ this.stackTrace,
+ this.level,
+ this.culprit,
+ this.tags,
+ this.extra,
+ this.fingerprint,
+ this.userContext,
+ });
+
+ /// The logger that logged the event.
+ final String loggerName;
+
+ /// Identifies the server that logged this event.
+ final String serverName;
+
+ /// The version of the application that logged the event.
+ final String release;
+
+ /// The environment that logged the event, e.g. "production", "staging".
+ final String environment;
+
+ /// Event message.
+ ///
+ /// Generally an event either contains a [message] or an [exception].
+ final String message;
+
+ /// An object that was thrown.
+ ///
+ /// It's `runtimeType` and `toString()` are logged. If this behavior is
+ /// undesirable, consider using a custom formatted [message] instead.
+ final dynamic exception;
+
+ /// The stack trace corresponding to the thrown [exception].
+ ///
+ /// Can be `null`, a [String], or a [StackTrace].
+ final dynamic stackTrace;
+
+ /// How important this event is.
+ final SeverityLevel level;
+
+ /// What caused this event to be logged.
+ final String culprit;
+
+ /// Name/value pairs that events can be searched by.
+ final Map tags;
+
+ /// Arbitrary name/value pairs attached to the event.
+ ///
+ /// Sentry.io docs do not talk about restrictions on the values, other than
+ /// they must be JSON-serializable.
+ final Map extra;
+
+ /// Information about the current user.
+ ///
+ /// The value in this field overrides the user context
+ /// set in [SentryClient.userContext] for this logged event.
+ final User userContext;
+
+ /// Used to deduplicate events by grouping ones with the same fingerprint
+ /// together.
+ ///
+ /// If not specified a default deduplication fingerprint is used. The default
+ /// fingerprint may be supplemented by additional fingerprints by specifying
+ /// multiple values. The default fingerprint can be specified by adding
+ /// [defaultFingerprint] to the list in addition to your custom values.
+ ///
+ /// Examples:
+ ///
+ /// // A completely custom fingerprint:
+ /// var custom = ['foo', 'bar', 'baz'];
+ /// // A fingerprint that supplements the default one with value 'foo':
+ /// var supplemented = [Event.defaultFingerprint, 'foo'];
+ final List fingerprint;
+
+ /// Serializes this event to JSON.
+ Map toJson() {
+ final Map json = {
+ 'platform': sdkPlatform,
+ 'sdk': {
+ 'version': sdkVersion,
+ 'name': sdkName,
+ },
+ };
+
+ if (loggerName != null) json['logger'] = loggerName;
+
+ if (serverName != null) json['server_name'] = serverName;
+
+ if (release != null) json['release'] = release;
+
+ if (environment != null) json['environment'] = environment;
+
+ if (message != null) json['message'] = message;
+
+ if (exception != null) {
+ json['exception'] = [
+ {
+ 'type': '${exception.runtimeType}',
+ 'value': '$exception',
+ }
+ ];
+ }
+
+ if (stackTrace != null) {
+ json['stacktrace'] = {
+ 'frames': encodeStackTrace(stackTrace),
+ };
+ }
+
+ if (level != null) json['level'] = level.name;
+
+ if (culprit != null) json['culprit'] = culprit;
+
+ if (tags != null && tags.isNotEmpty) json['tags'] = tags;
+
+ if (extra != null && extra.isNotEmpty) json['extra'] = extra;
+
+ Map userContextMap;
+ if (userContext != null &&
+ (userContextMap = userContext.toJson()).isNotEmpty)
+ json['user'] = userContextMap;
+
+ if (fingerprint != null && fingerprint.isNotEmpty)
+ json['fingerprint'] = fingerprint;
+
+ return json;
+ }
+}
+
+/// Describes the current user associated with the application, such as the
+/// currently signed in user.
+///
+/// The user can be specified globally in the [SentryClient.userContext] field,
+/// or per event in the [Event.userContext] field.
+///
+/// You should provide at least either an [id] (a unique identifier for an
+/// authenticated user) or [ipAddress] (their IP address).
+///
+/// Conforms to the User Interface contract for Sentry
+/// https://docs.sentry.io/clientdev/interfaces/user/.
+///
+/// The outgoing JSON representation is:
+///
+/// ```
+/// "user": {
+/// "id": "unique_id",
+/// "username": "my_user",
+/// "email": "foo@example.com",
+/// "ip_address": "127.0.0.1",
+/// "subscription": "basic"
+/// }
+/// ```
+class User {
+ /// A unique identifier of the user.
+ final String id;
+
+ /// The username of the user.
+ final String username;
+
+ /// The email address of the user.
+ final String email;
+
+ /// The IP of the user.
+ final String ipAddress;
+
+ /// Any other user context information that may be helpful.
+ ///
+ /// These keys are stored as extra information but not specifically processed
+ /// by Sentry.
+ final Map extras;
+
+ /// At a minimum you must set an [id] or an [ipAddress].
+ const User({this.id, this.username, this.email, this.ipAddress, this.extras})
+ : assert(id != null || ipAddress != null);
+
+ /// Produces a [Map] that can be serialized to JSON.
+ Map toJson() {
+ return {
+ "id": id,
+ "username": username,
+ "email": email,
+ "ip_address": ipAddress,
+ "extras": extras,
+ };
+ }
+}
diff --git a/packages/sentry/lib/src/stack_trace.dart b/packages/sentry/lib/src/stack_trace.dart
new file mode 100644
index 000000000000..d1175d69213f
--- /dev/null
+++ b/packages/sentry/lib/src/stack_trace.dart
@@ -0,0 +1,57 @@
+// 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 'package:stack_trace/stack_trace.dart';
+
+/// Sentry.io JSON encoding of a stack frame for the asynchronous suspension,
+/// which is the gap between asynchronous calls.
+const Map asynchronousGapFrameJson = const {
+ 'abs_path': '',
+};
+
+/// Encodes [stackTrace] as JSON in the Sentry.io format.
+///
+/// [stackTrace] must be [String] or [StackTrace].
+List