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

Implement logging for debug adapter clients #45

Merged
merged 9 commits into from
Oct 21, 2024
28 changes: 27 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ members = [
#

"tooling/xtask",
"crates/debugger_tools",
]
default-members = ["crates/zed"]

Expand Down Expand Up @@ -209,6 +210,7 @@ dap = { path = "crates/dap" }
dap_adapters = { path = "crates/dap_adapters" }
db = { path = "crates/db" }
debugger_ui = { path = "crates/debugger_ui" }
debugger_tools = { path = "crates/debugger_tools" }
dev_server_projects = { path = "crates/dev_server_projects" }
diagnostics = { path = "crates/diagnostics" }
editor = { path = "crates/editor" }
Expand Down
38 changes: 27 additions & 11 deletions crates/dap/src/adapters.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::client::TransportParams;
use ::fs::Fs;
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
Expand Down Expand Up @@ -26,6 +25,8 @@ use std::{

use task::{DebugAdapterConfig, TCPHost};

use crate::client::{AdapterLogIo, TransportParams};

/// Get an open port to use with the tcp client when not supplied by debug config
async fn get_open_port(host: Ipv4Addr) -> Option<u16> {
Some(
Expand Down Expand Up @@ -53,7 +54,7 @@ pub async fn create_tcp_client(
host: TCPHost,
adapter_binary: &DebugAdapterBinary,
cx: &mut AsyncAppContext,
) -> Result<TransportParams> {
) -> Result<(TransportParams, AdapterLogIo)> {
let host_address = host.host.unwrap_or_else(|| Ipv4Addr::new(127, 0, 0, 1));

let mut port = host.port;
Expand All @@ -73,14 +74,23 @@ pub async fn create_tcp_client(

command
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.kill_on_drop(true);

let process = command
let mut process = command
.spawn()
.with_context(|| "failed to start debug adapter.")?;

let log_stdout = process
.stdout
.take()
.ok_or_else(|| anyhow!("Failed to open stdout"))?;
let log_stderr = process
.stderr
.take()
.ok_or_else(|| anyhow!("Failed to open stderr"))?;

if let Some(delay) = host.delay {
// some debug adapters need some time to start the TCP server
// so we have to wait few milliseconds before we can connect to it
Expand All @@ -97,11 +107,17 @@ pub async fn create_tcp_client(
let (rx, tx) = TcpStream::connect(address).await?.split();
log::info!("Debug adapter has connected to tcp server");

Ok(TransportParams::new(
Box::new(BufReader::new(rx)),
Box::new(tx),
None,
Some(process),
Ok((
TransportParams::new(
Box::new(BufReader::new(rx)),
Box::new(tx),
None,
Some(process),
),
AdapterLogIo::new(
Box::new(BufReader::new(log_stdout)),
Box::new(BufReader::new(log_stderr)),
),
))
}

Expand Down Expand Up @@ -186,7 +202,7 @@ pub trait DebugAdapter: 'static + Send + Sync {
&self,
adapter_binary: &DebugAdapterBinary,
cx: &mut AsyncAppContext,
) -> anyhow::Result<TransportParams>;
) -> anyhow::Result<(TransportParams, Option<AdapterLogIo>)>;

/// Installs the binary for the debug adapter.
/// This method is called when the adapter binary is not found or needs to be updated.
Expand Down
94 changes: 91 additions & 3 deletions crates/dap/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::transport::Transport;
pub use crate::transport::IoKind;
use crate::transport::{IoHandler, Transport};
use anyhow::{anyhow, Result};

use dap_types::{
messages::{Message, Response},
requests::Request,
};
use futures::{AsyncBufRead, AsyncWrite};
use futures::{AsyncBufRead, AsyncBufReadExt, AsyncWrite};
use gpui::{AppContext, AsyncAppContext};
use parking_lot::Mutex;
use serde_json::Value;
Expand Down Expand Up @@ -42,6 +44,8 @@ pub struct DebugAdapterClient {
_process: Arc<Mutex<Option<Child>>>,
sequence_count: AtomicU64,
config: DebugAdapterConfig,
io_handlers: Arc<Mutex<Vec<IoHandler>>>,
log_io_handlers: Option<Arc<Mutex<Vec<IoHandler>>>>,
RemcoSmitsDev marked this conversation as resolved.
Show resolved Hide resolved
}

pub struct TransportParams {
Expand All @@ -67,24 +71,51 @@ impl TransportParams {
}
}

pub struct AdapterLogIo {
out: Box<dyn AsyncBufRead + Unpin + Send>,
err: Box<dyn AsyncBufRead + Unpin + Send>,
}

impl AdapterLogIo {
pub fn new(
out: Box<dyn AsyncBufRead + Unpin + Send>,
err: Box<dyn AsyncBufRead + Unpin + Send>,
) -> Self {
Self { out, err }
}
}

impl DebugAdapterClient {
pub async fn new<F>(
id: DebugAdapterClientId,
adapter_id: String,
request_args: Value,
config: DebugAdapterConfig,
transport_params: TransportParams,
adapter_log_io: Option<AdapterLogIo>,
event_handler: F,
cx: &mut AsyncAppContext,
) -> Result<Arc<Self>>
where
F: FnMut(Message, &mut AppContext) + 'static + Send + Sync + Clone,
{
let io_handlers = Arc::new(Mutex::new(Vec::new()));
let log_io_handlers = match adapter_log_io {
Some(AdapterLogIo { out, err }) => {
let log_io_handlers = Arc::new(Mutex::new(Vec::new()));

Self::handle_adapter_logs(out, err, log_io_handlers.clone(), cx);
Some(log_io_handlers)
}
None => None,
};

let transport = Self::handle_transport(
transport_params.rx,
transport_params.tx,
transport_params.err,
event_handler,
io_handlers.clone(),
cx,
);
Ok(Arc::new(Self {
Expand All @@ -93,6 +124,8 @@ impl DebugAdapterClient {
request_args,
config,
transport,
io_handlers,
log_io_handlers,
sequence_count: AtomicU64::new(1),
_process: Arc::new(Mutex::new(transport_params.process)),
}))
Expand All @@ -103,12 +136,13 @@ impl DebugAdapterClient {
tx: Box<dyn AsyncWrite + Unpin + Send>,
err: Option<Box<dyn AsyncBufRead + Unpin + Send>>,
event_handler: F,
io_handlers: Arc<Mutex<Vec<IoHandler>>>,
cx: &mut AsyncAppContext,
) -> Arc<Transport>
where
F: FnMut(Message, &mut AppContext) + 'static + Send + Sync + Clone,
{
let transport = Transport::start(rx, tx, err, cx);
let transport = Transport::start(rx, tx, err, io_handlers, cx);

let server_rx = transport.server_rx.clone();
let server_tr = transport.server_tx.clone();
Expand All @@ -120,6 +154,38 @@ impl DebugAdapterClient {
transport
}

fn handle_adapter_logs(
out: Box<dyn AsyncBufRead + Unpin + Send>,
err: Box<dyn AsyncBufRead + Unpin + Send>,
log_io_handlers: Arc<Mutex<Vec<IoHandler>>>,
cx: &mut AsyncAppContext,
) {
{
let mut out = out.lines();
let log_io_handlers = log_io_handlers.clone();
cx.spawn(|_| async move {
use futures::stream::StreamExt;
while let Some(Ok(line)) = out.next().await {
for handler in log_io_handlers.lock().iter_mut() {
handler(IoKind::StdOut, line.as_ref());
}
}
})
.detach();
}

let mut err = err.lines();
cx.spawn(|_| async move {
use futures::stream::StreamExt;
while let Some(Ok(line)) = err.next().await {
for handler in log_io_handlers.lock().iter_mut() {
handler(IoKind::StdErr, line.as_ref());
}
}
})
.detach();
}

async fn handle_recv<F>(
server_rx: Receiver<Message>,
client_tx: Sender<Message>,
Expand Down Expand Up @@ -183,6 +249,10 @@ impl DebugAdapterClient {
.map_err(|e| anyhow::anyhow!("Failed to send response back: {}", e))
}

pub fn has_adapter_logs(&self) -> bool {
self.log_io_handlers.is_some()
}

pub fn id(&self) -> DebugAdapterClientId {
self.id
}
Expand Down Expand Up @@ -233,4 +303,22 @@ impl DebugAdapterClient {
}
.await
}

pub fn on_io<F>(&self, f: F)
where
F: 'static + Send + FnMut(IoKind, &str),
{
let mut io_handlers = self.io_handlers.lock();
io_handlers.push(Box::new(f));
}

pub fn on_log_io<F>(&self, f: F)
where
F: 'static + Send + FnMut(IoKind, &str),
{
if let Some(ref io_log_handlers) = self.log_io_handlers {
let mut io_log_handlers = io_log_handlers.lock();
io_log_handlers.push(Box::new(f));
}
}
}
Loading