[dds] Support logging VM Service traffic to the client from DAP

Change-Id: Id9f6b2158dcf1603468b989ed2f900b390b9b259
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209546
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index e61c8c4..27ad157 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -176,6 +176,8 @@
   /// VM Service closing).
   bool _hasSentTerminatedEvent = false;
 
+  late final sendLogsToClient = args.sendLogsToClient ?? false;
+
   DartDebugAdapter(
     ByteStreamServerChannel channel, {
     this.ipv6 = false,
@@ -305,8 +307,7 @@
 
     logger?.call('Connecting to debugger at $uri');
     sendOutput('console', 'Connecting to VM Service at $uri\n');
-    final vmService =
-        await _vmServiceConnectUri(uri.toString(), logger: logger);
+    final vmService = await _vmServiceConnectUri(uri.toString());
     logger?.call('Connected to debugger at $uri!');
 
     // TODO(dantup): VS Code currently depends on a custom dart.debuggerUris
@@ -1313,6 +1314,13 @@
     }
   }
 
+  void _logTraffic(String data) {
+    logger?.call(data);
+    if (sendLogsToClient) {
+      sendEvent(RawEventBody(data), eventType: 'dart.log');
+    }
+  }
+
   /// Performs some setup that is common to both [launchRequest] and
   /// [attachRequest].
   Future<void> _prepareForLaunchOrAttach() async {
@@ -1352,17 +1360,15 @@
 
   /// A wrapper around the same name function from package:vm_service that
   /// allows logging all traffic over the VM Service.
-  Future<vm.VmService> _vmServiceConnectUri(
-    String wsUri, {
-    Logger? logger,
-  }) async {
+  Future<vm.VmService> _vmServiceConnectUri(String wsUri) async {
     final socket = await WebSocket.connect(wsUri);
     final controller = StreamController();
     final streamClosedCompleter = Completer();
+    final logger = this.logger;
 
     socket.listen(
       (data) {
-        logger?.call('<== [VM] $data');
+        _logTraffic('<== [VM] $data');
         controller.add(data);
       },
       onDone: () => streamClosedCompleter.complete(),
@@ -1372,6 +1378,7 @@
       controller.stream,
       (String message) {
         logger?.call('==> [VM] $message');
+        _logTraffic('==> [VM] $message');
         socket.add(message);
       },
       log: logger != null ? VmServiceLogger(logger) : null,
diff --git a/pkg/dds/lib/src/dap/base_debug_adapter.dart b/pkg/dds/lib/src/dap/base_debug_adapter.dart
index 6b15fac..1071fee 100644
--- a/pkg/dds/lib/src/dap/base_debug_adapter.dart
+++ b/pkg/dds/lib/src/dap/base_debug_adapter.dart
@@ -167,10 +167,10 @@
 
   /// Sends an event, lookup up the event type based on the runtimeType of
   /// [body].
-  void sendEvent(EventBody body) {
+  void sendEvent(EventBody body, {String? eventType}) {
     final event = Event(
       seq: _sequence++,
-      event: eventTypes[body.runtimeType]!,
+      event: eventType ?? eventTypes[body.runtimeType]!,
       body: body,
     );
     _channel.sendEvent(event);
diff --git a/pkg/dds/lib/src/dap/protocol_common.dart b/pkg/dds/lib/src/dap/protocol_common.dart
index f9c09f4..9a20311 100644
--- a/pkg/dds/lib/src/dap/protocol_common.dart
+++ b/pkg/dds/lib/src/dap/protocol_common.dart
@@ -2,12 +2,35 @@
 // for details. 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';
+
 /// A base class for (spec-generated) classes that represent the `body` of a an
 /// event.
 abstract class EventBody {
   static bool canParse(Object? obj) => obj is Map<String, Object?>?;
 }
 
+/// A generic event body class that just supplies an object directly.
+///
+/// Used to support custom events sent by the debug adapter such as 'dart.log'.
+///
+/// The supplied [body] must be convertable to JSON.
+class RawEventBody extends EventBody {
+  final Object body;
+
+  RawEventBody(this.body)
+      : assert(() {
+          try {
+            jsonEncode(body);
+            return true;
+          } catch (e) {
+            return false;
+          }
+        }(), 'body should be JSON encodable');
+
+  Object toJson() => body;
+}
+
 /// A generic arguments class that just supplies the arguments map directly.
 ///
 /// Used to support custom requests that may be provided by other implementing
diff --git a/pkg/dds/test/dap/integration/test_server.dart b/pkg/dds/test/dap/integration/test_server.dart
index ffaabf2..3e4cb72 100644
--- a/pkg/dds/test/dap/integration/test_server.dart
+++ b/pkg/dds/test/dap/integration/test_server.dart
@@ -53,7 +53,6 @@
   }) async {
     return InProcessDapTestServer._([
       ...?additionalArgs,
-      if (logger != null) '--verbose',
     ]);
   }
 }
@@ -107,12 +106,7 @@
 
     final _process = await Process.start(
       Platform.resolvedExecutable,
-      [
-        dapServerScript,
-        'dap',
-        ...?additionalArgs,
-        if (logger != null) '--verbose'
-      ],
+      [dapServerScript, 'dap', ...?additionalArgs],
     );
 
     return OutOfProcessDapTestServer._(_process, logger);
diff --git a/pkg/dds/test/dap/integration/test_support.dart b/pkg/dds/test/dap/integration/test_support.dart
index 4f8fac1..9fd5a88 100644
--- a/pkg/dds/test/dap/integration/test_support.dart
+++ b/pkg/dds/test/dap/integration/test_support.dart
@@ -23,6 +23,13 @@
 /// simplified in VS Code by using a launch config with custom CodeLens links).
 final useInProcessDap = Platform.environment['DAP_TEST_INTERNAL'] == 'true';
 
+/// Whether to print all protocol traffic to stdout while running tests.
+///
+/// This is useful for debugging locally or on the bots and will include both
+/// DAP traffic (between the test DAP client and the DAP server) and the VM
+/// Service traffic (wrapped in a custom 'dart.log' event).
+final verboseLogging = Platform.environment['DAP_TEST_VERBOSE'] == 'true';
+
 /// A [RegExp] that matches the `path` part of a VM Service URI that contains
 /// an authentication token.
 final vmServiceAuthCodePathPattern = RegExp(r'^/[\w_\-=]{5,15}/ws$');
@@ -141,8 +148,11 @@
 
   static Future<DapTestSession> setUp({List<String>? additionalArgs}) async {
     final server = await _startServer(additionalArgs: additionalArgs);
-    final client =
-        await DapTestClient.connect(server, captureVmServiceTraffic: true);
+    final client = await DapTestClient.connect(
+      server,
+      captureVmServiceTraffic: verboseLogging,
+      logger: verboseLogging ? print : null,
+    );
     return DapTestSession._(server, client);
   }
 
diff --git a/pkg/dds/tool/dap/run_server.dart b/pkg/dds/tool/dap/run_server.dart
index ba22d72..0178e85 100644
--- a/pkg/dds/tool/dap/run_server.dart
+++ b/pkg/dds/tool/dap/run_server.dart
@@ -26,7 +26,6 @@
   static const argIpv6 = 'ipv6';
   static const argDds = 'dds';
   static const argAuthCodes = 'auth-codes';
-  static const argVerbose = 'verbose';
 
   final Stream<List<int>> _inputStream;
   final StreamSink<List<int>> _outputSink;