Use $/logTrace for server trace logs in Zed and VS Code (#12564)

## Summary

This pull request adds support for logging via `$/logTrace` RPC
messages. It also enables that code path for when a client is Zed editor
or VS Code (as there's no way for us to generically tell whether a client prefers
`$/logTrace` over stderr.

Related to: #12523

## Test Plan

I've built Ruff from this branch and tested it manually with Zed.

---------

Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
This commit is contained in:
Piotr Osiewicz 2024-07-30 05:02:20 +02:00 committed by GitHub
parent 381bd1ff4a
commit fb9f566f56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 55 additions and 6 deletions

View file

@ -90,6 +90,7 @@ impl Server {
.log_level .log_level
.unwrap_or(crate::trace::LogLevel::Info), .unwrap_or(crate::trace::LogLevel::Info),
global_settings.tracing.log_file.as_deref(), global_settings.tracing.log_file.as_deref(),
init_params.client_info.as_ref(),
); );
let mut workspace_for_url = |url: lsp_types::Url| { let mut workspace_for_url = |url: lsp_types::Url| {

View file

@ -14,16 +14,22 @@
//! //!
//! Tracing will write to `stderr` by default, which should appear in the logs for most LSP clients. //! Tracing will write to `stderr` by default, which should appear in the logs for most LSP clients.
//! A `logFile` path can also be specified in the settings, and output will be directed there instead. //! A `logFile` path can also be specified in the settings, and output will be directed there instead.
use lsp_types::TraceValue; use core::str;
use lsp_server::{Message, Notification};
use lsp_types::{
notification::{LogTrace, Notification as _},
ClientInfo, TraceValue,
};
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
io::{Error as IoError, ErrorKind, Write},
path::PathBuf, path::PathBuf,
str::FromStr, str::FromStr,
sync::{Arc, Mutex, OnceLock}, sync::{Arc, Mutex, OnceLock},
}; };
use tracing::level_filters::LevelFilter; use tracing::level_filters::LevelFilter;
use tracing_subscriber::{ use tracing_subscriber::{
fmt::{time::Uptime, writer::BoxMakeWriter}, fmt::{time::Uptime, writer::BoxMakeWriter, MakeWriter},
layer::SubscriberExt, layer::SubscriberExt,
Layer, Layer,
}; };
@ -43,10 +49,43 @@ pub(crate) fn set_trace_value(trace_value: TraceValue) {
*global_trace_value = trace_value; *global_trace_value = trace_value;
} }
// A tracing writer that uses LSPs logTrace method.
struct TraceLogWriter;
impl Write for TraceLogWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let message = str::from_utf8(buf).map_err(|e| IoError::new(ErrorKind::InvalidData, e))?;
LOGGING_SENDER
.get()
.expect("logging sender should be initialized at this point")
.send(Message::Notification(Notification {
method: LogTrace::METHOD.to_owned(),
params: serde_json::json!({
"message": message
}),
}))
.map_err(|e| IoError::new(ErrorKind::Other, e))?;
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<'a> MakeWriter<'a> for TraceLogWriter {
type Writer = Self;
fn make_writer(&'a self) -> Self::Writer {
Self
}
}
pub(crate) fn init_tracing( pub(crate) fn init_tracing(
sender: ClientSender, sender: ClientSender,
log_level: LogLevel, log_level: LogLevel,
log_file: Option<&std::path::Path>, log_file: Option<&std::path::Path>,
client: Option<&ClientInfo>,
) { ) {
LOGGING_SENDER LOGGING_SENDER
.set(sender) .set(sender)
@ -82,15 +121,24 @@ pub(crate) fn init_tracing(
.ok() .ok()
}); });
let logger = match log_file {
Some(file) => BoxMakeWriter::new(Arc::new(file)),
None => {
if client.is_some_and(|client| {
client.name.starts_with("Zed") || client.name.starts_with("Visual Studio Code")
}) {
BoxMakeWriter::new(TraceLogWriter)
} else {
BoxMakeWriter::new(std::io::stderr)
}
}
};
let subscriber = tracing_subscriber::Registry::default().with( let subscriber = tracing_subscriber::Registry::default().with(
tracing_subscriber::fmt::layer() tracing_subscriber::fmt::layer()
.with_timer(Uptime::default()) .with_timer(Uptime::default())
.with_thread_names(true) .with_thread_names(true)
.with_ansi(false) .with_ansi(false)
.with_writer(match log_file { .with_writer(logger)
Some(file) => BoxMakeWriter::new(Arc::new(file)),
None => BoxMakeWriter::new(std::io::stderr),
})
.with_filter(TraceLevelFilter) .with_filter(TraceLevelFilter)
.with_filter(LogLevelFilter { filter: log_level }), .with_filter(LogLevelFilter { filter: log_level }),
); );