Skip to content

Commit

Permalink
Merge pull request #239 from bugsnag/releases/v3.1.0
Browse files Browse the repository at this point in the history
Release v3.1.0
  • Loading branch information
richardelms committed Apr 9, 2024
2 parents e9f0684 + d308c71 commit d7c6d59
Show file tree
Hide file tree
Showing 13 changed files with 178 additions and 22 deletions.
6 changes: 6 additions & 0 deletions .gitmodules
@@ -0,0 +1,6 @@
[submodule "packages/bugsnag_http_client"]
path = packages/bugsnag_http_client
url = git@github.com:bugsnag/bugsnag-flutter-http-client.git
[submodule "packages/bugsnag_flutter_dart_io_http_client"]
path = packages/bugsnag_flutter_dart_io_http_client
url = git@github.com:bugsnag/bugsnag-flutter-dart-io-http-client.git
8 changes: 8 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,14 @@
# Changelog


## 3.1.0 (2024-04-09)

This release introduces a new `networkInstrumentation` listener so that the new [http](https://pub.dev/packages/bugsnag_http_client) and [dart:io](https://pub.dev/packages/bugsnag_flutter_dart_io_http_client) wrappers can trigger network breadcrumbs. It also introduces support for [dio](https://pub.dev/packages/dio).

The previous `bugsnag_breadcrumbs_http` and `bugsnag_breadcrumbs_dart_io` packages will continue to work but will be deprecated in the next major release.

See our [online docs](https://docs.bugsnag.com/platforms/flutter/customizing-breadcrumbs/#network-request-breadcrumbs) for full integration instructions.

## 3.0.2 (2024-02-28)

- Change the bugsnag_breadcrumbs_http http dependancy to ">=0.13.4" so that there are less strict version requirements [#235](https://github.com/bugsnag/bugsnag-flutter/pull/235)
Expand Down
23 changes: 12 additions & 11 deletions examples/flutter/lib/main.dart
@@ -1,12 +1,13 @@
import 'dart:async';

import 'package:bugsnag_breadcrumbs_dart_io/bugsnag_breadcrumbs_dart_io.dart';
import 'package:bugsnag_breadcrumbs_http/bugsnag_breadcrumbs_http.dart' as http;
import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' as dart_io;
import 'package:bugsnag_http_client/bugsnag_http_client.dart' as http;
import 'package:bugsnag_example/native_crashes.dart';
import 'package:bugsnag_flutter/bugsnag_flutter.dart';
import 'package:flutter/material.dart';

void main() async {
http.addSubscriber(bugsnag.networkInstrumentation);
dart_io.addSubscriber(bugsnag.networkInstrumentation);
await bugsnag.start(
// Find your API key in the settings menu of your Bugsnag dashboard
apiKey: 'add_your_api_key_here',
Expand Down Expand Up @@ -48,7 +49,7 @@ class ExampleHomeScreen extends StatelessWidget {

// Unhandled exceptions will automatically be detected and reported.
// They are displayed with an 'Error' severity on the dashboard.
void _unhandledFlutterError() {
void _unhandledFlutterError() async {
throw Exception('Unhandled Exception');
}

Expand Down Expand Up @@ -86,8 +87,8 @@ class ExampleHomeScreen extends StatelessWidget {
void _networkError() async =>
http.get(Uri.parse('https://example.invalid')).ignore();

void _networkHttpClient() async {
var client = BugsnagHttpClient();
void _networkDartIoHttpClient() async {
var client = dart_io.HttpClient();
try {
final request = await client.getUrl(Uri.parse('https://example.com'));
await request.close();
Expand Down Expand Up @@ -154,19 +155,19 @@ class ExampleHomeScreen extends StatelessWidget {
),
ElevatedButton(
onPressed: _networkSuccess,
child: const Text('Success'),
child: const Text('Http Success'),
),
ElevatedButton(
onPressed: _networkFailure,
child: const Text('Failure'),
child: const Text('Http Failure'),
),
ElevatedButton(
onPressed: _networkError,
child: const Text('Error'),
child: const Text('Http Error'),
),
ElevatedButton(
onPressed: _networkHttpClient,
child: const Text('HttpClient'),
onPressed: _networkDartIoHttpClient,
child: const Text('Dart Io Success'),
),
],
),
Expand Down
10 changes: 4 additions & 6 deletions examples/flutter/pubspec.yaml
Expand Up @@ -27,17 +27,15 @@ dependencies:
# the parent directory to use the current plugin's version.
path: ../../packages/bugsnag_flutter

bugsnag_breadcrumbs_dart_io:
path: ../../packages/bugsnag_breadcrumbs_dart_io

bugsnag_breadcrumbs_http:
path: ../../packages/bugsnag_breadcrumbs_http
bugsnag_http_client: 1.2.0
bugsnag_flutter_dart_io_http_client: 1.2.0

# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2

http: ^0.13.4
http: ^1.1.0


dev_dependencies:
flutter_test:
Expand Down
29 changes: 29 additions & 0 deletions features/breadcrumbs.feature
Expand Up @@ -16,3 +16,32 @@ Feature: Start Bugsnag from Flutter
And the error payload field "events.0.breadcrumbs.1.metaData.object.list.2" is true
And the error payload field "events.0.breadcrumbs.1.name" equals "Manual breadcrumb"
And the error payload field "events.0.breadcrumbs.1.type" equals "manual"

Scenario: Http Wrapper Breadcrumbs
Given I run "HttpBreadcrumbScenario"
And I wait to receive an error
Then the error payload field "events" is an array with 1 elements
And the error payload field "events.0.breadcrumbs" is an array with 2 elements
And the error payload field "events.0.breadcrumbs.1.name" equals "package:http request succeeded"
And the error payload field "events.0.breadcrumbs.1.type" equals "request"
And the error payload field "events.0.breadcrumbs.1.metaData.status" equals 200
And the error payload field "events.0.breadcrumbs.1.metaData.method" equals "GET"
And the error payload field "events.0.breadcrumbs.1.metaData.duration" is greater than 1
And the error payload field "events.0.breadcrumbs.1.metaData.url" equals "http://www.google.com"
And the error payload field "events.0.breadcrumbs.1.metaData.responseContentLength" is greater than 1
And the error payload field "events.0.breadcrumbs.1.metaData.urlParams" equals "test=test"

Scenario: Dart IO Wrapper Breadcrumbs
Given I run "DartIoHttpBreadcrumbScenario"
And I wait to receive an error
Then the error payload field "events" is an array with 1 elements
And the error payload field "events.0.breadcrumbs" is an array with 2 elements
And the error payload field "events.0.breadcrumbs.1.name" equals "dart:io request succeeded"
And the error payload field "events.0.breadcrumbs.1.type" equals "request"
And the error payload field "events.0.breadcrumbs.1.metaData.status" equals 200
And the error payload field "events.0.breadcrumbs.1.metaData.method" equals "GET"
And the error payload field "events.0.breadcrumbs.1.metaData.duration" is greater than 1
And the error payload field "events.0.breadcrumbs.1.metaData.url" equals "http://www.google.com"
And the error payload field "events.0.breadcrumbs.1.metaData.responseContentLength" is greater than 1
And the error payload field "events.0.breadcrumbs.1.metaData.urlParams" equals "test=test"

@@ -0,0 +1,24 @@
import 'package:MazeRunner/scenarios/scenario.dart';
import 'package:bugsnag_flutter/bugsnag_flutter.dart';
import 'package:bugsnag_flutter_dart_io_http_client/bugsnag_flutter_dart_io_http_client.dart' as dart_io;


class DartIoHttpBreadcrumbScenario extends Scenario {
@override
Future<void> run() async {
dart_io.addSubscriber(bugsnag.networkInstrumentation);
await bugsnag.start(
enabledBreadcrumbTypes: {BugsnagEnabledBreadcrumbType.state},
endpoints: endpoints,
);

final client = dart_io.HttpClient();
try {
final request = await client.getUrl(Uri.parse('http://www.google.com?test=test'));
await request.close();
} finally {
client.close();
}
await bugsnag.notify(Exception('DartIoHttpBreadcrumbScenario'), null);
}
}
16 changes: 16 additions & 0 deletions features/fixtures/app/lib/scenarios/http_breadcrumb_scenario.dart
@@ -0,0 +1,16 @@
import 'package:MazeRunner/scenarios/scenario.dart';
import 'package:bugsnag_flutter/bugsnag_flutter.dart';
import 'package:bugsnag_http_client/bugsnag_http_client.dart' as http;

class HttpBreadcrumbScenario extends Scenario {
@override
Future<void> run() async {
http.addSubscriber(bugsnag.networkInstrumentation);
await bugsnag.start(
enabledBreadcrumbTypes: {BugsnagEnabledBreadcrumbType.state},
endpoints: endpoints,
);
await http.get(Uri.parse("http://www.google.com?test=test"));
await bugsnag.notify(Exception('HttpBreadcrumbScenario'), null);
}
}
4 changes: 4 additions & 0 deletions features/fixtures/app/lib/scenarios/scenarios.dart
Expand Up @@ -20,6 +20,8 @@ import 'scenario.dart';
import 'start_bugsnag_scenario.dart';
import 'throw_exception_scenario.dart';
import 'unhandled_exception_scenario.dart';
import 'http_breadcrumb_scenario.dart';
import 'dart_io_http_breadcrumb_scenario.dart';

class ScenarioInfo<T extends Scenario> {
const ScenarioInfo(this.name, this.init);
Expand Down Expand Up @@ -55,4 +57,6 @@ final List<ScenarioInfo<Scenario>> scenarios = [
ScenarioInfo('ThrowExceptionScenario', () => ThrowExceptionScenario()),
ScenarioInfo(
'UnhandledExceptionScenario', () => UnhandledExceptionScenario()),
ScenarioInfo("HttpBreadcrumbScenario", () => HttpBreadcrumbScenario()),
ScenarioInfo("DartIoHttpBreadcrumbScenario", () => DartIoHttpBreadcrumbScenario()),
];
7 changes: 5 additions & 2 deletions features/fixtures/app/pubspec.yaml
Expand Up @@ -37,9 +37,12 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
http: ^0.13.4
path_provider: ^2.0.15

http: ^1.1.0
bugsnag_http_client:
path: ../../../packages/bugsnag_http_client
bugsnag_flutter_dart_io_http_client:
path: ../../../packages/bugsnag_flutter_dart_io_http_client
dev_dependencies:
flutter_test:
sdk: flutter
Expand Down
69 changes: 67 additions & 2 deletions packages/bugsnag_flutter/lib/src/client.dart
Expand Up @@ -17,10 +17,13 @@ import 'model.dart';
final _notifier = {
'name': 'Flutter Bugsnag Notifier',
'url': 'https://github.com/bugsnag/bugsnag-flutter',
'version': '3.0.2'
'version': '3.1.0'
};

abstract class BugsnagClient {

final Map<String, Stopwatch> _openNetworkRequests = {};

/// An utility error handling function that will send reported errors to
/// Bugsnag as unhandled. The [errorHandler] is suitable for use with
/// common Dart error callbacks such as [runZonedGuarded] or [Future.onError].
Expand Down Expand Up @@ -234,6 +237,64 @@ abstract class BugsnagClient {

/// Removes a previously added "on error" callback.
void removeOnError(BugsnagOnErrorCallback onError);

networkInstrumentation(dynamic data) {
try {
if (data is! Map<String, dynamic>) return;
String? status = data["status"];
String? requestId = data["request_id"];
if (requestId == null || status == null) return;

if (status == "started") {
_onRequestStarted(requestId);
} else if (status == "complete") {
_onRequestComplete(requestId, data);
}
} catch (e) {
// Fail silently
}
}

void _onRequestStarted(String requestId) {
final stopwatch = Stopwatch()..start();
_openNetworkRequests[requestId] = stopwatch;
}

void _onRequestComplete(String requestId, dynamic data) {
final stopwatch = _openNetworkRequests.remove(requestId);
if (stopwatch != null && data is Map<String, dynamic>) {
final duration = stopwatch.elapsedMilliseconds;
final String? clientName = data["client"];
if (clientName == null) return;

String params = "";
final url = data["url"];
final splitUrl = url.split("?");
if (splitUrl != null && splitUrl.length > 1) {
params = splitUrl.last;
}
final int? statusCode = data["status_code"];
if (statusCode == null) return;

final String status = statusCode < 400 ? "succeeded" : "failed";
// Assuming leaveBreadcrumb is a predefined method to log the event
leaveBreadcrumb("$clientName request $status", metadata: {
"duration": duration,
"method": data["http_method"],
"url": splitUrl.first,
if(params.isNotEmpty)
"urlParams": params,
if(data["request_content_length"] != null && data["request_content_length"] > 0)
"requestContentLength": data["request_content_length"],
if(data["response_content_length"] != null && data["response_content_length"] > 0)
"responseContentLength": data["response_content_length"],
"status": statusCode,
},
type: BugsnagBreadcrumbType.request,
);
}
}

}

mixin DelegateClient implements BugsnagClient {
Expand Down Expand Up @@ -338,7 +399,7 @@ mixin DelegateClient implements BugsnagClient {
client.removeOnError(onError);
}

class ChannelClient implements BugsnagClient {
class ChannelClient extends BugsnagClient {
FlutterExceptionHandler? _previousFlutterOnError;
ErrorCallback? _previousPlatformDispatcherOnError;

Expand Down Expand Up @@ -572,6 +633,9 @@ class ChannelClient implements BugsnagClient {

Future<void> _deliverEvent(BugsnagEvent event) =>
_channel.invokeMethod('deliverEvent', event);

@override
networkInstrumentation(data) {}
}

/// The primary `Client`. Typically this class is not accessed directly, and
Expand Down Expand Up @@ -769,6 +833,7 @@ class Bugsnag extends BugsnagClient with DelegateClient {

return const <String>{};
}

}

/// In order to determine where a crash happens Bugsnag needs to know which
Expand Down
2 changes: 1 addition & 1 deletion packages/bugsnag_flutter/pubspec.yaml
@@ -1,6 +1,6 @@
name: bugsnag_flutter
description: Bugsnag crash monitoring and reporting tool for Flutter apps
version: 3.0.2
version: 3.1.0
homepage: https://www.bugsnag.com/
documentation: https://docs.bugsnag.com/platforms/flutter/
repository: https://github.com/bugsnag/bugsnag-flutter
Expand Down
1 change: 1 addition & 0 deletions packages/bugsnag_flutter_dart_io_http_client
1 change: 1 addition & 0 deletions packages/bugsnag_http_client
Submodule bugsnag_http_client added at 04a691

0 comments on commit d7c6d59

Please sign in to comment.