Thanks to visit codestin.com
Credit goes to github.com

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
30 changes: 21 additions & 9 deletions packages/devtools_app/lib/src/framework/app_error_handling.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'dart:async';
import 'dart:convert';

import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart';
Expand All @@ -14,6 +15,7 @@ import 'package:source_maps/source_maps.dart';
import 'package:stack_trace/stack_trace.dart' as stack_trace;

import '../shared/analytics/analytics.dart' as ga;
import '../shared/analytics/analytics_common.dart';
import '../shared/globals.dart';

final _log = Logger('app_error_handling');
Expand Down Expand Up @@ -93,17 +95,27 @@ Future<void> _reportError(
bool notifyUser = false,
StackTrace? stack,
}) async {
final terseStackTrace = await _mapAndTersify(stack);
final errorMessage = '$error\n$terseStackTrace';

_log.severe('[$errorType]: $errorMessage', error, stack);
ga.reportError(errorMessage);
final stackTrace = await _mapAndTersify(stack);
final terseStackTrace = stackTrace?.terse;
final errorMessageWithTerseStackTrace = '$error\n${terseStackTrace ?? ''}';
_log.severe('[$errorType]: $errorMessageWithTerseStackTrace', error, stack);

// Split the stack trace up into substrings of size
// [ga4ParamValueCharacterLimit] so that we can send the stack trace in chunks
// to GA4 through unified_analytics.
final stackTraceSubstrings = stackTrace
.toString()
.characters
.slices(ga4ParamValueCharacterLimit)
.map((slice) => slice.join())
.toList();
ga.reportError('$error', stackTraceSubstrings: stackTraceSubstrings);

// Show error message in a notification pop-up:
if (notifyUser) {
notificationService.pushError(
error.toString(),
stackTrace: terseStackTrace,
stackTrace: terseStackTrace?.toString(),
);
}
}
Expand Down Expand Up @@ -136,16 +148,16 @@ Future<SingleMapping?> _initializeSourceMapping() async {
}
}

