From 64ba7e6937cab27c180426403ddbf77a7fe8101f Mon Sep 17 00:00:00 2001 From: Kenzie Davisson Date: Mon, 4 Nov 2024 08:48:55 -0800 Subject: [PATCH] Remove logging v2 code --- packages/devtools_app/lib/devtools_app.dart | 4 - packages/devtools_app/lib/src/app.dart | 15 +- .../src/screens/logging/logging_controls.dart | 7 +- .../logging/logging_screen_v2/log_data.dart | 96 -- .../logging_controller_v2.dart | 587 ------------ .../logging_screen_v2/logging_model.dart | 845 ------------------ .../logging_screen_v2/logging_screen_v2.dart | 111 --- .../logging_screen_v2/logging_table_row.dart | 218 ----- .../logging_screen_v2/logging_table_v2.dart | 275 ------ .../src/screens/logging/shared/constants.dart | 9 - .../lib/src/shared/feature_flags.dart | 6 - .../logging_controller_v2_test.dart | 118 --- .../logging_screen_v2/logging_model_test.dart | 551 ------------ .../logging_screen_v2_test.dart | 76 -- .../logging_table_row_test.dart | 125 --- .../shared/primitives/feature_flags_test.dart | 1 - .../lib/src/helpers/wrappers.dart | 3 - .../lib/src/mocks/generated.dart | 1 - .../src/mocks/generated_mocks_factories.dart | 11 - 19 files changed, 8 insertions(+), 3051 deletions(-) delete mode 100644 packages/devtools_app/lib/src/screens/logging/logging_screen_v2/log_data.dart delete mode 100644 packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_controller_v2.dart delete mode 100644 packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_model.dart delete mode 100644 packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_screen_v2.dart delete mode 100644 packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_table_row.dart delete mode 100644 packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_table_v2.dart delete mode 100644 packages/devtools_app/lib/src/screens/logging/shared/constants.dart delete mode 100644 packages/devtools_app/test/logging/logging_screen_v2/logging_controller_v2_test.dart delete mode 100644 packages/devtools_app/test/logging/logging_screen_v2/logging_model_test.dart delete mode 100644 packages/devtools_app/test/logging/logging_screen_v2/logging_screen_v2_test.dart delete mode 100644 packages/devtools_app/test/logging/logging_screen_v2/logging_table_row_test.dart diff --git a/packages/devtools_app/lib/devtools_app.dart b/packages/devtools_app/lib/devtools_app.dart index 7a66ddf5224..9433e556e7d 100644 --- a/packages/devtools_app/lib/devtools_app.dart +++ b/packages/devtools_app/lib/devtools_app.dart @@ -29,10 +29,6 @@ export 'src/screens/inspector_shared/inspector_screen.dart'; export 'src/screens/inspector_shared/inspector_screen_controller.dart'; export 'src/screens/logging/logging_controller.dart'; export 'src/screens/logging/logging_screen.dart'; -export 'src/screens/logging/logging_screen_v2/log_data.dart'; -export 'src/screens/logging/logging_screen_v2/logging_controller_v2.dart'; -export 'src/screens/logging/logging_screen_v2/logging_model.dart'; -export 'src/screens/logging/logging_screen_v2/logging_screen_v2.dart'; export 'src/screens/memory/framework/memory_controller.dart'; export 'src/screens/memory/framework/memory_screen.dart'; export 'src/screens/network/network_controller.dart'; diff --git a/packages/devtools_app/lib/src/app.dart b/packages/devtools_app/lib/src/app.dart index 464637a413a..5cdcda109e2 100644 --- a/packages/devtools_app/lib/src/app.dart +++ b/packages/devtools_app/lib/src/app.dart @@ -31,8 +31,6 @@ import 'screens/inspector_shared/inspector_screen.dart'; import 'screens/inspector_shared/inspector_screen_controller.dart'; import 'screens/logging/logging_controller.dart'; import 'screens/logging/logging_screen.dart'; -import 'screens/logging/logging_screen_v2/logging_controller_v2.dart'; -import 'screens/logging/logging_screen_v2/logging_screen_v2.dart'; import 'screens/memory/framework/memory_controller.dart'; import 'screens/memory/framework/memory_screen.dart'; import 'screens/network/network_controller.dart'; @@ -677,15 +675,10 @@ List defaultScreens({ NetworkScreen(), createController: (_) => NetworkController(), ), - FeatureFlags.loggingV2 - ? DevToolsScreen( - LoggingScreenV2(), - createController: (_) => LoggingControllerV2(), - ) - : DevToolsScreen( - LoggingScreen(), - createController: (_) => LoggingController(), - ), + DevToolsScreen( + LoggingScreen(), + createController: (_) => LoggingController(), + ), DevToolsScreen(ProviderScreen()), DevToolsScreen( AppSizeScreen(), diff --git a/packages/devtools_app/lib/src/screens/logging/logging_controls.dart b/packages/devtools_app/lib/src/screens/logging/logging_controls.dart index 4119e564836..880dfee8a72 100644 --- a/packages/devtools_app/lib/src/screens/logging/logging_controls.dart +++ b/packages/devtools_app/lib/src/screens/logging/logging_controls.dart @@ -16,7 +16,8 @@ import '../../shared/primitives/utils.dart'; import '../../shared/ui/filter.dart'; import '../../shared/ui/search.dart'; import 'logging_controller.dart'; -import 'shared/constants.dart'; + +const _loggingMinVerboseWidth = 650.0; class LoggingControls extends StatelessWidget { const LoggingControls({super.key}); @@ -30,7 +31,7 @@ class LoggingControls extends StatelessWidget { onPressed: controller.clear, gaScreen: gac.logging, gaSelection: gac.clear, - minScreenWidthForTextBeforeScaling: loggingMinVerboseWidth, + minScreenWidthForTextBeforeScaling: _loggingMinVerboseWidth, ), const SizedBox(width: denseSpacing), Expanded( @@ -39,7 +40,7 @@ class LoggingControls extends StatelessWidget { valueListenable: controller.filteredData, builder: (context, _, _) => SearchField( searchFieldWidth: - isScreenWiderThan(context, loggingMinVerboseWidth) + isScreenWiderThan(context, _loggingMinVerboseWidth) ? wideSearchFieldWidth : defaultSearchFieldWidth, searchController: controller, diff --git a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/log_data.dart b/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/log_data.dart deleted file mode 100644 index 5b07610cd64..00000000000 --- a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/log_data.dart +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2024 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:convert'; - -import 'package:flutter/foundation.dart'; - -import '../../../shared/diagnostics/diagnostics_node.dart'; -import '../../../shared/primitives/utils.dart'; -import '../../../shared/ui/search.dart'; - -/// A log data object that includes optional summary information about whether -/// the log entry represents an error entry, the log entry kind, and more -/// detailed data for the entry. -/// -/// The details can optionally be loaded lazily on first use. If this is the -/// case, this log entry will have a non-null `detailsComputer` field. After the -/// data is calculated, the log entry will be modified to contain the calculated -/// `details` data. -class LogDataV2 with SearchableDataMixin { - LogDataV2( - this.kind, - this._details, - this.timestamp, { - this.summary, - this.isError = false, - this.detailsComputer, - this.node, - }) { - // Fetch details immediately on creation. - unawaited(compute()); - } - - final String kind; - final int? timestamp; - final bool isError; - final String? summary; - - final RemoteDiagnosticsNode? node; - String? _details; - Future Function()? detailsComputer; - - static const prettyPrinter = JsonEncoder.withIndent(' '); - - String? get details => _details; - - ValueListenable get detailsComputed => _detailsComputed; - final _detailsComputed = ValueNotifier(false); - - Future compute() async { - if (!detailsComputed.value) { - if (detailsComputer != null) { - _details = await detailsComputer!(); - } - detailsComputer = null; - _detailsComputed.value = true; - } - } - - /// The current calculated display height. - double? height; - - /// The current offset of this log entry in the logs table. - double? offset; - - String? prettyPrinted() { - if (!detailsComputed.value) { - return details?.trim(); - } - - try { - return prettyPrinter - .convert(jsonDecode(details!)) - .replaceAll(r'\n', '\n') - .trim(); - } catch (_) { - return details?.trim(); - } - } - - String asLogDetails() { - return !detailsComputed.value ? '' : prettyPrinted() ?? ''; - } - - @override - bool matchesSearchToken(RegExp regExpSearch) { - return kind.caseInsensitiveContains(regExpSearch) || - (summary?.caseInsensitiveContains(regExpSearch) == true) || - (details?.caseInsensitiveContains(regExpSearch) == true); - } - - @override - String toString() => 'LogData($kind, $timestamp)'; -} diff --git a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_controller_v2.dart b/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_controller_v2.dart deleted file mode 100644 index 7067b5d1848..00000000000 --- a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_controller_v2.dart +++ /dev/null @@ -1,587 +0,0 @@ -// Copyright 2024 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:convert'; - -import 'package:devtools_app_shared/service.dart'; -import 'package:devtools_app_shared/utils.dart'; -import 'package:devtools_shared/devtools_shared.dart'; -import 'package:flutter/foundation.dart'; -import 'package:logging/logging.dart'; -import 'package:vm_service/vm_service.dart'; - -import '../../../service/vm_service_wrapper.dart'; -import '../../../shared/diagnostics/diagnostics_node.dart'; -import '../../../shared/diagnostics/inspector_service.dart'; -import '../../../shared/globals.dart'; -import '../../../shared/primitives/byte_utils.dart'; -import '../../../shared/primitives/message_bus.dart'; -import '../../../shared/primitives/utils.dart'; -import '../logging_controller.dart' - show - FrameInfo, - ImageSizesForFrame, - NavigationInfo, - ServiceExtensionStateChangedInfo; -import '../logging_screen.dart'; -import 'log_data.dart'; -import 'logging_model.dart'; - -final _log = Logger('logging_controller'); - -// For performance reasons, we drop old logs in batches, so the log will grow -// to kMaxLogItemsUpperBound then truncate to kMaxLogItemsLowerBound. - -bool _verboseDebugging = false; - -Future _retrieveFullStringValue( - VmServiceWrapper? service, - IsolateRef isolateRef, - InstanceRef stringRef, -) { - final fallback = '${stringRef.valueAsString}...'; - // TODO(kenz): why is service null? - - return service - ?.retrieveFullStringValue( - isolateRef.id!, - stringRef, - onUnavailable: (truncatedValue) => fallback, - ) - .then((value) => value ?? fallback) ?? - Future.value(fallback); -} - -/// Logs kinds to show without a summary in the table. -final _hideSummaryLogKinds = { - FlutterEvent.firstFrame, - FlutterEvent.frameworkInitialization, -}; - -class LoggingControllerV2 extends DisposableController - with AutoDisposeControllerMixin { - LoggingControllerV2() { - addAutoDisposeListener(serviceConnection.serviceManager.connectedState, () { - if (serviceConnection.serviceManager.connectedState.value.connected) { - _handleConnectionStart(serviceConnection.serviceManager.service!); - - autoDisposeStreamSubscription( - serviceConnection.serviceManager.service!.onIsolateEvent - .listen((event) { - messageBus.addEvent( - BusEvent( - 'debugger', - data: event, - ), - ); - }), - ); - } - }); - if (serviceConnection.serviceManager.connectedAppInitialized) { - _handleConnectionStart(serviceConnection.serviceManager.service!); - } - _handleBusEvents(); - } - - static const kindFilterId = 'logging-kind-filter'; - - final _logStatusController = StreamController.broadcast(); - - /// A stream of events for the textual description of the log contents. - /// - /// See also [statusText]. - Stream get onLogStatusChanged => _logStatusController.stream; - - final selectedLog = ValueNotifier(null); - final loggingModel = LoggingTableModel(); - - void _updateSelection() { - final selected = selectedLog.value; - if (selected != null) { - final logs = loggingModel.filteredData.value; - if (!logs.contains(selected)) { - selectedLog.value = null; - } - } - } - - ObjectGroup get objectGroup => - serviceConnection.consoleService.objectGroup as ObjectGroup; - - String get statusText { - final totalCount = loggingModel.logCount; - final showingCount = loggingModel.filteredData.value.length; - - String label; - - label = totalCount == showingCount - ? nf.format(totalCount) - : 'showing ${nf.format(showingCount)} of ' '${nf.format(totalCount)}'; - - label = '$label ${pluralize('event', totalCount)}'; - - return label; - } - - void _updateStatus() { - final label = statusText; - _logStatusController.add(label); - } - - void clear() { - loggingModel.clear(); - _updateSelection(); - _updateStatus(); - serviceConnection.errorBadgeManager.clearErrors(LoggingScreen.id); - } - - void _handleConnectionStart(VmServiceWrapper service) { - // Log stdout events. - final stdoutHandler = _StdoutEventHandler(this, 'stdout'); - autoDisposeStreamSubscription( - service.onStdoutEventWithHistorySafe.listen(stdoutHandler.handle), - ); - - // Log stderr events. - final stderrHandler = _StdoutEventHandler(this, 'stderr', isError: true); - autoDisposeStreamSubscription( - service.onStderrEventWithHistorySafe.listen(stderrHandler.handle), - ); - - // Log GC events. - autoDisposeStreamSubscription(service.onGCEvent.listen(_handleGCEvent)); - - // Log `dart:developer` `log` events. - autoDisposeStreamSubscription( - service.onLoggingEventWithHistorySafe.listen(_handleDeveloperLogEvent), - ); - - // Log Flutter extension events. - autoDisposeStreamSubscription( - service.onExtensionEventWithHistorySafe.listen(_handleExtensionEvent), - ); - } - - void _handleExtensionEvent(Event e) { - if (e.extensionKind == FlutterEvent.frame) { - final frame = FrameInfo(e.extensionData!.data); - - final frameId = '#${frame.number}'; - final frameInfoText = - '$frameId ${frame.elapsedMs.toStringAsFixed(1).padLeft(4)}ms '; - - log( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(e.extensionData!.data), - e.timestamp, - summary: frameInfoText, - ), - ); - } else if (e.extensionKind == FlutterEvent.imageSizesForFrame) { - final images = ImageSizesForFrame.from(e.extensionData!.data); - for (final image in images) { - log( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(image.json), - e.timestamp, - summary: image.summary, - ), - ); - } - } else if (e.extensionKind == FlutterEvent.navigation) { - final navInfo = NavigationInfo.from(e.extensionData!.data); - - log( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(e.json), - e.timestamp, - summary: navInfo.routeDescription, - ), - ); - } else if (_hideSummaryLogKinds.contains(e.extensionKind)) { - log( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(e.json), - e.timestamp, - summary: '', - ), - ); - } else if (e.extensionKind == FlutterEvent.serviceExtensionStateChanged) { - final changedInfo = - ServiceExtensionStateChangedInfo.from(e.extensionData!.data); - - log( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(e.json), - e.timestamp, - summary: '${changedInfo.extension}: ${changedInfo.value}', - ), - ); - } else if (e.extensionKind == FlutterEvent.error) { - // TODO(pq): add tests for error extension handling once framework changes - // are landed. - final node = RemoteDiagnosticsNode( - e.extensionData!.data, - objectGroup, - false, - null, - ); - // Workaround the fact that the error objects from the server don't have - // style error. - node.style = DiagnosticsTreeStyle.error; - if (_verboseDebugging) { - _log.info('node toStringDeep:######\n${node.toStringDeep()}\n###'); - } - - final summary = _findFirstSummary(node) ?? node; - log( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(e.extensionData!.data), - e.timestamp, - summary: summary.toDiagnosticsNode().toString(), - ), - ); - } else { - log( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(e.json), - e.timestamp, - summary: e.json.toString(), - ), - ); - } - } - - void _handleGCEvent(Event e) { - final newSpace = HeapSpace.parse(e.json!['new'])!; - final oldSpace = HeapSpace.parse(e.json!['old'])!; - final isolateRef = (e.json!['isolate'] as Map).cast(); - - final usedBytes = newSpace.used! + oldSpace.used!; - final capacityBytes = newSpace.capacity! + oldSpace.capacity!; - - final time = ((newSpace.time! + oldSpace.time!) * 1000).round(); - - final summary = '${isolateRef['name']} • ' - '${e.json!['reason']} collection in $time ms • ' - '${printBytes(usedBytes, unit: ByteUnit.mb, includeUnit: true)} used of ' - '${printBytes(capacityBytes, unit: ByteUnit.mb, includeUnit: true)}'; - - final event = { - 'reason': e.json!['reason'], - 'new': newSpace.json, - 'old': oldSpace.json, - 'isolate': isolateRef, - }; - - final message = jsonEncode(event); - log(LogDataV2('gc', message, e.timestamp, summary: summary)); - } - - void _handleDeveloperLogEvent(Event e) { - final service = serviceConnection.serviceManager.service; - - final logRecord = _LogRecord(e.json!['logRecord']); - - String? loggerName = - _valueAsString(InstanceRef.parse(logRecord.loggerName)); - if (loggerName == null || loggerName.isEmpty) { - loggerName = 'log'; - } - final level = logRecord.level; - final messageRef = InstanceRef.parse(logRecord.message)!; - String? summary = _valueAsString(messageRef); - if (messageRef.valueAsStringIsTruncated == true) { - summary = '${summary!}...'; - } - final error = InstanceRef.parse(logRecord.error); - final stackTrace = InstanceRef.parse(logRecord.stackTrace); - - final details = summary; - Future Function()? detailsComputer; - - // If the message string was truncated by the VM, or the error object or - // stackTrace objects were non-null, we need to ask the VM for more - // information in order to render the log entry. We do this asynchronously - // on-demand using the `detailsComputer` Future. - if (messageRef.valueAsStringIsTruncated == true || - _isNotNull(error) || - _isNotNull(stackTrace)) { - detailsComputer = () async { - // Get the full string value of the message. - String result = - await _retrieveFullStringValue(service, e.isolate!, messageRef); - - // Get information about the error object. Some users of the - // dart:developer log call may pass a data payload in the `error` - // field, encoded as a json encoded string, so handle that case. - if (_isNotNull(error)) { - if (error!.valueAsString != null) { - final errorString = - await _retrieveFullStringValue(service, e.isolate!, error); - result += '\n\n$errorString'; - } else { - // Call `toString()` on the error object and display that. - final toStringResult = await service!.invoke( - e.isolate!.id!, - error.id!, - 'toString', - [], - disableBreakpoints: true, - ); - - if (toStringResult is ErrorRef) { - final errorString = _valueAsString(error); - result += '\n\n$errorString'; - } else if (toStringResult is InstanceRef) { - final str = await _retrieveFullStringValue( - service, - e.isolate!, - toStringResult, - ); - result += '\n\n$str'; - } - } - } - - // Get info about the stackTrace object. - if (_isNotNull(stackTrace)) { - result += '\n\n${_valueAsString(stackTrace)}'; - } - - return result; - }; - } - - const severeIssue = 1000; - final isError = level != null && level >= severeIssue ? true : false; - - log( - LogDataV2( - loggerName, - details, - e.timestamp, - isError: isError, - summary: summary, - detailsComputer: detailsComputer, - ), - ); - } - - void log(LogDataV2 log) { - loggingModel.add(log); - } - - static RemoteDiagnosticsNode? _findFirstSummary(RemoteDiagnosticsNode node) { - if (node.level == DiagnosticLevel.summary) { - return node; - } - RemoteDiagnosticsNode? summary; - for (final property in node.inlineProperties) { - summary = _findFirstSummary(property); - if (summary != null) return summary; - } - - for (final child in node.childrenNow) { - summary = _findFirstSummary(child); - if (summary != null) return summary; - } - - return null; - } - - void _handleBusEvents() { - // TODO(jacobr): expose the messageBus for use by vm tests. - autoDisposeStreamSubscription( - messageBus.onEvent(type: 'reload.end').listen((BusEvent event) { - log( - LogDataV2( - 'hot.reload', - event.data as String?, - DateTime.now().millisecondsSinceEpoch, - ), - ); - }), - ); - - autoDisposeStreamSubscription( - messageBus.onEvent(type: 'restart.end').listen((BusEvent event) { - log( - LogDataV2( - 'hot.restart', - event.data as String?, - DateTime.now().millisecondsSinceEpoch, - ), - ); - }), - ); - - // Listen for debugger events. - autoDisposeStreamSubscription( - messageBus - .onEvent() - .where( - (event) => - event.type == 'debugger' || event.type.startsWith('debugger.'), - ) - .listen(_handleDebuggerEvent), - ); - - // Listen for DevTools internal events. - autoDisposeStreamSubscription( - messageBus - .onEvent() - .where((event) => event.type.startsWith('devtools.')) - .listen(_handleDevToolsEvent), - ); - } - - void _handleDebuggerEvent(BusEvent event) { - final debuggerEvent = event.data as Event; - - // Filter ServiceExtensionAdded events as they're pretty noisy. - if (debuggerEvent.kind == EventKind.kServiceExtensionAdded) { - return; - } - - log( - LogDataV2( - event.type, - jsonEncode(debuggerEvent.json), - debuggerEvent.timestamp, - summary: '${debuggerEvent.kind} ${debuggerEvent.isolate!.id}', - ), - ); - } - - void _handleDevToolsEvent(BusEvent event) { - var details = event.data.toString(); - String? summary; - - if (details.contains('\n')) { - final lines = details.split('\n'); - summary = lines.first; - details = lines.sublist(1).join('\n'); - } - - log( - LogDataV2( - event.type, - details, - DateTime.now().millisecondsSinceEpoch, - summary: summary, - ), - ); - } -} - -extension type _LogRecord(Map json) { - int? get level => json['level']; - - Map get loggerName => json['loggerName']; - - Map get message => json['message']; - - Map get error => json['error']; - - Map get stackTrace => json['stackTrace']; -} - -/// Receive and log stdout / stderr events from the VM. -/// -/// This class buffers the events for up to 1ms. This is in order to combine a -/// stdout message and its newline. Currently, `foo\n` is sent as two VM events; -/// we wait for up to 1ms when we get the `foo` event, to see if the next event -/// is a single newline. If so, we add the newline to the previous log message. -class _StdoutEventHandler { - _StdoutEventHandler( - this.loggingController, - this.name, { - this.isError = false, - }); - - final LoggingControllerV2 loggingController; - final String name; - final bool isError; - - LogDataV2? buffer; - Timer? timer; - - void handle(Event e) { - final message = decodeBase64(e.bytes!); - - if (buffer != null) { - timer?.cancel(); - - if (message == '\n') { - loggingController.log( - LogDataV2( - buffer!.kind, - buffer!.details! + message, - buffer!.timestamp, - summary: buffer!.summary! + message, - isError: buffer!.isError, - ), - ); - buffer = null; - return; - } - - loggingController.log(buffer!); - buffer = null; - } - - const maxLength = 200; - - String summary = message; - if (message.length > maxLength) { - summary = message.substring(0, maxLength); - } - - final data = LogDataV2( - name, - message, - e.timestamp, - summary: summary, - isError: isError, - ); - - if (message == '\n') { - loggingController.log(data); - } else { - buffer = data; - timer = Timer(const Duration(milliseconds: 1), () { - loggingController.log(buffer!); - buffer = null; - }); - } - } -} - -bool _isNotNull(InstanceRef? serviceRef) { - return serviceRef != null && serviceRef.kind != 'Null'; -} - -String? _valueAsString(InstanceRef? ref) { - if (ref == null) { - return null; - } - - if (ref.valueAsString == null) { - return ref.valueAsString; - } - - return ref.valueAsStringIsTruncated == true - ? '${ref.valueAsString}...' - : ref.valueAsString; -} diff --git a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_model.dart b/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_model.dart deleted file mode 100644 index ae069a12ab5..00000000000 --- a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_model.dart +++ /dev/null @@ -1,845 +0,0 @@ -// Copyright 2024 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:collection'; -import 'dart:convert'; - -import 'package:devtools_app_shared/service.dart'; -import 'package:devtools_app_shared/utils.dart'; -import 'package:devtools_shared/devtools_shared.dart'; -import 'package:flutter/foundation.dart'; -import 'package:logging/logging.dart'; -import 'package:vm_service/vm_service.dart'; - -import '../../../service/vm_service_wrapper.dart'; -import '../../../shared/diagnostics/diagnostics_node.dart'; -import '../../../shared/diagnostics/inspector_service.dart'; -import '../../../shared/globals.dart'; -import '../../../shared/primitives/byte_utils.dart'; -import '../../../shared/primitives/message_bus.dart'; -import '../../../shared/primitives/utils.dart'; -import '../../../shared/ui/filter.dart'; -import '../../../shared/utils.dart'; -import '../logging_controller.dart' - show - FrameInfo, - ImageSizesForFrame, - NavigationInfo, - ServiceExtensionStateChangedInfo; -import 'log_data.dart'; -import 'logging_controller_v2.dart'; -import 'logging_table_row.dart'; -import 'logging_table_v2.dart'; - -final _log = Logger('logging_model'); - -bool _verboseDebugging = false; - -Future _retrieveFullStringValue( - VmServiceWrapper? service, - IsolateRef isolateRef, - InstanceRef stringRef, -) { - final fallback = '${stringRef.valueAsString}...'; - // TODO(kenz): why is service null? - - return service - ?.retrieveFullStringValue( - isolateRef.id!, - stringRef, - onUnavailable: (truncatedValue) => fallback, - ) - .then((value) => value ?? fallback) ?? - Future.value(fallback); -} - -const _gcLogKind = 'gc'; - -final _verboseFlutterFrameworkLogKinds = { - FlutterEvent.firstFrame, - FlutterEvent.frameworkInitialization, - FlutterEvent.frame, - FlutterEvent.imageSizesForFrame, -}; - -final _verboseFlutterServiceLogKinds = { - FlutterEvent.serviceExtensionStateChanged, -}; - -/// Log kinds to show without a summary in the table. -final _hideSummaryLogKinds = { - FlutterEvent.firstFrame, - FlutterEvent.frameworkInitialization, -}; - -/// A class for holding state and state changes relevant to [LoggingControllerV2] -/// and [LoggingTableV2]. -/// -/// The [LoggingTableV2] table uses variable height rows. This model caches the -/// relevant heights and offsets so that the row heights only need to be calculated -/// once per parent width. -class LoggingTableModel extends DisposableController - with ChangeNotifier, DisposerMixin, FilterControllerMixin { - LoggingTableModel() { - _worker = InterruptableChunkWorker( - callback: (index) => getLogHeight( - index, - ), - progressCallback: (progress) => _cacheLoadProgress.value = progress, - ); - - _retentionLimit = preferences.logging.retentionLimit.value; - - addAutoDisposeListener( - preferences.logging.retentionLimit, - _onRetentionLimitUpdate, - ); - - initFilterController(); - - _retentionLimit = preferences.logging.retentionLimit.value; - addAutoDisposeListener(serviceConnection.serviceManager.connectedState, () { - if (serviceConnection.serviceManager.connectedState.value.connected) { - _handleConnectionStart(serviceConnection.serviceManager.service!); - - autoDisposeStreamSubscription( - serviceConnection.serviceManager.service!.onIsolateEvent - .listen((event) { - messageBus.addEvent( - BusEvent( - 'debugger', - data: event, - ), - ); - }), - ); - } - }); - if (serviceConnection.serviceManager.connectedAppInitialized) { - _handleConnectionStart(serviceConnection.serviceManager.service!); - } - _handleBusEvents(); - } - - /// The toggle filters available for the Logging screen. - @override - SettingFilters createSettingFilters() => [ - if (serviceConnection.serviceManager.connectedApp?.isFlutterAppNow ?? - true) ...[ - ToggleFilter( - id: 'verbose-flutter-framework-logs', - name: 'Hide verbose Flutter framework logs (initialization, frame ' - 'times, image sizes)', - includeCallback: (log) => !_verboseFlutterFrameworkLogKinds - .any((kind) => kind.caseInsensitiveEquals(log.kind)), - defaultValue: true, - ), - ToggleFilter( - id: 'verbose-flutter-service-logs', - name: 'Hide verbose Flutter service logs (service extension state ' - 'changes)', - includeCallback: (log) => !_verboseFlutterServiceLogKinds - .any((kind) => kind.caseInsensitiveEquals(log.kind)), - defaultValue: true, - ), - ], - ToggleFilter( - id: 'gc-logs', - name: 'Hide garbage collection logs', - includeCallback: (log) => !log.kind.caseInsensitiveEquals(_gcLogKind), - defaultValue: true, - ), - ]; - - static const kindFilterId = 'logging-kind-filter'; - - @override - Map> createQueryFilterArgs() => { - kindFilterId: QueryFilterArgument( - keys: ['kind', 'k'], - exampleUsages: ['k:stderr', '-k:stdout'], - dataValueProvider: (log) => log.kind, - substringMatch: true, - ), - }; - - final _logStatusController = StreamController.broadcast(); - - /// A stream of events for the textual description of the log contents. - /// - /// See also [statusText]. - Stream get onLogStatusChanged => _logStatusController.stream; - - ObjectGroup get objectGroup => - serviceConnection.consoleService.objectGroup as ObjectGroup; - - final _logs = ListQueue(); - - /// [FilterControllerMixin] uses [ListValueNotifier] which isn't well optimized to the - /// retention limit behavior that [LoggingTableModel] uses. So we use - /// [ListQueue] here to facilitate those actions. Then instead of - /// using [FilterControllerMixin.filteredLogs] in [FilterControllerMixin.filterData], - /// we use [_filteredLogs]. After any changes are done to [_filteredLogs], [notifyListeners] - /// must be manually triggered, since the listener behaviour is accomplished by the - /// [LoggingTableModel] being a [ChangeNotifier]. - final _filteredLogs = ListQueue(); - - final _selectedLogs = ListQueue(); - late int _retentionLimit; - - late final InterruptableChunkWorker _worker; - - /// Represents the state of reloading the height caches. - /// - /// When null, then the cache is not loading. - /// When double, then the value is represents how much progress has been made. - ValueListenable get cacheLoadProgress => _cacheLoadProgress; - final _cacheLoadProgress = ValueNotifier(null); - - String get statusText { - final totalCount = _logs.length; - final showingCount = _filteredLogs.length; - - String label; - - label = totalCount == showingCount - ? nf.format(totalCount) - : 'showing ${nf.format(showingCount)} of ' '${nf.format(totalCount)}'; - - label = '$label ${pluralize('event', totalCount)}'; - - return label; - } - - void _updateStatus() { - final label = statusText; - _logStatusController.add(label); - } - - void _onRetentionLimitUpdate() { - _retentionLimit = preferences.logging.retentionLimit.value; - while (_logs.length > _retentionLimit) { - _trimOneOutOfRetentionLog(); - } - _recalculateOffsets(); - notifyListeners(); - } - - void _handleConnectionStart(VmServiceWrapper service) { - // Log stdout events. - final stdoutHandler = _StdoutEventHandler(this, 'stdout'); - autoDisposeStreamSubscription( - service.onStdoutEventWithHistorySafe.listen(stdoutHandler.handle), - ); - - // Log stderr events. - final stderrHandler = _StdoutEventHandler(this, 'stderr', isError: true); - autoDisposeStreamSubscription( - service.onStderrEventWithHistorySafe.listen(stderrHandler.handle), - ); - - // Log GC events. - autoDisposeStreamSubscription(service.onGCEvent.listen(_handleGCEvent)); - - // Log `dart:developer` `log` events. - autoDisposeStreamSubscription( - service.onLoggingEventWithHistorySafe.listen(_handleDeveloperLogEvent), - ); - - // Log Flutter extension events. - autoDisposeStreamSubscription( - service.onExtensionEventWithHistorySafe.listen(_handleExtensionEvent), - ); - } - - void _handleExtensionEvent(Event e) { - if (e.extensionKind == FlutterEvent.frame) { - final frame = FrameInfo(e.extensionData!.data); - - final frameId = '#${frame.number}'; - final frameInfoText = - '$frameId ${frame.elapsedMs.toStringAsFixed(1).padLeft(4)}ms '; - - add( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(e.extensionData!.data), - e.timestamp, - summary: frameInfoText, - ), - ); - } else if (e.extensionKind == FlutterEvent.imageSizesForFrame) { - final images = ImageSizesForFrame.from(e.extensionData!.data); - - for (final image in images) { - add( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(image.json), - e.timestamp, - summary: image.summary, - ), - ); - } - } else if (e.extensionKind == FlutterEvent.navigation) { - final navInfo = NavigationInfo.from(e.extensionData!.data); - - add( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(e.json), - e.timestamp, - summary: navInfo.routeDescription, - ), - ); - } else if (_hideSummaryLogKinds.contains(e.extensionKind)) { - add( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(e.json), - e.timestamp, - summary: '', - ), - ); - } else if (e.extensionKind == FlutterEvent.serviceExtensionStateChanged) { - final changedInfo = - ServiceExtensionStateChangedInfo.from(e.extensionData!.data); - - add( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(e.json), - e.timestamp, - summary: '${changedInfo.extension}: ${changedInfo.value}', - ), - ); - } else if (e.extensionKind == FlutterEvent.error) { - // TODO(pq): add tests for error extension handling once framework changes - // are landed. - final node = RemoteDiagnosticsNode( - e.extensionData!.data, - objectGroup, - false, - null, - ); - // Workaround the fact that the error objects from the server don't have - // style error. - node.style = DiagnosticsTreeStyle.error; - if (_verboseDebugging) { - _log.info('node toStringDeep:######\n${node.toStringDeep()}\n###'); - } - - final summary = _findFirstSummary(node) ?? node; - add( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(e.extensionData!.data), - e.timestamp, - summary: summary.toDiagnosticsNode().toString(), - ), - ); - } else { - add( - LogDataV2( - e.extensionKind!.toLowerCase(), - jsonEncode(e.json), - e.timestamp, - summary: e.json.toString(), - ), - ); - } - } - - void _handleGCEvent(Event e) { - final newSpace = HeapSpace.parse(e.json!['new'])!; - final oldSpace = HeapSpace.parse(e.json!['old'])!; - final isolateRef = (e.json!['isolate'] as Map).cast(); - - final usedBytes = newSpace.used! + oldSpace.used!; - final capacityBytes = newSpace.capacity! + oldSpace.capacity!; - - final time = ((newSpace.time! + oldSpace.time!) * 1000).round(); - - final summary = '${isolateRef['name']} • ' - '${e.json!['reason']} collection in $time ms • ' - '${printBytes(usedBytes, unit: ByteUnit.mb, includeUnit: true)} used of ' - '${printBytes(capacityBytes, unit: ByteUnit.mb, includeUnit: true)}'; - - final event = { - 'reason': e.json!['reason'], - 'new': newSpace.json, - 'old': oldSpace.json, - 'isolate': isolateRef, - }; - - final message = jsonEncode(event); - add(LogDataV2('gc', message, e.timestamp, summary: summary)); - } - - void _handleDeveloperLogEvent(Event e) { - final service = serviceConnection.serviceManager.service; - - final logRecord = _LogRecord(e.json!['logRecord']); - - String? loggerName = - _valueAsString(InstanceRef.parse(logRecord.loggerName)); - if (loggerName == null || loggerName.isEmpty) { - loggerName = 'log'; - } - final level = logRecord.level; - final messageRef = InstanceRef.parse(logRecord.message)!; - String? summary = _valueAsString(messageRef); - if (messageRef.valueAsStringIsTruncated == true) { - summary = '${summary!}...'; - } - final error = InstanceRef.parse(logRecord.error); - final stackTrace = InstanceRef.parse(logRecord.stackTrace); - - final details = summary; - Future Function()? detailsComputer; - - // If the message string was truncated by the VM, or the error object or - // stackTrace objects were non-null, we need to ask the VM for more - // information in order to render the log entry. We do this asynchronously - // on-demand using the `detailsComputer` Future. - if (messageRef.valueAsStringIsTruncated == true || - _isNotNull(error) || - _isNotNull(stackTrace)) { - detailsComputer = () async { - // Get the full string value of the message. - String result = - await _retrieveFullStringValue(service, e.isolate!, messageRef); - - // Get information about the error object. Some users of the - // dart:developer log call may pass a data payload in the `error` - // field, encoded as a json encoded string, so handle that case. - if (_isNotNull(error)) { - if (error!.valueAsString != null) { - final errorString = - await _retrieveFullStringValue(service, e.isolate!, error); - result += '\n\n$errorString'; - } else { - // Call `toString()` on the error object and display that. - final toStringResult = await service!.invoke( - e.isolate!.id!, - error.id!, - 'toString', - [], - disableBreakpoints: true, - ); - - if (toStringResult is ErrorRef) { - final errorString = _valueAsString(error); - result += '\n\n$errorString'; - } else if (toStringResult is InstanceRef) { - final str = await _retrieveFullStringValue( - service, - e.isolate!, - toStringResult, - ); - result += '\n\n$str'; - } - } - } - - // Get info about the stackTrace object. - if (_isNotNull(stackTrace)) { - result += '\n\n${_valueAsString(stackTrace)}'; - } - - return result; - }; - } - - const severeIssue = 1000; - final isError = level != null && level >= severeIssue ? true : false; - - add( - LogDataV2( - loggerName, - details, - e.timestamp, - isError: isError, - summary: summary, - detailsComputer: detailsComputer, - ), - ); - } - - void add(LogDataV2 log) { - _logs.add(log); - getLogHeight(_logs.length - 1); - _trimOneOutOfRetentionLog(); - - if (!_filterCallback(log)) { - // Only add the log to filtered logs if it matches the filter. - return; - } - _filteredLogs.add(log); - - // TODO(danchevalier): Calculate the new offset here - - _updateStatus(); - notifyListeners(); - } - - static RemoteDiagnosticsNode? _findFirstSummary(RemoteDiagnosticsNode node) { - if (node.level == DiagnosticLevel.summary) { - return node; - } - RemoteDiagnosticsNode? summary; - for (final property in node.inlineProperties) { - summary = _findFirstSummary(property); - if (summary != null) return summary; - } - - for (final child in node.childrenNow) { - summary = _findFirstSummary(child); - if (summary != null) return summary; - } - - return null; - } - - void _handleBusEvents() { - // TODO(jacobr): expose the messageBus for use by vm tests. - autoDisposeStreamSubscription( - messageBus.onEvent(type: 'reload.end').listen((BusEvent event) { - add( - LogDataV2( - 'hot.reload', - event.data as String?, - DateTime.now().millisecondsSinceEpoch, - ), - ); - }), - ); - - autoDisposeStreamSubscription( - messageBus.onEvent(type: 'restart.end').listen((BusEvent event) { - add( - LogDataV2( - 'hot.restart', - event.data as String?, - DateTime.now().millisecondsSinceEpoch, - ), - ); - }), - ); - - // Listen for debugger events. - autoDisposeStreamSubscription( - messageBus - .onEvent() - .where( - (event) => - event.type == 'debugger' || event.type.startsWith('debugger.'), - ) - .listen(_handleDebuggerEvent), - ); - - // Listen for DevTools internal events. - autoDisposeStreamSubscription( - messageBus - .onEvent() - .where((event) => event.type.startsWith('devtools.')) - .listen(_handleDevToolsEvent), - ); - } - - void _handleDebuggerEvent(BusEvent event) { - final debuggerEvent = event.data as Event; - - // Filter ServiceExtensionAdded events as they're pretty noisy. - if (debuggerEvent.kind == EventKind.kServiceExtensionAdded) { - return; - } - - add( - LogDataV2( - event.type, - jsonEncode(debuggerEvent.json), - debuggerEvent.timestamp, - summary: '${debuggerEvent.kind} ${debuggerEvent.isolate!.id}', - ), - ); - } - - void _handleDevToolsEvent(BusEvent event) { - var details = event.data.toString(); - String? summary; - - if (details.contains('\n')) { - final lines = details.split('\n'); - summary = lines.first; - details = lines.sublist(1).join('\n'); - } - - add( - LogDataV2( - event.type, - details, - DateTime.now().millisecondsSinceEpoch, - summary: summary, - ), - ); - } - - void _recalculateOffsets() { - double runningOffset = 0.0; - for (var i = 0; i < _filteredLogs.length; i++) { - _filteredLogs.elementAt(i).offset = runningOffset; - runningOffset += getFilteredLogHeight(i); - } - } - - @override - void dispose() { - _cacheLoadProgress.dispose(); - _worker.dispose(); - super.dispose(); - } - - @override - void filterData(Filter filter) { - super.filterData(filter); - - _filteredLogs - ..clear() - ..addAll(_logs.where(_filterCallback).toList()); - - _recalculateOffsets(); - - _updateStatus(); - - notifyListeners(); - } - - bool _filterCallback(LogDataV2 log) { - final filter = activeFilter.value; - - final filteredOutBySettingFilters = filter.settingFilters.any( - (settingFilter) => !settingFilter.includeData(log), - ); - if (filteredOutBySettingFilters) return false; - - final queryFilter = filter.queryFilter; - if (!queryFilter.isEmpty) { - final filteredOutByQueryFilterArgument = queryFilter - .filterArguments.values - .any((argument) => !argument.matchesValue(log)); - if (filteredOutByQueryFilterArgument) return false; - - if (filter.queryFilter.substringExpressions.isNotEmpty) { - for (final substring in filter.queryFilter.substringExpressions) { - final matchesKind = log.kind.caseInsensitiveContains(substring); - if (matchesKind) return true; - - final matchesSummary = log.summary != null && - log.summary!.caseInsensitiveContains(substring); - if (matchesSummary) return true; - - final matchesDetails = log.details != null && - log.details!.caseInsensitiveContains(substring); - if (matchesDetails) return true; - } - return false; - } - } - - return true; - } - - double get tableWidth => _tableWidth; - - /// Update the width of the table. - /// - /// If different from the last width, this will flush all of the calculated heights, and recalculate their heights - /// in the background. - set tableWidth(double width) { - if (width != _tableWidth) { - _tableWidth = width; - for (final log in _logs) { - log.height = null; - } - for (final log in _filteredLogs) { - log.offset = null; - } - unawaited(_preFetchRowHeights()); - } - } - - /// Get the filtered log at [index]. - LogDataV2 filteredLogAt(int index) => _filteredLogs.elementAt(index); - - double _tableWidth = 0.0; - - /// The total number of logs being held by the [LoggingTableModel]. - int get logCount => _logs.length; - - /// The number of filtered logs. - int get filteredLogCount => _filteredLogs.length; - - /// The number of selected logs. - int get selectedLogCount => _selectedLogs.length; - - /// Add a log to the list of tracked logs. - - void _trimOneOutOfRetentionLog() { - if (_logs.length > _retentionLimit) { - if (identical(_logs.first, _filteredLogs.first)) { - // Remove a filtered log if it is about to go out of retention. - _filteredLogs.removeFirst(); - } - - // Remove the log that has just gone out of retention. - _logs.removeFirst(); - } - } - - /// Clears all of the logs from the model. - void clear() { - _logs.clear(); - _filteredLogs.clear(); - notifyListeners(); - } - - /// Get the offset of a filtered log, at [index], from the top of the list of filtered logs. - double filteredLogOffsetAt(int _) { - throw Exception('Implement this when needed'); - } - - double getLogHeight(int index) { - final log = _logs.elementAt(index); - final cachedHeight = log.height; - if (cachedHeight != null) return cachedHeight; - return log.height ??= LoggingTableRow.estimateRowHeight( - log, - _tableWidth, - ); - } - - /// Get the height of a filtered Log at [index]. - double getFilteredLogHeight(int index) { - final filteredLog = _filteredLogs.elementAt(index); - final cachedHeight = filteredLog.height; - if (cachedHeight != null) return cachedHeight; - - return filteredLog.height ??= LoggingTableRow.estimateRowHeight( - filteredLog, - _tableWidth, - ); - } - - Future _preFetchRowHeights() async { - final didComplete = await _worker.doWork(_logs.length); - if (didComplete) { - _cacheLoadProgress.value = null; - } - _recalculateOffsets(); - return didComplete; - } -} - -extension type _LogRecord(Map json) { - int? get level => json['level']; - - Map get loggerName => json['loggerName']; - - Map get message => json['message']; - - Map get error => json['error']; - - Map get stackTrace => json['stackTrace']; -} - -/// Receive and log stdout / stderr events from the VM. -/// -/// This class buffers the events for up to 1ms. This is in order to combine a -/// stdout message and its newline. Currently, `foo\n` is sent as two VM events; -/// we wait for up to 1ms when we get the `foo` event, to see if the next event -/// is a single newline. If so, we add the newline to the previous log message. -class _StdoutEventHandler { - _StdoutEventHandler( - this.loggingModel, - this.name, { - this.isError = false, - }); - - final LoggingTableModel loggingModel; - final String name; - final bool isError; - - LogDataV2? buffer; - Timer? timer; - - void handle(Event e) { - final message = decodeBase64(e.bytes!); - - if (buffer != null) { - timer?.cancel(); - - if (message == '\n') { - loggingModel.add( - LogDataV2( - buffer!.kind, - buffer!.details! + message, - buffer!.timestamp, - summary: buffer!.summary! + message, - isError: buffer!.isError, - ), - ); - buffer = null; - return; - } - - loggingModel.add(buffer!); - buffer = null; - } - - const maxLength = 200; - - String summary = message; - if (message.length > maxLength) { - summary = message.substring(0, maxLength); - } - - final data = LogDataV2( - name, - message, - e.timestamp, - summary: summary, - isError: isError, - ); - - if (message == '\n') { - loggingModel.add(data); - } else { - buffer = data; - timer = Timer(const Duration(milliseconds: 1), () { - loggingModel.add(buffer!); - buffer = null; - }); - } - } -} - -bool _isNotNull(InstanceRef? serviceRef) { - return serviceRef != null && serviceRef.kind != 'Null'; -} - -String? _valueAsString(InstanceRef? ref) { - if (ref == null) { - return null; - } - - if (ref.valueAsString == null) { - return ref.valueAsString; - } - - return ref.valueAsStringIsTruncated == true - ? '${ref.valueAsString}...' - : ref.valueAsString; -} diff --git a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_screen_v2.dart b/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_screen_v2.dart deleted file mode 100644 index 38d87fcbae6..00000000000 --- a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_screen_v2.dart +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2024 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:devtools_app_shared/ui.dart'; -import 'package:devtools_app_shared/utils.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import '../../../service/service_extension_widgets.dart'; -import '../../../shared/analytics/analytics.dart' as ga; -import '../../../shared/analytics/constants.dart' as gac; -import '../../../shared/screen.dart'; -import '../../../shared/utils.dart'; -import 'log_data.dart'; -import 'logging_controller_v2.dart'; -import 'logging_table_v2.dart'; - -/// Presents logs from the connected app. -class LoggingScreenV2 extends Screen { - LoggingScreenV2() : super.fromMetaData(ScreenMetaData.logging); - - static final id = ScreenMetaData.logging.id; - - @override - String get docPageId => screenId; - - @override - Widget buildScreenBody(BuildContext context) => const LoggingScreenBodyV2(); - - @override - Widget buildStatus(BuildContext context) { - final controller = Provider.of(context); - - return StreamBuilder( - initialData: controller.statusText, - stream: controller.onLogStatusChanged, - builder: (BuildContext context, AsyncSnapshot snapshot) { - return Text(snapshot.data ?? ''); - }, - ); - } -} - -class LoggingScreenBodyV2 extends StatefulWidget { - const LoggingScreenBodyV2({super.key}); - - @override - State createState() => _LoggingScreenBodyV2State(); -} - -class _LoggingScreenBodyV2State extends State - with - AutoDisposeMixin, - ProvidedControllerMixin { - List items = []; - late List filteredLogs; - @override - void initState() { - super.initState(); - ga.screen(gac.logging); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - if (!initController()) return; - - cancelListeners(); - - filteredLogs = controller.loggingModel.filteredData.value; - addAutoDisposeListener(controller.loggingModel.filteredData, () { - setState(() { - filteredLogs = controller.loggingModel.filteredData.value; - }); - }); - } - - @override - Widget build(BuildContext context) { - return LoggingTableV2( - model: controller.loggingModel, - ); - } -} - -class LoggingSettingsDialogV2 extends StatelessWidget { - const LoggingSettingsDialogV2({super.key}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return DevToolsDialog( - title: const DialogTitleText('Logging Settings'), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...dialogSubHeader( - theme, - 'General', - ), - const StructuredErrorsToggle(), - ], - ), - actions: const [ - DialogCloseButton(), - ], - ); - } -} diff --git a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_table_row.dart b/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_table_row.dart deleted file mode 100644 index 6b700656c50..00000000000 --- a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_table_row.dart +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2024 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:convert'; -import 'dart:math'; - -import 'package:devtools_app_shared/ui.dart'; -import 'package:flutter/material.dart'; - -import '../../../shared/globals.dart'; -import '../../../shared/primitives/utils.dart'; -import '../../../shared/ui/utils.dart'; -import '../metadata.dart'; -import '../shared/constants.dart'; -import 'log_data.dart'; - -class LoggingTableRow extends StatefulWidget { - const LoggingTableRow({ - super.key, - required this.index, - required this.data, - required this.isSelected, - }); - - final int index; - - final LogDataV2 data; - - final bool isSelected; - - static TextStyle get metadataStyle { - final currentContext = navigatorKey.currentContext; - assert( - currentContext != null, - 'LoggingTableRow.metadataStyle requires a valid navigatorKey to be set. If this assertion is hit in tests then make sure to `wrap()` the widget being pumped.', - ); - return Theme.of(currentContext!).subtleTextStyle; - } - - static TextStyle get detailsStyle => - Theme.of(navigatorKey.currentContext!).regularTextStyle; - - /// All of the metatadata chips that can be visible for this [data] entry. - @visibleForTesting - static List metadataChips(LogDataV2 data, double maxWidth) { - String? elapsedFrameTimeAsString; - try { - final int micros = (jsonDecode(data.details!) as Map)['elapsed']; - elapsedFrameTimeAsString = durationText( - Duration(microseconds: micros), - unit: DurationDisplayUnit.milliseconds, - fractionDigits: 2, - ); - } catch (e) { - // Ignore exception; [elapsedFrameTimeAsString] will be null. - } - - final kindIcon = KindMetaDataChip.generateIcon(data.kind); - return [ - if (data.timestamp != null) - WhenMetaDataChip( - timestamp: data.timestamp, - maxWidth: maxWidth, - ), - KindMetaDataChip( - kind: data.kind, - maxWidth: maxWidth, - icon: kindIcon.icon, - iconAsset: kindIcon.iconAsset, - ), - if (elapsedFrameTimeAsString != null) - FrameElapsedMetaDataChip( - maxWidth: maxWidth, - elapsedTimeDisplay: elapsedFrameTimeAsString, - ), - ]; - } - - @override - State createState() => _LoggingTableRowState(); - - static const _padding = densePadding; - - /// Estimates the height of the row, including the details section and all of the metadata chips. - static double estimateRowHeight( - LogDataV2 log, - double width, - ) { - final text = log.asLogDetails(); - final maxWidth = max(0.0, width - _padding * 2); - - final row1Height = calculateTextSpanHeight( - TextSpan(text: text, style: detailsStyle), - maxWidth: maxWidth, - ); - - final row2Height = estimateMetaDataWrapHeight(log, maxWidth); - - return row1Height + row2Height + _padding * 2; - } - - /// Estimates the height that the [metadataChips] will occupy if they are - /// the children of a [Wrap] widget with a parent of [maxWidth] width. - @visibleForTesting - static double estimateMetaDataWrapHeight(LogDataV2 data, double maxWidth) { - var totalHeight = 0.0; - var rowHeight = 0.0; - var remainingWidth = maxWidth; - - for (final presentChip in LoggingTableRow.metadataChips(data, maxWidth)) { - final chipSize = presentChip.estimateSize(); - if (chipSize.width > remainingWidth) { - // The chip does not fit so add it to a new row - totalHeight += rowHeight; - rowHeight = 0.0; - remainingWidth = maxWidth; - } - - // The Chip fits so it will stay in this row. - rowHeight = max(rowHeight, chipSize.height); - remainingWidth -= chipSize.width; - } - - totalHeight += rowHeight; - return totalHeight; - } -} - -class _LoggingTableRowState extends State { - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - final color = widget.isSelected - ? colorScheme.selectedRowBackgroundColor - : alternatingColorForIndex( - widget.index, - colorScheme, - ); - - return Container( - color: color, - child: ValueListenableBuilder( - valueListenable: widget.data.detailsComputed, - builder: (context, _, _) { - return Padding( - padding: const EdgeInsets.all(LoggingTableRow._padding), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - RichText( - text: TextSpan( - text: widget.data.asLogDetails(), - style: LoggingTableRow.detailsStyle, - ), - ), - LayoutBuilder( - builder: (context, constraints) { - return Wrap( - children: LoggingTableRow.metadataChips( - widget.data, - constraints.maxWidth, - ), - ); - }, - ), - ], - ), - ); - }, - ), - ); - } -} - -class WhenMetaDataChip extends MetadataChip { - WhenMetaDataChip({ - super.key, - required int? timestamp, - required super.maxWidth, - }) : super( - icon: null, - text: timestamp == null - ? '' - : loggingTableTimeFormat - .format(DateTime.fromMillisecondsSinceEpoch(timestamp)), - ); -} - -extension SizeExtension on MetadataChip { - /// Estimates the size of this single metadata chip. - /// - /// If the [build] method is changed then this may need to be updated - Size estimateSize() { - final horizontalPaddingCount = includeLeadingMargin ? 2 : 1; - final maxWidthInsidePadding = max( - 0.0, - maxWidth - MetadataChip.horizontalPadding * horizontalPaddingCount, - ); - final iconSize = Size.square(defaultIconSize); - final textSize = calculateTextSpanSize( - TextSpan( - text: text, - style: LoggingTableRow.metadataStyle, - ), - maxWidth: maxWidthInsidePadding, - ); - return Size( - ((icon != null || iconAsset != null) - ? iconSize.width + MetadataChip.iconPadding - : 0.0) + - textSize.width + - MetadataChip.horizontalPadding * horizontalPaddingCount, - max(iconSize.height, textSize.height) + MetadataChip.verticalPadding * 2, - ); - } -} diff --git a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_table_v2.dart b/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_table_v2.dart deleted file mode 100644 index 3f2338c1d0a..00000000000 --- a/packages/devtools_app/lib/src/screens/logging/logging_screen_v2/logging_table_v2.dart +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2024 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 'package:devtools_app_shared/ui.dart'; -import 'package:devtools_app_shared/utils.dart'; -import 'package:flutter/material.dart'; - -import '../../../service/service_extension_widgets.dart'; -import '../../../shared/analytics/constants.dart' as gac; -import '../../../shared/common_widgets.dart'; -import '../../../shared/globals.dart'; -import '../../../shared/ui/filter.dart'; -import '../../../shared/utils.dart'; -import 'log_data.dart'; -import 'logging_controller_v2.dart'; -import 'logging_model.dart'; -import 'logging_table_row.dart'; - -/// A Widget for displaying logs with line wrapping, along with log metadata. -class LoggingTableV2 extends StatefulWidget { - // TODO(danchevalier): Use SearchControllerMixin and FilterControllerMixin. - const LoggingTableV2({super.key, required this.model}); - - final LoggingTableModel model; - - @override - State createState() => _LoggingTableV2State(); -} - -class _LoggingTableV2State extends State - with ProvidedControllerMixin { - final selections = {}; - final cachedOffets = {}; - String lastSearch = ''; - double maxWidth = 0.0; - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - if (!initController()) return; - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Row( - children: [ - ClearButton( - onPressed: controller.clear, - gaScreen: gac.logging, - gaSelection: gac.clear, - ), - const SizedBox(width: defaultSpacing), - Expanded( - child: DevToolsClearableTextField( - labelText: 'Search', // TODO(danchevalier): use SearchField - ), - ), - const SizedBox(width: defaultSpacing), - Expanded( - child: StandaloneFilterField( - controller: widget.model, - filteredItem: 'log', - ), - ), // TODO for some reason the controller isn't hooking up correctly to the one over in the model. It is not getting notified of the changes? - const SizedBox(width: defaultSpacing), - SettingsOutlinedButton( - gaScreen: gac.logging, - gaSelection: gac.loggingSettings, - tooltip: 'Logging Settings', - onPressed: () { - unawaited( - showDialog( - context: context, - builder: (context) => const LoggingSettingsDialogV2(), - ), - ); - }, - ), - ], - ), - const SizedBox(height: denseSpacing), - Expanded( - child: RoundedOutlinedBorder( - clip: true, - child: _LoggingTableProgress( - model: widget.model, - ), - ), - ), - ], - ); - } -} - -class _LoggingTableProgress extends StatefulWidget { - const _LoggingTableProgress({ - required this.model, - }); - - final LoggingTableModel model; - - @override - State<_LoggingTableProgress> createState() => _LoggingTableProgressState(); -} - -class _LoggingTableProgressState extends State<_LoggingTableProgress> { - static const _millisecondsUntilCacheProgressShows = 500; - static const _millisecondsUntilCacheProgressHelperShows = 2000; - - final _progressStopwatch = Stopwatch(); - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (context, constraints) { - widget.model.tableWidth = constraints.maxWidth; - _progressStopwatch.reset(); - return ValueListenableBuilder( - valueListenable: widget.model.cacheLoadProgress, - builder: (context, cacheLoadProgress, _) { - if (cacheLoadProgress != null) { - double progress = cacheLoadProgress; - if (!_progressStopwatch.isRunning) { - _progressStopwatch.start(); - } - - if (_progressStopwatch.elapsedMilliseconds < - _millisecondsUntilCacheProgressShows) { - progress = 0.0; - } - - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Resizing... this will only take a moment.', - ), - const SizedBox(height: defaultSpacing), - SizedBox( - width: defaultLinearProgressIndicatorWidth, - height: defaultLinearProgressIndicatorHeight, - child: LinearProgressIndicator( - value: progress, - ), - ), - const SizedBox(height: defaultSpacing), - if (_progressStopwatch.elapsedMilliseconds > - _millisecondsUntilCacheProgressHelperShows) - const Text( - 'To make this process faster, reduce the "Log Retention" setting.', - ), - ], - ), - ); - } else { - _progressStopwatch - ..stop() - ..reset(); - return _LoggingTableRows(model: widget.model); - } - }, - ); - }, - ); - } - - @override - void dispose() { - _progressStopwatch.stop(); - super.dispose(); - } -} - -class _LoggingTableRows extends StatefulWidget { - const _LoggingTableRows({required this.model}); - - final LoggingTableModel model; - - @override - State<_LoggingTableRows> createState() => _LoggingTableRowsState(); -} - -class _LoggingTableRowsState extends State<_LoggingTableRows> - with AutoDisposeMixin { - late final _verticalController = ScrollController(); - - @override - void initState() { - super.initState(); - addAutoDisposeListener(widget.model); - } - - @override - Widget build(BuildContext context) { - return Scrollbar( - thumbVisibility: true, - controller: _verticalController, - child: CustomScrollView( - controller: _verticalController, - slivers: [ - SliverVariedExtentList.builder( - itemCount: widget.model.filteredLogCount, - itemBuilder: (context, index) { - return LoggingTableRow( - index: index, - data: widget.model.filteredLogAt(index), - isSelected: false, - ); - }, - itemExtentBuilder: (index, _) => - widget.model.getFilteredLogHeight(index), - ), - ], - ), - ); - } -} - -class LoggingSettingsDialogV2 extends StatefulWidget { - const LoggingSettingsDialogV2({super.key}); - - @override - State createState() => - _LoggingSettingsDialogV2State(); -} - -class _LoggingSettingsDialogV2State extends State { - late final ValueNotifier newRetentionLimit; - - @override - void initState() { - super.initState(); - newRetentionLimit = - ValueNotifier(preferences.logging.retentionLimit.value); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return DevToolsDialog( - title: const DialogTitleText('Logging Settings'), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...dialogSubHeader( - theme, - 'General', - ), - const StructuredErrorsToggle(), - const SizedBox(height: defaultSpacing), - PositiveIntegerSetting( - title: preferences.logging.retentionLimitTitle, - subTitle: 'Used to limit the number of log messages retained.', - notifier: newRetentionLimit, - ), - ], - ), - actions: [ - DialogApplyButton( - onPressed: () { - preferences.logging.retentionLimit.value = newRetentionLimit.value; - }, - ), - const DialogCloseButton(), - ], - ); - } -} diff --git a/packages/devtools_app/lib/src/screens/logging/shared/constants.dart b/packages/devtools_app/lib/src/screens/logging/shared/constants.dart deleted file mode 100644 index 74c92dd67cb..00000000000 --- a/packages/devtools_app/lib/src/screens/logging/shared/constants.dart +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2023 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:intl/intl.dart'; - -const loggingMinVerboseWidth = 650.0; - -final loggingTableTimeFormat = DateFormat('HH:mm:ss.SSS'); diff --git a/packages/devtools_app/lib/src/shared/feature_flags.dart b/packages/devtools_app/lib/src/shared/feature_flags.dart index 8901a3b4195..73bf7dbdc8a 100644 --- a/packages/devtools_app/lib/src/shared/feature_flags.dart +++ b/packages/devtools_app/lib/src/shared/feature_flags.dart @@ -84,11 +84,6 @@ abstract class FeatureFlags { /// once extension support is added in g3. static bool devToolsExtensions = isExternalBuild; - /// Flag to enable the new Logging experience. - /// - /// https://github.com/flutter/devtools/issues/7703 - static bool loggingV2 = enableExperiments; - /// Flag to enable debugging via DAP. /// /// https://github.com/flutter/devtools/issues/6056 @@ -112,7 +107,6 @@ abstract class FeatureFlags { 'widgetRebuildStats': widgetRebuildStats, 'memorySaveLoad': memorySaveLoad, 'deepLinkIosCheck': deepLinkIosCheck, - 'loggingV2': loggingV2, 'dapDebugging': dapDebugging, 'inspectorV2': inspectorV2, 'wasmOptInSetting': wasmOptInSetting, diff --git a/packages/devtools_app/test/logging/logging_screen_v2/logging_controller_v2_test.dart b/packages/devtools_app/test/logging/logging_screen_v2/logging_controller_v2_test.dart deleted file mode 100644 index 49b4cd77c7c..00000000000 --- a/packages/devtools_app/test/logging/logging_screen_v2/logging_controller_v2_test.dart +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2024 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. - -// ignore_for_file: unused_import - -@TestOn('vm') -library; - -import 'dart:convert'; - -import 'package:devtools_app/devtools_app.dart'; -import 'package:devtools_app/src/shared/primitives/message_bus.dart'; -import 'package:devtools_app_shared/ui.dart'; -import 'package:devtools_app_shared/utils.dart'; -import 'package:devtools_test/devtools_test.dart'; -import 'package:devtools_test/helpers.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('LoggingControllerV2', () { - late LoggingControllerV2 controller; - - void addStdoutData(String message) { - controller.log( - LogDataV2( - 'stdout', - jsonEncode({'kind': 'stdout', 'message': message}), - 0, - summary: message, - ), - ); - } - - setUp(() { - setGlobal( - ServiceConnectionManager, - FakeServiceConnectionManager(), - ); - setGlobal(PreferencesController, PreferencesController()); - setGlobal(IdeTheme, getIdeTheme()); - setGlobal(MessageBus, MessageBus()); - TestWidgetsFlutterBinding.ensureInitialized(); - controller = LoggingControllerV2(); - controller.loggingModel.tableWidth = 100.0; - }); - - testWidgets('initial state', (WidgetTester tester) async { - await tester.pumpWidget(wrap(const Text(''))); - expect(controller.loggingModel.logCount, 0); - expect(controller.loggingModel.filteredLogCount, 0); - expect(controller.loggingModel.selectedLogCount, 0); - expect( - controller.loggingModel.activeFilter.value.settingFilters.length, - 3, - ); - expect( - controller.loggingModel.activeFilter.value.queryFilter.isEmpty, - isTrue, - ); - }); - - testWidgets('receives data', (WidgetTester tester) async { - await tester.pumpWidget(wrap(const Text(''))); - expect(controller.loggingModel.logCount, 0); - expect(controller.loggingModel.filteredLogCount, 0); - expect(controller.loggingModel.selectedLogCount, 0); - - addStdoutData('Abc.'); - - expect(controller.loggingModel.logCount, 1); - expect(controller.loggingModel.filteredLogCount, 1); - expect(controller.loggingModel.selectedLogCount, 0); - - expect( - controller.loggingModel.filteredLogAt(0).summary, - contains('Abc'), - ); - }); - - testWidgets('clear', (WidgetTester tester) async { - await tester.pumpWidget(wrap(const Text(''))); - addStdoutData('Abc.'); - - expect(controller.loggingModel.logCount, 1); - expect(controller.loggingModel.filteredLogCount, 1); - - controller.clear(); - - expect(controller.loggingModel.logCount, 0); - expect(controller.loggingModel.filteredLogCount, 0); - }); - }); - - group('LogData', () { - test( - 'pretty prints when details are json, and returns its details otherwise.', - () { - final nonJson = LogData('some kind', 'Not json', 0); - final json = LogData( - 'some kind', - '{"firstValue": "value", "otherValue": "value2"}', - 1, - ); - final nullDetails = LogData('some kind', null, 1); - const prettyJson = '{\n' - ' "firstValue": "value",\n' - ' "otherValue": "value2"\n' - '}'; - - expect(json.prettyPrinted(), prettyJson); - expect(nonJson.prettyPrinted(), 'Not json'); - expect(nullDetails.prettyPrinted(), null); - }, - ); - }); -} diff --git a/packages/devtools_app/test/logging/logging_screen_v2/logging_model_test.dart b/packages/devtools_app/test/logging/logging_screen_v2/logging_model_test.dart deleted file mode 100644 index 8613cc150ee..00000000000 --- a/packages/devtools_app/test/logging/logging_screen_v2/logging_model_test.dart +++ /dev/null @@ -1,551 +0,0 @@ -// Copyright 2024 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:convert'; - -import 'package:devtools_app/src/screens/logging/logging_screen_v2/log_data.dart'; -import 'package:devtools_app/src/screens/logging/logging_screen_v2/logging_model.dart'; -import 'package:devtools_app/src/screens/logging/logging_screen_v2/logging_table_row.dart'; -import 'package:devtools_app/src/service/service_manager.dart'; -import 'package:devtools_app/src/shared/globals.dart'; -import 'package:devtools_app/src/shared/preferences/preferences.dart'; -import 'package:devtools_app/src/shared/primitives/message_bus.dart'; -import 'package:devtools_app_shared/ui.dart'; -import 'package:devtools_app_shared/utils.dart'; -import 'package:devtools_test/devtools_test.dart'; -import 'package:devtools_test/helpers.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - late LoggingTableModel loggingTableModel; - final log1 = LogDataV2('test', 'The details', 464564); - - Future pumpForContext(WidgetTester tester) async { - // A barebones widget is pumped to ensure that a style is available - // for the LogTableModel to approximate widget sizes with - await tester.pumpWidget(wrap(const Placeholder())); - } - - Finder findLogRow(String details) => find.ancestor( - of: find.richText(details), - matching: find.byType(LoggingTableRow), - ); - - setUp(() { - final fakeServiceConnection = FakeServiceConnectionManager(); - setGlobal(PreferencesController, PreferencesController()); - setGlobal(IdeTheme, getIdeTheme()); - setGlobal(ServiceConnectionManager, fakeServiceConnection); - setGlobal(GlobalKey, GlobalKey()); - - TestWidgetsFlutterBinding.ensureInitialized(); - loggingTableModel = LoggingTableModel(); - - // Set a generic width for all tests - loggingTableModel.tableWidth = 500; - }); - - tearDown(() { - loggingTableModel.dispose(); - }); - - return; - - // ignore: dead_code, intentionally skipped tests. This code will be removed soon. - group('LoggingModel', () { - testWidgets('can add logs', (WidgetTester tester) async { - await pumpForContext(tester); - expect(loggingTableModel.logCount, 0); - expect(loggingTableModel.filteredLogCount, 0); - expect(loggingTableModel.selectedLogCount, 0); - - loggingTableModel.add(log1); - - expect(loggingTableModel.logCount, 1); - expect(loggingTableModel.filteredLogCount, 1); - expect(loggingTableModel.selectedLogCount, 0); - }); - - for (var windowWidth = 300.0; windowWidth <= 550.0; windowWidth += 50.0) { - testWidgetsWithWindowSize( - 'calculate heights correctly for window of width: $windowWidth', - Size(windowWidth, 3000), - (WidgetTester tester) async { - await pumpForContext(tester); - final shortLog = LogDataV2('test', 'Some short details', 464564); - final longLog = LogDataV2( - 'test', - 'A long log, A long log, A long log, A long log, A long log, A long log, A long log.', - 464564, - ); - final frameElapsedLog = - LogDataV2('frameLog', '{"elapsed": 1000000}', 4684506); - double? columnWidth; - final frameElapsedKey = GlobalKey(); - - await tester.pumpWidget( - wrap( - LayoutBuilder( - builder: (context, constraints) { - columnWidth = constraints.maxWidth; - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - LoggingTableRow( - index: 0, - data: shortLog, - isSelected: false, - ), - LoggingTableRow( - index: 1, - data: longLog, - isSelected: false, - ), - LoggingTableRow( - key: frameElapsedKey, - index: 2, - data: frameElapsedLog, - isSelected: false, - ), - ], - ); - }, - ), - ), - ); - - await tester.runAsync(() async { - // tester.runAsync is needed here to prevent the ChunkWorker - // in set tableWidth from blocking in the test. The Future.delayed - // in it would hang otherwise. - loggingTableModel.tableWidth = columnWidth!; - }); - - loggingTableModel - ..add(shortLog) - ..add(longLog) - ..add(frameElapsedLog); - - final shortWidgetFinder = findLogRow(shortLog.details!); - final longWidgetFinder = findLogRow(longLog.details!); - final frameElapsedFinder = find.byKey(frameElapsedKey); - - await tester.runAsync(() async { - // tester.runAsync is needed here to prevent the ChunkWorker - // in set tableWidth from blocking in the test. The Future.delayed - // in it would hang otherwise. - loggingTableModel.tableWidth = windowWidth; - }); - - expect(shortWidgetFinder, findsOneWidget); - expect(longWidgetFinder, findsOneWidget); - expect(frameElapsedFinder, findsOneWidget); - - expect( - loggingTableModel.getFilteredLogHeight(0), - tester.getSize(shortWidgetFinder).height, - ); - expect( - loggingTableModel.getFilteredLogHeight(1), - tester.getSize(longWidgetFinder).height, - ); - expect( - loggingTableModel.getFilteredLogHeight(2), - tester.getSize(frameElapsedFinder).height, - ); - }, - ); - } - - testWidgets('Handles log retention', (WidgetTester tester) async { - final log1 = LogDataV2('test', 'The details 1', 464564); - final log2 = LogDataV2('test', 'The details 2', 464564); - final log3 = LogDataV2('test', 'The details 3', 464564); - - await pumpForContext(tester); - - preferences.logging.retentionLimit.value = 2; - await tester.pump(); - - loggingTableModel - ..add(log1) - ..add(log2) - ..add(log3); - - expect(loggingTableModel.logCount, 2); - expect(loggingTableModel.filteredLogCount, 2); - expect(loggingTableModel.selectedLogCount, 0); - - final completer = Completer(); - loggingTableModel.addListener(() => completer.complete()); - preferences.logging.retentionLimit.value = 1; - await completer.future; - - expect(loggingTableModel.logCount, 1); - expect(loggingTableModel.filteredLogCount, 1); - expect(loggingTableModel.selectedLogCount, 0); - }); - - testWidgets('filters logs', (WidgetTester tester) async { - final log1 = LogDataV2('test', 'The details 123', 464564); - final log2 = LogDataV2('test', 'The details 456', 464564); - final log3 = LogDataV2('test', 'The details 476', 464564); - - await pumpForContext(tester); - - preferences.logging.retentionLimit.value = 20; - - loggingTableModel - ..add(log1) - ..add(log2) - ..add(log3); - - expect(loggingTableModel.logCount, 3); - expect(loggingTableModel.filteredLogCount, 3); - expect(loggingTableModel.selectedLogCount, 0); - - // Set the filter - loggingTableModel.setActiveFilter( - query: '6', - ); - - expect(loggingTableModel.logCount, 3); - expect(loggingTableModel.filteredLogCount, 2); - expect(loggingTableModel.selectedLogCount, 0); - - // Add a log that does not match to ensure it is filtered out - loggingTableModel - .add(LogDataV2('test', 'some non-matching details', 45654)); - - expect(loggingTableModel.logCount, 4); - expect(loggingTableModel.filteredLogCount, 2); - expect(loggingTableModel.selectedLogCount, 0); - - // Add a log that matches to ensure that it is included - loggingTableModel - .add(LogDataV2('test', 'some matching details: 6', 45654)); - - expect(loggingTableModel.logCount, 5); - expect(loggingTableModel.filteredLogCount, 3); - expect(loggingTableModel.selectedLogCount, 0); - }); - - testWidgets('filters by all relevant fields', (WidgetTester tester) async { - final log1 = LogDataV2( - 'test1', - 'The details 4', - 464564, - summary: 'Summary 7', - ); - final log2 = LogDataV2( - 'test2', - 'The details 5', - 464564, - summary: 'Summary 8', - ); - final log3 = LogDataV2( - 'test3', - 'The details 6', - 464564, - summary: 'Summary 9', - ); - - await pumpForContext(tester); - - preferences.logging.retentionLimit.value = 20; - - loggingTableModel - ..add(log1) - ..add(log2) - ..add(log3); - - expect(loggingTableModel.logCount, 3); - expect(loggingTableModel.filteredLogCount, 3); - expect(loggingTableModel.selectedLogCount, 0); - - // Check against kind - loggingTableModel.setActiveFilter( - query: '1', - ); - - expect(loggingTableModel.logCount, 3); - expect(loggingTableModel.filteredLogCount, 1); - expect(loggingTableModel.selectedLogCount, 0); - expect(loggingTableModel.filteredLogAt(0), equals(log1)); - - // Check against details - loggingTableModel.setActiveFilter( - query: '5', - ); - - expect(loggingTableModel.logCount, 3); - expect(loggingTableModel.filteredLogCount, 1); - expect(loggingTableModel.selectedLogCount, 0); - expect(loggingTableModel.filteredLogAt(0), equals(log2)); - - // Check against summary - loggingTableModel.setActiveFilter( - query: '9', - ); - - expect(loggingTableModel.logCount, 3); - expect(loggingTableModel.filteredLogCount, 1); - expect(loggingTableModel.selectedLogCount, 0); - expect(loggingTableModel.filteredLogAt(0), equals(log3)); - }); - - testWidgets('works with regexp', (WidgetTester tester) async { - final log1 = LogDataV2( - 'test1', - '456', - 464564, - summary: 'Summary 7', - ); - final log2 = LogDataV2( - 'test2', - '789', - 464564, - summary: 'Summary 8', - ); - final log3 = LogDataV2( - 'test3', - '476', - 464564, - summary: 'Summary 9', - ); - - await pumpForContext(tester); - - preferences.logging.retentionLimit.value = 20; - - loggingTableModel - ..add(log1) - ..add(log2) - ..add(log3); - - expect(loggingTableModel.logCount, 3); - expect(loggingTableModel.filteredLogCount, 3); - expect(loggingTableModel.selectedLogCount, 0); - - loggingTableModel.setActiveFilter( - query: '4.6', - ); - - expect(loggingTableModel.logCount, 3); - expect(loggingTableModel.filteredLogCount, 0); - expect(loggingTableModel.selectedLogCount, 0); - - loggingTableModel.useRegExp.value = true; - loggingTableModel.setActiveFilter( - query: '4.6', - ); - - expect(loggingTableModel.logCount, 3); - expect(loggingTableModel.filteredLogCount, 2); - expect(loggingTableModel.selectedLogCount, 0); - expect(loggingTableModel.filteredLogAt(0), equals(log1)); - expect(loggingTableModel.filteredLogAt(1), equals(log3)); - }); - }); - - group('Original LoggingController tests', () { - setGlobal(MessageBus, MessageBus()); - - void addStdoutData(String message) { - loggingTableModel.add( - LogDataV2( - 'stdout', - jsonEncode({'kind': 'stdout', 'message': message}), - 0, - summary: message, - ), - ); - } - - void addGcData(String message) { - loggingTableModel.add( - LogDataV2( - 'gc', - jsonEncode({'kind': 'gc', 'message': message}), - 0, - summary: message, - ), - ); - } - - void addLogWithKind(String kind) { - loggingTableModel - .add(LogDataV2(kind, jsonEncode({'foo': 'test_data'}), 0)); - } - - setUp(() { - setGlobal( - ServiceConnectionManager, - FakeServiceConnectionManager(), - ); - }); - - test('initial state', () { - expect(loggingTableModel.logCount, 0); - expect(loggingTableModel.filteredLogCount, 0); - expect(loggingTableModel.activeFilter.value.isEmpty, isFalse); - }); - - testWidgets('receives data', (WidgetTester tester) async { - await pumpForContext(tester); - - expect(loggingTableModel.logCount, 0); - - addStdoutData('Abc.'); - - expect(loggingTableModel.logCount, greaterThan(0)); - expect(loggingTableModel.filteredLogCount, greaterThan(0)); - - expect(loggingTableModel.filteredLogAt(0).summary, contains('Abc')); - }); - - testWidgets('clear', (WidgetTester tester) async { - await pumpForContext(tester); - addStdoutData('Abc.'); - - expect(loggingTableModel.logCount, greaterThan(0)); - expect(loggingTableModel.filteredLogCount, greaterThan(0)); - - loggingTableModel.clear(); - - expect(loggingTableModel.logCount, 0); - expect(loggingTableModel.filteredLogCount, 0); - }); - - // test('matchesForSearch', () { - // addStdoutData('abc'); - // addStdoutData('def'); - // addStdoutData('abc ghi'); - // addLogWithKind('Flutter.Navigation'); - // addLogWithKind('Flutter.Error'); - // addGcData('gc1'); - // addGcData('gc2'); - // - // expect(loggingTableModel.filteredData.value, 5); - // expect(loggingTableModel.matchesForSearch('abc').length, equals(2)); - // expect(loggingTableModel.matchesForSearch('ghi').length, equals(1)); - // expect(loggingTableModel.matchesForSearch('abcd').length, equals(0)); - // expect(loggingTableModel.matchesForSearch('Flutter*').length, equals(2)); - // expect(loggingTableModel.matchesForSearch('').length, equals(0)); - // - // // Search by event kind. - // expect(loggingTableModel.matchesForSearch('stdout').length, equals(3)); - // expect(loggingTableModel.matchesForSearch('flutter.*').length, equals(2)); - // - // // Search with incorrect case. - // expect(loggingTableModel.matchesForSearch('STDOUT').length, equals(3)); - // }); - // - // test('matchesForSearch sets isSearchMatch property', () { - // addStdoutData('abc'); - // addStdoutData('def'); - // addStdoutData('abc ghi'); - // addLogWithKind('Flutter.Navigation'); - // addLogWithKind('Flutter.Error'); - // addGcData('gc1'); - // addGcData('gc2'); - // - // expect(loggingTableModel.filteredData.value, 5); - // loggingTableModel.search = 'abc'; - // var matches = loggingTableModel.searchMatches.value; - // expect(matches.length, equals(2)); - // verifyIsSearchMatch(loggingTableModel.filteredData.value, matches); - // - // loggingTableModel.search = 'Flutter.'; - // matches = loggingTableModel.searchMatches.value; - // expect(matches.length, equals(2)); - // verifyIsSearchMatch(loggingTableModel.filteredData.value, matches); - // }); - - testWidgets('filterData', (WidgetTester tester) async { - await pumpForContext(tester); - - addStdoutData('abc'); - addStdoutData('def'); - addStdoutData('abc ghi'); - addLogWithKind('Flutter.Navigation'); - addLogWithKind('Flutter.Error'); - - // The following logs should all be filtered by default. - addGcData('gc1'); - addGcData('gc2'); - addLogWithKind('Flutter.FirstFrame'); - addLogWithKind('Flutter.FrameworkInitialization'); - addLogWithKind('Flutter.Frame'); - addLogWithKind('Flutter.ImageSizesForFrame'); - addLogWithKind('Flutter.ServiceExtensionStateChanged'); - - // At this point data is filtered by the default toggle filter values. - expect(loggingTableModel.logCount, 12); - expect(loggingTableModel.filteredLogCount, 5); - - // Test query filters assuming default toggle filters are all enabled. - for (final filter - in loggingTableModel.activeFilter.value.settingFilters) { - filter.setting.value = true; - } - - loggingTableModel.setActiveFilter(query: 'abc'); - expect(loggingTableModel.logCount, 12); - expect(loggingTableModel.filteredLogCount, 2); - - loggingTableModel.setActiveFilter(query: 'def'); - expect(loggingTableModel.logCount, 12); - expect(loggingTableModel.filteredLogCount, 1); - - loggingTableModel.setActiveFilter(query: 'abc def'); - expect(loggingTableModel.logCount, 12); - expect(loggingTableModel.filteredLogCount, 3); - - loggingTableModel.setActiveFilter(query: 'k:stdout'); - expect(loggingTableModel.logCount, 12); - expect(loggingTableModel.filteredLogCount, 3); - - loggingTableModel.setActiveFilter(query: '-k:stdout'); - expect(loggingTableModel.logCount, 12); - expect(loggingTableModel.filteredLogCount, 2); - - loggingTableModel.setActiveFilter(query: 'k:stdout abc'); - expect(loggingTableModel.logCount, 12); - expect(loggingTableModel.filteredLogCount, 2); - - loggingTableModel.setActiveFilter(query: 'k:stdout,flutter.navigation'); - expect(loggingTableModel.logCount, 12); - expect(loggingTableModel.filteredLogCount, 4); - - loggingTableModel.setActiveFilter(); - expect(loggingTableModel.logCount, 12); - expect(loggingTableModel.filteredLogCount, 5); - - // Test toggle filters. - final verboseFlutterFrameworkFilter = - loggingTableModel.activeFilter.value.settingFilters[0]; - final verboseFlutterServiceFilter = - loggingTableModel.activeFilter.value.settingFilters[1]; - final gcFilter = loggingTableModel.activeFilter.value.settingFilters[2]; - - verboseFlutterFrameworkFilter.setting.value = false; - loggingTableModel.setActiveFilter(); - expect(loggingTableModel.logCount, 12); - expect(loggingTableModel.filteredLogCount, 9); - - verboseFlutterServiceFilter.setting.value = false; - loggingTableModel.setActiveFilter(); - expect(loggingTableModel.logCount, 12); - expect(loggingTableModel.filteredLogCount, 10); - - gcFilter.setting.value = false; - loggingTableModel.setActiveFilter(); - expect(loggingTableModel.logCount, 12); - expect(loggingTableModel.filteredLogCount, 12); - }); - }); -} diff --git a/packages/devtools_app/test/logging/logging_screen_v2/logging_screen_v2_test.dart b/packages/devtools_app/test/logging/logging_screen_v2/logging_screen_v2_test.dart deleted file mode 100644 index f1a29afe811..00000000000 --- a/packages/devtools_app/test/logging/logging_screen_v2/logging_screen_v2_test.dart +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2024 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. - -@TestOn('vm') -library; - -import 'package:devtools_app/devtools_app.dart'; -import 'package:devtools_app/src/screens/logging/logging_screen_v2/logging_table_v2.dart'; -import 'package:devtools_app/src/shared/primitives/message_bus.dart'; -import 'package:devtools_app_shared/ui.dart'; -import 'package:devtools_app_shared/utils.dart'; -import 'package:devtools_test/devtools_test.dart'; -import 'package:devtools_test/helpers.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; - -void main() { - late LoggingScreenV2 screen; - late MockLoggingControllerV2 mockLoggingController; - const windowSize = Size(1000.0, 1000.0); - TestWidgetsFlutterBinding.ensureInitialized(); - - group('Logging Screen', () { - Future pumpLoggingScreen(WidgetTester tester) async { - await tester.pumpWidget( - wrapWithControllers( - const LoggingScreenBodyV2(), - loggingV2: mockLoggingController, - ), - ); - } - - setUp(() { - final fakeServiceConnection = FakeServiceConnectionManager(); - when( - fakeServiceConnection.serviceManager.connectedApp!.isFlutterWebAppNow, - ).thenReturn(false); - when(fakeServiceConnection.serviceManager.connectedApp!.isProfileBuildNow) - .thenReturn(false); - when( - fakeServiceConnection.errorBadgeManager.errorCountNotifier('logging'), - ).thenReturn(ValueNotifier(0)); - setGlobal(GlobalKey, GlobalKey()); - setGlobal(NotificationService, NotificationService()); - setGlobal( - DevToolsEnvironmentParameters, - ExternalDevToolsEnvironmentParameters(), - ); - setGlobal(PreferencesController, PreferencesController()); - setGlobal(ServiceConnectionManager, fakeServiceConnection); - setGlobal(IdeTheme, IdeTheme()); - setGlobal(MessageBus, MessageBus()); - - mockLoggingController = createMockLoggingControllerV2WithDefaults(); - - screen = LoggingScreenV2(); - }); - - testWidgets('builds its tab', (WidgetTester tester) async { - await tester.pumpWidget(wrap(Builder(builder: screen.buildTab))); - expect(find.text('Logging'), findsOneWidget); - }); - - testWidgetsWithWindowSize( - 'builds with no data', - windowSize, - (WidgetTester tester) async { - await pumpLoggingScreen(tester); - expect(find.byType(LoggingScreenBodyV2), findsOneWidget); - expect(find.byType(LoggingTableV2), findsOneWidget); - }, - ); - }); -} diff --git a/packages/devtools_app/test/logging/logging_screen_v2/logging_table_row_test.dart b/packages/devtools_app/test/logging/logging_screen_v2/logging_table_row_test.dart deleted file mode 100644 index 792f3b1ddb1..00000000000 --- a/packages/devtools_app/test/logging/logging_screen_v2/logging_table_row_test.dart +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2024 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:devtools_app/devtools_app.dart'; -import 'package:devtools_app/src/screens/logging/logging_screen_v2/logging_table_row.dart'; -import 'package:devtools_app/src/screens/logging/metadata.dart'; -import 'package:devtools_app_shared/ui.dart'; -import 'package:devtools_app_shared/utils.dart'; -import 'package:devtools_test/devtools_test.dart'; -import 'package:devtools_test/helpers.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - const windowSize = Size(1000, 1000); - - setUp(() { - setGlobal(IdeTheme, getIdeTheme()); - setGlobal(GlobalKey, GlobalKey()); - setGlobal(ServiceConnectionManager, FakeServiceConnectionManager()); - }); - - return; - - // ignore: dead_code, intentionally skipped tests. This code will be removed soon. - group('logging_table_row', () { - for (double windowWidth = 208.0; windowWidth < 600.0; windowWidth += 15.0) { - const numberOfChips = 3; - final data = LogDataV2( - 'someKind', - '{"elapsed": 374}', - 213567823783, - ); - final windowSize = Size(windowWidth, 500); - - testWidgetsWithWindowSize( - 'Estimates the height of a row correctly for windowWidth: $windowWidth', - windowSize, - (WidgetTester tester) async { - await tester.pumpWidget( - Column( - mainAxisSize: MainAxisSize.min, - children: [ - wrap( - LoggingTableRow( - index: 0, - data: data, - isSelected: false, - ), - ), - ], - ), - ); - expect( - LoggingTableRow.estimateRowHeight(data, windowWidth), - tester.getSize(find.byType(LoggingTableRow)).height, - ); - }, - ); - - testWidgetsWithWindowSize( - 'Estimates the height of the wrapped chips correctly for windowWidth: $windowWidth', - windowSize, - (WidgetTester tester) async { - final chips = LoggingTableRow.metadataChips(data, windowSize.width); - final wrapKey = GlobalKey(); - - expect(chips.length, numberOfChips); - await tester.pumpWidget( - wrap( - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Wrap( - key: wrapKey, - children: chips, - ), - ], - ), - ), - ); - expect( - LoggingTableRow.estimateMetaDataWrapHeight( - data, - windowWidth, - ), - tester.getSize(find.byKey(wrapKey)).height, - ); - }, - ); - } - - testWidgetsWithWindowSize( - 'estimates MetadataChip sizes correctly', - windowSize, - (WidgetTester tester) async { - const numberOfChips = 3; - final data = - LogDataV2('someKind', '{"elapsed": 378564654}', 213567823783); - final chips = LoggingTableRow.metadataChips(data, windowSize.width); - final wrapKey = GlobalKey(); - - await tester.pumpWidget( - wrap( - Wrap( - key: wrapKey, - children: chips, - ), - ), - ); - - final chipFinder = find.bySubtype(); - - expect(chipFinder, findsExactly(numberOfChips)); - final chipElements = chipFinder.evaluate(); - for (var i = 0; i < numberOfChips; i++) { - final chip = chips[i]; - final chipElement = chipElements.elementAt(i); - expect(chip.estimateSize(), chipElement.size); - } - }, - ); - }); -} diff --git a/packages/devtools_app/test/shared/primitives/feature_flags_test.dart b/packages/devtools_app/test/shared/primitives/feature_flags_test.dart index 0cfb7f3582f..327fa6c523c 100644 --- a/packages/devtools_app/test/shared/primitives/feature_flags_test.dart +++ b/packages/devtools_app/test/shared/primitives/feature_flags_test.dart @@ -13,7 +13,6 @@ void main() { expect(isExternalBuild, true); expect(FeatureFlags.memorySaveLoad, false); expect(FeatureFlags.deepLinkIosCheck, true); - expect(FeatureFlags.loggingV2, false); expect(FeatureFlags.dapDebugging, false); expect(FeatureFlags.wasmOptInSetting, true); }); diff --git a/packages/devtools_test/lib/src/helpers/wrappers.dart b/packages/devtools_test/lib/src/helpers/wrappers.dart index bad4b948c8a..d4a1c21a748 100644 --- a/packages/devtools_test/lib/src/helpers/wrappers.dart +++ b/packages/devtools_test/lib/src/helpers/wrappers.dart @@ -87,7 +87,6 @@ Widget wrapWithControllers( Widget widget, { InspectorScreenController? inspector, LoggingController? logging, - LoggingControllerV2? loggingV2, MemoryController? memory, PerformanceController? performance, ProfilerScreenController? profiler, @@ -105,8 +104,6 @@ Widget wrapWithControllers( if (inspector != null) Provider.value(value: inspector), if (logging != null) Provider.value(value: logging), - if (loggingV2 != null) - Provider.value(value: loggingV2), if (memory != null) Provider.value(value: memory), if (performance != null) Provider.value(value: performance), diff --git a/packages/devtools_test/lib/src/mocks/generated.dart b/packages/devtools_test/lib/src/mocks/generated.dart index a6ac4d7668c..bba51c04bc4 100644 --- a/packages/devtools_test/lib/src/mocks/generated.dart +++ b/packages/devtools_test/lib/src/mocks/generated.dart @@ -22,7 +22,6 @@ import 'package:vm_service/vm_service.dart'; MockSpec(), MockSpec(), MockSpec(), - MockSpec(), MockSpec(), MockSpec(), MockSpec(), diff --git a/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart b/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart index 7956bb3779d..339f547ff82 100644 --- a/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart +++ b/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart @@ -225,14 +225,3 @@ MockLoggingController createMockLoggingControllerWithDefaults({ return mockLoggingController; } - -MockLoggingControllerV2 createMockLoggingControllerV2WithDefaults() { - provideDummy>( - ListValueNotifier([]), - ); - final mockLoggingController = MockLoggingControllerV2(); - when(mockLoggingController.loggingModel).thenReturn(LoggingTableModel()); - when(mockLoggingController.selectedLog) - .thenReturn(ValueNotifier(null)); - return mockLoggingController; -}