diff --git a/flutter-candidate.txt b/flutter-candidate.txt index 0892039e5de..8cb5248387f 100644 --- a/flutter-candidate.txt +++ b/flutter-candidate.txt @@ -1 +1 @@ -b05246d305ac94798ca5429754917c0afa92fd9b +5dff2b54f93ebbc5e711b5fc68d52cd275be9b07 diff --git a/packages/devtools_app/lib/src/framework/app_error_handling.dart b/packages/devtools_app/lib/src/framework/app_error_handling.dart index 86292c53cf8..ed2ceea3832 100644 --- a/packages/devtools_app/lib/src/framework/app_error_handling.dart +++ b/packages/devtools_app/lib/src/framework/app_error_handling.dart @@ -3,10 +3,14 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; +import 'package:http/http.dart'; import 'package:logging/logging.dart'; +import 'package:source_map_stack_trace/source_map_stack_trace.dart'; +import 'package:source_maps/source_maps.dart'; import 'package:stack_trace/stack_trace.dart' as stack_trace; import '../shared/analytics/analytics.dart' as ga; @@ -26,10 +30,11 @@ void setupErrorHandling(Future Function() appStartCallback) { // First, run all our code in a new zone. unawaited( runZonedGuarded>( - // ignore: avoid-passing-async-when-sync-expected this ignore should be fixed. - () { + () async { WidgetsFlutterBinding.ensureInitialized(); + await _initializeSourceMapping(); + final FlutterExceptionHandler? oldHandler = FlutterError.onError; FlutterError.onError = (FlutterErrorDetails details) { @@ -70,13 +75,29 @@ void reportError( bool notifyUser = false, StackTrace? stack, }) { - stack = stack ?? StackTrace.empty; - - final terseStackTrace = stack_trace.Trace.from(stack).terse.toString(); + unawaited( + _reportError( + error, + errorType: errorType, + notifyUser: notifyUser, + stack: stack, + ).catchError((_) { + // Ignore errors. + }), + ); +} - _log.severe('[$errorType]: ${error.toString()}', error, stack); +Future _reportError( + Object error, { + String errorType = 'DevToolsError', + bool notifyUser = false, + StackTrace? stack, +}) async { + final terseStackTrace = await _mapAndTersify(stack); + final errorMessage = '$error\n$terseStackTrace'; - ga.reportError('$error\n$terseStackTrace'); + _log.severe('[$errorType]: $errorMessage', error, stack); + ga.reportError(errorMessage); // Show error message in a notification pop-up: if (notifyUser) { @@ -86,3 +107,53 @@ void reportError( ); } } + +SingleMapping? _cachedJsSourceMapping; +SingleMapping? _cachedWasmSourceMapping; + +Future _fetchSourceMapping() async { + final cachedSourceMapping = + kIsWasm ? _cachedWasmSourceMapping : _cachedJsSourceMapping; + + return cachedSourceMapping ?? (await _initializeSourceMapping()); +} + +Future _initializeSourceMapping() async { + try { + final sourceMapUri = Uri.parse( + 'main.dart.${kIsWasm ? 'wasm' : 'js'}.map', + ); + final sourceMapFile = await get(sourceMapUri); + + return SingleMapping.fromJson( + jsonDecode(sourceMapFile.body), + mapUrl: sourceMapUri, + ); + } catch (_) { + // Ignore any errors loading the source map. + return null; + } +} + +Future _mapAndTersify(StackTrace? stack) async { + final originalStackTrace = stack; + if (originalStackTrace == null) return ''; + + final mappedStackTrace = await _maybeMapStackTrace(originalStackTrace); + // If mapping fails, revert back to the original stack trace: + final stackTrace = mappedStackTrace.toString().isEmpty + ? originalStackTrace + : mappedStackTrace; + return stack_trace.Trace.from(stackTrace).terse.toString(); +} + +Future _maybeMapStackTrace(StackTrace stack) async { + final sourceMapping = await _fetchSourceMapping(); + return sourceMapping != null + ? mapStackTrace( + sourceMapping, + stack, + minified: true, + ) + : stack; +} diff --git a/packages/devtools_app/pubspec.yaml b/packages/devtools_app/pubspec.yaml index 07f350215aa..27645b02f27 100644 --- a/packages/devtools_app/pubspec.yaml +++ b/packages/devtools_app/pubspec.yaml @@ -47,8 +47,10 @@ dependencies: provider: ^6.0.2 # Only used for debug mode logic. shared_preferences: ^2.0.15 + source_map_stack_trace: ^2.1.2 + source_maps: ^0.10.12 sse: ^4.1.2 - stack_trace: ^1.10.0 + stack_trace: ^1.12.0 stream_channel: ^2.1.1 string_scanner: ^1.1.0 unified_analytics: ^6.1.3 diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index df3400cc6f8..bfa84f83075 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -25,6 +25,9 @@ show. - [#8277](https://github.com/flutter/devtools/pull/8277) * Added support for loading extensions in pub workspaces [8347](https://github.com/flutter/devtools/pull/8347). +* Mapped error stacktraces to use the Dart source code locations so that they are human- + readable. - [#8385](https://github.com/flutter/devtools/pull/8385) + ## Inspector updates - Added a setting to the Flutter Inspector controls that allows users to opt-in to the newly redesigned Flutter Inspector. - [#8342](https://github.com/flutter/devtools/pull/8342) diff --git a/tool/build_release.sh b/tool/build_release.sh index c88203ceef9..4c14b9b9e86 100755 --- a/tool/build_release.sh +++ b/tool/build_release.sh @@ -77,6 +77,7 @@ rm -rf build/web flutter pub get flutter build web \ + --source-maps \ --wasm \ --pwa-strategy=offline-first \ --release \ diff --git a/tool/lib/commands/build.dart b/tool/lib/commands/build.dart index 8668ee2b2d3..d0c8415d7c2 100644 --- a/tool/lib/commands/build.dart +++ b/tool/lib/commands/build.dart @@ -101,6 +101,7 @@ class BuildCommand extends Command { [ 'build', 'web', + '--source-maps', if (useWasm) ...[ BuildCommandArgs.wasm.asArg(), if (noStripWasm) BuildCommandArgs.noStripWasm.asArg(),