Skip to content

Commit

Permalink
Support logging 'flutter run' communication to DAP clients (#118674)
Browse files Browse the repository at this point in the history
* Support logging 'flutter run' communication to DAP clients

Fixes Dart-Code/Dart-Code#4266.

* Fix test
  • Loading branch information
DanTup committed Jan 19, 2023
1 parent 818bb4e commit 09bd0f6
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 6 deletions.
23 changes: 20 additions & 3 deletions packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
final String messageString = jsonEncode(message);
// Flutter requests are always wrapped in brackets as an array.
final String payload = '[$messageString]\n';
_logTraffic('==> [Flutter] $payload');
process.stdin.writeln(payload);
}

Expand Down Expand Up @@ -447,7 +448,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
@override
void handleExitCode(int code) {
final String codeSuffix = code == 0 ? '' : ' ($code)';
logger?.call('Process exited ($code)');
_logTraffic('<== [Flutter] Process exited ($code)');
handleSessionTerminate(codeSuffix);
}

Expand Down Expand Up @@ -559,7 +560,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {

@override
void handleStderr(List<int> data) {
logger?.call('stderr: $data');
_logTraffic('<== [Flutter] [stderr] $data');
sendOutput('stderr', utf8.decode(data));
}

Expand All @@ -575,7 +576,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
// - the item has an "event" field that is a String
// - the item has a "params" field that is a Map<String, Object?>?

logger?.call('stdout: $data');
_logTraffic('<== [Flutter] $data');

// Output is sent as console (eg. output from tooling) until the app has
// started, then stdout (users output). This is so info like
Expand Down Expand Up @@ -639,6 +640,22 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
}
}

/// Logs JSON traffic to aid debugging.
///
/// If `sendLogsToClient` was `true` in the launch/attach config, logs will
/// also be sent back to the client in a "dart.log" event to simplify
/// capturing logs from the IDE (such as using the **Dart: Capture Logs**
/// command in VS Code).
void _logTraffic(String message) {
logger?.call(message);
if (sendLogsToClient) {
sendEvent(
RawEventBody(<String, String>{'message': message}),
eventType: 'dart.log',
);
}
}

/// Performs a restart/reload by sending the `app.restart` message to the `flutter run --machine` process.
Future<void> _performRestart(
bool fullRestart, [
Expand Down
5 changes: 5 additions & 0 deletions packages/flutter_tools/test/general.shard/dap/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
late List<String> processArgs;
late Map<String, String>? env;

/// Overrides base implementation of [sendLogsToClient] which requires valid
/// `args` to have been set which may not be the case for mocks.
@override
bool get sendLogsToClient => false;

final StreamController<Map<String, Object?>> _dapToClientMessagesController = StreamController<Map<String, Object?>>.broadcast();

/// A stream of all messages sent from the adapter back to the client.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,39 @@ void main() {
]);
});

testWithoutContext('logs to client when sendLogsToClient=true', () async {
final BasicProject project = BasicProject();
await project.setUpIn(tempDir);

// Launch the app and wait for it to print "topLevelFunction".
await Future.wait(<Future<void>>[
dap.client.stdoutOutput.firstWhere((String output) => output.startsWith('topLevelFunction')),
dap.client.start(
launch: () => dap.client.launch(
cwd: project.dir.path,
noDebug: true,
toolArgs: <String>['-d', 'flutter-tester'],
sendLogsToClient: true,
),
),
], eagerError: true);

// Capture events while terminating.
final Future<List<Event>> logEventsFuture = dap.client.events('dart.log').toList();
await dap.client.terminate();

// Ensure logs contain both the app.stop request and the result.
final List<Event> logEvents = await logEventsFuture;
final List<String> logMessages = logEvents.map((Event l) => (l.body! as Map<String, Object?>)['message']! as String).toList();
expect(
logMessages,
containsAll(<Matcher>[
startsWith('==> [Flutter] [{"id":1,"method":"app.stop"'),
startsWith('<== [Flutter] [{"id":1,"result":true}]'),
]),
);
});

testWithoutContext('can run and terminate a Flutter app in noDebug mode', () async {
final BasicProject project = BasicProject();
await project.setUpIn(tempDir);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class DapTestClient {
bool? debugExternalPackageLibraries,
bool? evaluateGettersInDebugViews,
bool? evaluateToStringInDebugViews,
bool sendLogsToClient = false,
}) {
return sendRequest(
FlutterLaunchRequestArguments(
Expand All @@ -167,9 +168,9 @@ class DapTestClient {
evaluateGettersInDebugViews: evaluateGettersInDebugViews,
evaluateToStringInDebugViews: evaluateToStringInDebugViews,
// When running out of process, VM Service traffic won't be available
// to the client-side logger, so force logging on which sends VM Service
// traffic in a custom event.
sendLogsToClient: captureVmServiceTraffic,
// to the client-side logger, so force logging regardless of
// `sendLogsToClient` which sends VM Service traffic in a custom event.
sendLogsToClient: sendLogsToClient || captureVmServiceTraffic,
),
// We can't automatically pick the command when using a custom type
// (FlutterLaunchRequestArguments).
Expand Down

0 comments on commit 09bd0f6

Please sign in to comment.