Future<String> _mapAndTersify(StackTrace? stack) async {
Future<stack_trace.Trace?> _mapAndTersify(StackTrace? stack) async {
final originalStackTrace = stack;
if (originalStackTrace == null) return '';
if (originalStackTrace == null) return null;

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();
return stack_trace.Trace.from(stackTrace);
}

Future<StackTrace> _maybeMapStackTrace(StackTrace stack) async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ void impression(

void reportError(
String errorMessage, {
List<String> stackTraceSubstrings = const <String>[],
bool fatal = false,
}) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import 'package:web/web.dart';

import '../dtd_manager_extensions.dart';
import '../globals.dart';
import '../primitives/utils.dart';
import '../query_parameters.dart';
import '../server/server.dart' as server;
import '../utils.dart';
Expand Down Expand Up @@ -631,21 +632,32 @@ void impression(

String? _lastGaError;

/// Reports an error to analytics.
///
/// [errorMessage] is the description of the error.
/// [stackTraceSubstrings] is the stack trace broken up into substrings of
/// size [ga4ParamValueCharacterLimit] so that we can send the stack trace in
/// chunks to GA4 through unified_analytics.
void reportError(
String errorMessage, {
List<String> stackTraceSubstrings = const <String>[],
bool fatal = false,
}) {
// Don't keep recording same last error.
if (_lastGaError == errorMessage) return;
_lastGaError = errorMessage;

final gTagException = GtagExceptionDevTools._create(
errorMessage,
final gTagExceptionWithStackTrace = GtagExceptionDevTools._create(
// Include the stack trace in the message for legacy analytics.
'$errorMessage\n${stackTraceSubstrings.join()}',
fatal: fatal,
);
GTag.exception(gaExceptionProvider: () => gTagException);
GTag.exception(gaExceptionProvider: () => gTagExceptionWithStackTrace);

final uaEvent = _uaEventFromGtagException(gTagException);
final uaEvent = _uaEventFromGtagException(
GtagExceptionDevTools._create(errorMessage, fatal: fatal),
stackTraceSubstrings: stackTraceSubstrings,
);
unawaited(dtdManager.sendAnalyticsEvent(uaEvent));
}

Expand Down Expand Up @@ -877,6 +889,8 @@ void _sendEventForScreen(String screenName, GtagEventDevTools gtagEvent) {
}

ua.Event _uaEventFromGtagEvent(GtagEventDevTools gtagEvent) {
// Any dimensions or metrics that have a null value will be removed from
// the event data in the [ua.Event.devtoolsEvent] constructor.
return ua.Event.devtoolsEvent(
eventCategory: gtagEvent.event_category!,
label: gtagEvent.event_label!,
Expand All @@ -889,11 +903,15 @@ ua.Event _uaEventFromGtagEvent(GtagEventDevTools gtagEvent) {
devtoolsChrome: gtagEvent.devtools_chrome,
devtoolsVersion: gtagEvent.devtools_version,
ideLaunched: gtagEvent.ide_launched,
ideLaunchedFeature: gtagEvent.ide_launched_feature,
isExternalBuild: gtagEvent.is_external_build,
isEmbedded: gtagEvent.is_embedded,
ideLaunchedFeature: gtagEvent.ide_launched_feature,
isWasm: gtagEvent.is_wasm,
g3Username: gtagEvent.g3_username,
// Only 25 entries are permitted for GA4 event parameters, but since not
// all of the below metrics will be non-null at the same time, it is okay to
// include all the metrics here. The [ua.Event.devtoolsEvent] constructor
// will remove any entries with a null value from the sent event parameters.
uiDurationMicros: gtagEvent.ui_duration_micros,
rasterDurationMicros: gtagEvent.raster_duration_micros,
shaderCompilationDurationMicros:
Expand All @@ -912,36 +930,42 @@ ua.Event _uaEventFromGtagEvent(GtagEventDevTools gtagEvent) {
);
}

ua.Event _uaEventFromGtagException(GtagExceptionDevTools gtagException) {
ua.Event _uaEventFromGtagException(
GtagExceptionDevTools gtagException, {
List<String> stackTraceSubstrings = const <String>[],
}) {
// Any data entries that have a null value will be removed from the event data
// in the [ua.Event.exception] constructor.
return ua.Event.exception(
exception: gtagException.description ?? 'unknown exception',
data: {
'fatal': gtagException.fatal,
// Each stack trace substring of length [ga4ParamValueCharacterLimit]
// contains information for ~1 stack frame, so including 8 chunks should
// give us enough information to understand the source of the exception.
'stackTraceChunk0': stackTraceSubstrings.safeGet(0),
'stackTraceChunk1': stackTraceSubstrings.safeGet(1),
'stackTraceChunk2': stackTraceSubstrings.safeGet(2),
'stackTraceChunk3': stackTraceSubstrings.safeGet(3),
'stackTraceChunk4': stackTraceSubstrings.safeGet(4),
'stackTraceChunk5': stackTraceSubstrings.safeGet(5),
'stackTraceChunk6': stackTraceSubstrings.safeGet(6),
'stackTraceChunk7': stackTraceSubstrings.safeGet(7),
'userApp': gtagException.user_app,
'userBuild': gtagException.user_build,
'userPlatform': gtagException.user_platform,
'devtoolsPlatform': gtagException.devtools_platform,
'devtoolsChrome': gtagException.devtools_chrome,
'devtoolsVersion': gtagException.devtools_version,
'ideLaunched': gtagException.ide_launched,
'ideLaunchedFeature': gtagException.ide_launched_feature,
'isExternalBuild': gtagException.is_external_build,
'isEmbedded': gtagException.is_embedded,
'ideLaunchedFeature': gtagException.ide_launched_feature,
'isWasm': gtagException.is_wasm,
'g3Username': gtagException.g3_username,
'uiDurationMicros': gtagException.ui_duration_micros,
'rasterDurationMicros': gtagException.raster_duration_micros,
'shaderCompilationDurationMicros':
gtagException.shader_compilation_duration_micros,
'traceEventCount': gtagException.trace_event_count,
'cpuSampleCount': gtagException.cpu_sample_count,
'cpuStackDepth': gtagException.cpu_stack_depth,
'heapDiffObjectsBefore': gtagException.heap_diff_objects_before,
'heapDiffObjectsAfter': gtagException.heap_diff_objects_after,
'heapObjectsTotal': gtagException.heap_objects_total,
'rootSetCount': gtagException.root_set_count,
'rowCount': gtagException.row_count,
'inspectorTreeControllerId': gtagException.inspector_tree_controller_id,
// Do not include metrics in exceptions because GA4 event parameter are
// limited to 25 entries, and we need to reserve entries for the stack
// trace chunks.
},
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@
/// Then, add your fields to the [GtagEventDevTools] factory constructor and add
/// a corresponding getter in the class.
abstract class ScreenAnalyticsMetrics {}

/// The character limit for each event parameter value sent to GA4.
const ga4ParamValueCharacterLimit = 100;
2 changes: 1 addition & 1 deletion packages/devtools_app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ dependencies:
stack_trace: ^1.12.0
stream_channel: ^2.1.1
string_scanner: ^1.1.0
unified_analytics: ^6.1.4
unified_analytics: ^6.1.5
vm_service: ^14.2.5
vm_service_protos: ^1.0.0
vm_snapshot_analysis: ^0.7.6
Expand Down
Loading