Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

* Feat: Add current route as transaction (#615)

# 6.1.0-alpha.2

* Bump Sentry Android SDK to [5.2.0](https://github.com/getsentry/sentry-dart/pull/594) (#594)
Expand Down
1 change: 1 addition & 0 deletions dart/lib/src/enricher/web_enricher_event_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class WebEnricherEventProcessor extends EventProcessor {
return event.copyWith(
contexts: contexts,
request: _getRequest(event.request),
transaction: event.transaction ?? _window.location.pathname,
);
}

Expand Down
14 changes: 14 additions & 0 deletions dart/test/enricher/web_enricher_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ void main() {
fixture = Fixture();
});

test('add path as transaction if transaction is null', () async {
var enricher = fixture.getSut();
final event = await enricher.apply(SentryEvent());

expect(event.transaction, isNotNull);
});

test("don't overwrite transaction", () async {
var enricher = fixture.getSut();
final event = await enricher.apply(SentryEvent(transaction: 'foobar'));

expect(event.transaction, 'foobar');
});

test('add request with user-agent header', () async {
var enricher = fixture.getSut();
final event = await enricher.apply(SentryEvent());
Expand Down
6 changes: 6 additions & 0 deletions flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,12 @@ class SecondaryScaffold extends StatelessWidget {
Navigator.pop(context);
},
),
MaterialButton(
child: const Text('throw uncaught exception'),
onPressed: () {
throw Exception('Exception from SecondaryScaffold');
},
),
],
),
),
Expand Down
28 changes: 23 additions & 5 deletions flutter/lib/src/navigation/sentry_navigator_observer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,25 @@ const _navigationKey = 'navigation';
/// // other parameter ...
/// )
/// ```
/// Enabling the [setRouteNameAsTransaction] option overrides the current
/// [Scope.transaction]. So be careful when this is used together with
/// performance monitoring.
///
/// See also:
/// - [RouteObserver](https://api.flutter.dev/flutter/widgets/RouteObserver-class.html)
/// - [Navigating with arguments](https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments)
class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
SentryNavigatorObserver({Hub? hub}) : hub = hub ?? HubAdapter();
SentryNavigatorObserver({Hub? hub, bool setRouteNameAsTransaction = false})
: _hub = hub ?? HubAdapter(),
_setRouteNameAsTransaction = setRouteNameAsTransaction;

final Hub hub;
final Hub _hub;
final bool _setRouteNameAsTransaction;

@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPush(route, previousRoute);
_setCurrentRoute(route.settings.name);
_addBreadcrumb(
type: 'didPush',
from: previousRoute?.settings,
Expand All @@ -52,7 +59,7 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);

_setCurrentRoute(newRoute?.settings.name);
_addBreadcrumb(
type: 'didReplace',
from: oldRoute?.settings,
Expand All @@ -63,7 +70,7 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPop(route, previousRoute);

_setCurrentRoute(previousRoute?.settings.name);
_addBreadcrumb(
type: 'didPop',
from: route.settings,
Expand All @@ -76,12 +83,23 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
RouteSettings? from,
RouteSettings? to,
}) {
hub.addBreadcrumb(RouteObserverBreadcrumb(
_hub.addBreadcrumb(RouteObserverBreadcrumb(
navigationType: type,
from: from,
to: to,
));
}

void _setCurrentRoute(String? name) {
if (name == null) {
return;
}
if (_setRouteNameAsTransaction) {
_hub.configureScope((scope) {
scope.transaction = name;
});
}
}
}

/// This class makes it easier to record breadcrumbs for events of Flutters
Expand Down
91 changes: 86 additions & 5 deletions flutter/test/sentry_navigator_observer_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

import 'mocks.dart';
import 'mocks.mocks.dart';

void main() {
late Fixture fixture;

setUp(() {
fixture = Fixture();
});

group('RouteObserverBreadcrumb', () {
test('happy path with string route agrument', () {
const fromRouteSettings = RouteSettings(
Expand Down Expand Up @@ -140,12 +147,12 @@ void main() {
settings: settings,
);

RouteSettings routeSettings(String name, [Object? arguments]) =>
RouteSettings routeSettings(String? name, [Object? arguments]) =>
RouteSettings(name: name, arguments: arguments);

test('Test recording of Breadcrumbs', () {
final hub = MockHub();
final observer = SentryNavigatorObserver(hub: hub);
final observer = fixture.getSut(hub: hub);

final to = routeSettings('to', 'foobar');
final previous = routeSettings('previous', 'foobar');
Expand All @@ -166,7 +173,7 @@ void main() {

test('No arguments', () {
final hub = MockHub();
final observer = SentryNavigatorObserver(hub: hub);
final observer = fixture.getSut(hub: hub);

final to = routeSettings('to');
final previous = routeSettings('previous');
Expand All @@ -187,7 +194,7 @@ void main() {

test('No arguments & no name', () {
final hub = MockHub();
final observer = SentryNavigatorObserver(hub: hub);
final observer = fixture.getSut(hub: hub);

final to = route(null);
final previous = route(null);
Expand All @@ -210,7 +217,7 @@ void main() {
);

final hub = MockHub();
final observer = SentryNavigatorObserver(hub: hub);
final observer = fixture.getSut(hub: hub);

final to = route();
final previous = route();
Expand All @@ -226,5 +233,79 @@ void main() {
).data,
);
});

test('route name as transaction', () {
final hub = _MockHub();
final observer = fixture.getSut(
hub: hub,
setRouteNameAsTransaction: true,
);

final to = routeSettings('to');
final previous = routeSettings('previous');

observer.didPush(route(to), route(previous));
expect(hub.scope.transaction, 'to');

observer.didPop(route(to), route(previous));
expect(hub.scope.transaction, 'previous');

observer.didReplace(newRoute: route(to), oldRoute: route(previous));
expect(hub.scope.transaction, 'to');
});

test('route name does nothing if null', () {
final hub = _MockHub();
final observer = fixture.getSut(
hub: hub,
setRouteNameAsTransaction: true,
);

hub.scope.transaction = 'foo bar';

final to = routeSettings(null);
final previous = routeSettings(null);

observer.didPush(route(to), route(previous));
expect(hub.scope.transaction, 'foo bar');
});

test('disabled route as transaction', () {
final hub = _MockHub();
final observer =
fixture.getSut(hub: hub, setRouteNameAsTransaction: false);

final to = routeSettings('to');
final previous = routeSettings('previous');

observer.didPush(route(to), route(previous));
expect(hub.scope.transaction, null);

observer.didPop(route(to), route(previous));
expect(hub.scope.transaction, null);

observer.didReplace(newRoute: route(to), oldRoute: route(previous));
expect(hub.scope.transaction, null);
});
});
}

class Fixture {
SentryNavigatorObserver getSut({
required Hub hub,
bool setRouteNameAsTransaction = false,
}) {
return SentryNavigatorObserver(
hub: hub,
setRouteNameAsTransaction: setRouteNameAsTransaction,
);
}
}

class _MockHub extends MockHub {
final Scope scope = Scope(SentryOptions(dsn: fakeDsn));
@override
void configureScope(ScopeCallback? callback) {
callback?.call(scope);
}
}