From 123d9ea047a2e10803e260ebf00f31fcc98463ee Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Fri, 8 Dec 2023 17:04:56 +0000 Subject: [PATCH] feat(lsp): debug log file (#21500) --- cli/lsp/config.rs | 10 +++++ cli/lsp/language_server.rs | 2 + cli/lsp/logging.rs | 88 ++++++++++++++++++++++++++++++++++++-- cli/lsp/repl.rs | 1 + 4 files changed, 97 insertions(+), 4 deletions(-) diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 36d4c95c68..42478b5932 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -454,6 +454,10 @@ pub struct WorkspaceSettings { #[serde(default)] pub internal_debug: bool, + /// Write logs to a file in a project-local directory. + #[serde(default)] + pub log_file: bool, + /// A flag that indicates if linting is enabled for the workspace. #[serde(default = "default_to_true")] pub lint: bool, @@ -502,6 +506,7 @@ impl Default for WorkspaceSettings { import_map: None, code_lens: Default::default(), internal_debug: false, + log_file: false, lint: true, document_preload_limit: default_document_preload_limit(), suggest: Default::default(), @@ -1071,6 +1076,10 @@ impl Config { paths } + pub fn log_file(&self) -> bool { + self.settings.unscoped.log_file + } + pub fn update_capabilities( &mut self, capabilities: &lsp::ClientCapabilities, @@ -1321,6 +1330,7 @@ mod tests { test: true, }, internal_debug: false, + log_file: false, lint: true, document_preload_limit: 1_000, suggest: DenoCompletionSettings { diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index a893ab3a83..225b46eacd 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -102,6 +102,7 @@ use crate::factory::CliFactory; use crate::file_fetcher::FileFetcher; use crate::graph_util; use crate::http_util::HttpClient; +use crate::lsp::logging::init_log_file; use crate::lsp::tsc::file_text_changes_to_workspace_edit; use crate::lsp::urls::LspUrlKind; use crate::npm::create_cli_npm_resolver_for_lsp; @@ -3242,6 +3243,7 @@ impl tower_lsp::LanguageServer for LanguageServer { { let mut ls = self.0.write().await; + init_log_file(ls.config.log_file()); if let Err(err) = ls.update_tsconfig().await { ls.client.show_message(MessageType::WARNING, err); } diff --git a/cli/lsp/logging.rs b/cli/lsp/logging.rs index b8c8c99e60..39a53f2b87 100644 --- a/cli/lsp/logging.rs +++ b/cli/lsp/logging.rs @@ -1,13 +1,85 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use chrono::DateTime; +use chrono::Utc; +use deno_core::parking_lot::Mutex; +use std::fs; +use std::io::prelude::*; +use std::path::Path; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; +use std::thread; +use std::time::SystemTime; static LSP_DEBUG_FLAG: AtomicBool = AtomicBool::new(false); static LSP_LOG_LEVEL: AtomicUsize = AtomicUsize::new(log::Level::Info as usize); static LSP_WARN_LEVEL: AtomicUsize = AtomicUsize::new(log::Level::Warn as usize); +static LOG_FILE: LogFile = LogFile { + enabled: AtomicBool::new(true), + buffer: Mutex::new(String::new()), +}; + +pub struct LogFile { + enabled: AtomicBool, + buffer: Mutex, +} + +impl LogFile { + pub fn write_line(&self, s: &str) { + if LOG_FILE.enabled.load(Ordering::Relaxed) { + let mut buffer = self.buffer.lock(); + buffer.push_str(s); + buffer.push('\n'); + } + } + + fn commit(&self, path: &Path) { + let unbuffered = { + let mut buffer = self.buffer.lock(); + if buffer.is_empty() { + return; + } + // We clone here rather than take so the buffer can retain its capacity. + let unbuffered = buffer.clone(); + buffer.clear(); + unbuffered + }; + if let Ok(file) = fs::OpenOptions::new().append(true).open(path) { + write!(&file, "{}", unbuffered).ok(); + } + } +} + +pub fn init_log_file(enabled: bool) { + let prepare_path = || { + if !enabled { + return None; + } + let cwd = std::env::current_dir().ok()?; + let now = SystemTime::now(); + let now: DateTime = now.into(); + let now = now.to_rfc3339().replace(':', "_"); + let path = cwd.join(format!(".deno_lsp/log_{}.txt", now)); + fs::create_dir_all(path.parent()?).ok()?; + fs::write(&path, "").ok()?; + Some(path) + }; + let Some(path) = prepare_path() else { + LOG_FILE.enabled.store(false, Ordering::Relaxed); + LOG_FILE.buffer.lock().clear(); + return; + }; + thread::spawn(move || loop { + LOG_FILE.commit(&path); + thread::sleep(std::time::Duration::from_secs(1)); + }); +} + +pub fn write_line_to_log_file(s: &str) { + LOG_FILE.write_line(s); +} pub fn set_lsp_debug_flag(value: bool) { LSP_DEBUG_FLAG.store(value, Ordering::SeqCst) @@ -53,7 +125,9 @@ macro_rules! lsp_log { if lsp_log_level == log::Level::Debug { $crate::lsp::logging::lsp_debug!($($arg)+) } else { - log::log!(lsp_log_level, $($arg)+) + let s = std::format!($($arg)+); + $crate::lsp::logging::write_line_to_log_file(&s); + log::log!(lsp_log_level, "{}", s) } ) } @@ -67,7 +141,9 @@ macro_rules! lsp_warn { if lsp_log_level == log::Level::Debug { $crate::lsp::logging::lsp_debug!($($arg)+) } else { - log::log!(lsp_log_level, $($arg)+) + let s = std::format!($($arg)+); + $crate::lsp::logging::write_line_to_log_file(&s); + log::log!(lsp_log_level, "{}", s) } } ) @@ -75,8 +151,12 @@ macro_rules! lsp_warn { macro_rules! lsp_debug { ($($arg:tt)+) => ( - if crate::lsp::logging::lsp_debug_enabled() { - log::debug!($($arg)+) + { + let s = std::format!($($arg)+); + $crate::lsp::logging::write_line_to_log_file(&s); + if $crate::lsp::logging::lsp_debug_enabled() { + log::debug!("{}", s) + } } ) } diff --git a/cli/lsp/repl.rs b/cli/lsp/repl.rs index 2f023197d1..04905e3bd6 100644 --- a/cli/lsp/repl.rs +++ b/cli/lsp/repl.rs @@ -300,6 +300,7 @@ pub fn get_repl_workspace_settings() -> WorkspaceSettings { import_map: None, code_lens: Default::default(), internal_debug: false, + log_file: false, lint: false, document_preload_limit: 0, // don't pre-load any modules as it's expensive and not useful for the repl tls_certificate: None,