Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

trace the debug adapter protocol messages #1023

Merged
merged 1 commit into from Oct 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -7,6 +7,7 @@ import scala.concurrent.Promise
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.meta.internal.metals.Cancelable
import scala.meta.internal.metals.GlobalTrace
import scala.meta.internal.metals.debug.DebugProtocol.OutputNotification
import scala.meta.internal.metals.debug.DebugProtocol.RestartRequest
import scala.meta.internal.metals.debug.DebugProxy._
Expand Down Expand Up @@ -81,8 +82,19 @@ private[debug] object DebugProxy {
connectToServer: () => Future[Socket]
)(implicit ec: ExecutionContext): Future[DebugProxy] = {
for {
server <- connectToServer().map(new RemoteEndpoint(_))
client <- awaitClient().map(new RemoteEndpoint(_))
server <- connectToServer()
.map(new SocketEndpoint(_))
.map(endpoint => withLogger(endpoint, "dap-server"))
client <- awaitClient()
.map(new SocketEndpoint(_))
.map(endpoint => withLogger(endpoint, "dap-client"))
} yield new DebugProxy(name, client, server)
}

private def withLogger(
endpoint: RemoteEndpoint,
name: String
): RemoteEndpoint = {
new EndpointLogger(endpoint, GlobalTrace.setup(name))
}
}
@@ -0,0 +1,72 @@
package scala.meta.internal.metals.debug

import java.io.PrintWriter
import java.time.Clock
import java.time.format.DateTimeFormatter
import java.util.Collections
import java.util.function.Consumer
import com.google.gson.GsonBuilder
import org.eclipse.lsp4j.jsonrpc.MessageConsumer
import org.eclipse.lsp4j.jsonrpc.debug.json.DebugMessageJsonHandler
import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler
import org.eclipse.lsp4j.jsonrpc.messages.Message
import org.eclipse.lsp4j.jsonrpc.messages.NotificationMessage
import org.eclipse.lsp4j.jsonrpc.messages.RequestMessage
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage
import scala.meta.internal.metals.debug.EndpointLogger.Direction
import scala.meta.internal.metals.debug.EndpointLogger.Received
import scala.meta.internal.metals.debug.EndpointLogger.Sent
import scala.meta.internal.metals.debug.EndpointLogger.time

final class EndpointLogger(endpoint: RemoteEndpoint, logger: PrintWriter)
extends RemoteEndpoint {
private val writer: MessageJsonHandler = EndpointLogger.serializer

override def consume(message: Message): Unit = {
try log(Sent, message)
finally endpoint.consume(message)
}

override def listen(messageConsumer: MessageConsumer): Unit = {
endpoint.listen { message =>
try log(Received, message)
finally messageConsumer.consume(message)
}
}

override def cancel(): Unit = endpoint.cancel()

private def log(direction: Direction, message: Message): Unit = {
logger.println(s"[Trace][$time] $direction ${typeOf(message)}:")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it work with the inspector tool? It would be nice to keep it compatible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, no - the format is too much of a hassle to implement it manually. Reusing the implementation present in the lsp4j.jsonrpc library is also not viable because it is tightly coupled with their launcher implementation.

At one point we might want to change the format to accomodate the inspector but let's delay it for now as it is better to have some logs than none at all.

writer.serialize(message, logger)
logger.println()
logger.flush()
}

private def typeOf(message: Message): String = {
message match {
case _: RequestMessage => "request"
case _: ResponseMessage => "response"
case _: NotificationMessage => "notification"
}
}
}

object EndpointLogger {
sealed trait Direction
final case object Received extends Direction
final case object Sent extends Direction

private def serializer: MessageJsonHandler = {
val configure: Consumer[GsonBuilder] = { gson =>
gson.setPrettyPrinting()
}
new DebugMessageJsonHandler(Collections.emptyMap(), configure)
}

private val clock = Clock.systemDefaultZone()
private val timeFormat =
DateTimeFormatter.ofPattern("KK:mm:ss a").withZone(clock.getZone)

def time: String = timeFormat.format(clock.instant())
}
@@ -1,45 +1,10 @@
package scala.meta.internal.metals.debug

import java.net.Socket
import java.util.Collections
import org.eclipse.lsp4j.jsonrpc.MessageConsumer
import org.eclipse.lsp4j.jsonrpc.MessageProducer
import org.eclipse.lsp4j.jsonrpc.debug.json.DebugMessageJsonHandler
import org.eclipse.lsp4j.jsonrpc.json.StreamMessageConsumer
import org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer
import org.eclipse.lsp4j.jsonrpc.messages.Message
import scala.meta.internal.metals.Cancelable
import scala.meta.internal.metals.debug.RemoteEndpoint._

private[debug] final class RemoteEndpoint(socket: Socket)
trait RemoteEndpoint
extends MessageConsumer
with MessageProducer
with Cancelable {
private val source = messageSource(socket)
private val target = messageTarget(socket)

override def consume(message: Message): Unit = {
target.consume(message)
}

override def listen(consumer: MessageConsumer): Unit = {
source.listen(consumer)
}

override def cancel(): Unit = {
source.close()
socket.close()
}
}

private[debug] object RemoteEndpoint {
private val handler = new DebugMessageJsonHandler(Collections.emptyMap())

private def messageSource(socket: Socket): StreamMessageProducer = {
new StreamMessageProducer(socket.getInputStream, handler)
}

private def messageTarget(socket: Socket): MessageConsumer = {
new StreamMessageConsumer(socket.getOutputStream, handler)
}
}
with Cancelable
@@ -0,0 +1,41 @@
package scala.meta.internal.metals.debug

import java.net.Socket
import java.util.Collections
import org.eclipse.lsp4j.jsonrpc.MessageConsumer
import org.eclipse.lsp4j.jsonrpc.debug.json.DebugMessageJsonHandler
import org.eclipse.lsp4j.jsonrpc.json.StreamMessageConsumer
import org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer
import org.eclipse.lsp4j.jsonrpc.messages.Message
import scala.meta.internal.metals.debug.SocketEndpoint._

private[debug] final class SocketEndpoint(socket: Socket)
extends RemoteEndpoint {
private val source = messageSource(socket)
private val target = messageTarget(socket)

override def consume(message: Message): Unit = {
target.consume(message)
}

override def listen(consumer: MessageConsumer): Unit = {
source.listen(consumer)
}

override def cancel(): Unit = {
source.close()
socket.close()
}
}

private[debug] object SocketEndpoint {
private val handler = new DebugMessageJsonHandler(Collections.emptyMap())

private def messageSource(socket: Socket): StreamMessageProducer = {
new StreamMessageProducer(socket.getInputStream, handler)
}

private def messageTarget(socket: Socket): MessageConsumer = {
new StreamMessageConsumer(socket.getOutputStream, handler)
}
}
Expand Up @@ -32,7 +32,7 @@ private[debug] final class RemoteServer(
extends IDebugProtocolServer
with Cancelable {

private val remote = new RemoteEndpoint(socket)
private val remote = new SocketEndpoint(socket)
private val ongoing = new TrieMap[String, Response => Unit]()
private val id = new AtomicInteger(0)
lazy val listening: Future[Unit] = Future(listen())
Expand Down