diff --git a/Cargo.toml b/Cargo.toml index c61992f..84d0a8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,10 +23,13 @@ salsa = "0.23.0" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" tempfile = "3.20.0" +thiserror = "2.0.12" tokio = { version = "1.45.0", features = ["full"] } toml = "0.9.2" tower-lsp-server = { version = "0.22.0", features = ["proposed"] } -thiserror = "2.0.12" +tracing = "0.1.41" +tracing-appender = "0.2.3" +tracing-subscriber = { version = "0.3.19", features = ["env-filter", "fmt", "time"] } which = "8.0.0" [workspace.lints.clippy] diff --git a/crates/djls-server/Cargo.toml b/crates/djls-server/Cargo.toml index ad98c5b..7680f44 100644 --- a/crates/djls-server/Cargo.toml +++ b/crates/djls-server/Cargo.toml @@ -20,6 +20,7 @@ serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } tower-lsp-server = { workspace = true } +tracing = { workspace = true } [build-dependencies] djls-dev = { workspace = true } diff --git a/crates/djls-server/src/lib.rs b/crates/djls-server/src/lib.rs index 584de60..0f39072 100644 --- a/crates/djls-server/src/lib.rs +++ b/crates/djls-server/src/lib.rs @@ -1,6 +1,7 @@ mod client; mod db; mod documents; +mod logging; mod queue; mod server; mod session; diff --git a/crates/djls-server/src/logging.rs b/crates/djls-server/src/logging.rs new file mode 100644 index 0000000..ba16d94 --- /dev/null +++ b/crates/djls-server/src/logging.rs @@ -0,0 +1,64 @@ +//! Temporary logging macros for dual-dispatch to both LSP client and tracing. +//! +//! These macros bridge the gap during our migration from `client::log_message` +//! to the tracing infrastructure. They ensure messages are sent to both systems +//! so we maintain LSP client visibility while building out tracing support. +//! +//! Each macro supports two invocation patterns to handle the different APIs: +//! +//! 1. String literal: +//! ```rust,ignore +//! log_info!("Server initialized"); +//! log_warn!("Configuration not found"); +//! log_error!("Failed to parse document"); +//! ``` +//! +//! 2. Format string with arguments: +//! ```rust,ignore +//! log_info!("Processing {} documents", count); +//! log_warn!("Timeout after {}ms for {}", ms, path); +//! log_error!("Failed to open {}: {}", file, err); +//! ``` +//! +//! The difference in the macro arms exists because of how each system works: +//! +//! - `client::log_message` expects a single string value +//! - `tracing` macros can handle format strings natively for structured logging +//! - For format strings, we format once for the client but pass the original +//! format string and args to tracing to preserve structured data + +#[macro_export] +macro_rules! log_info { + ($msg:literal) => { + $crate::client::log_message(tower_lsp_server::lsp_types::MessageType::INFO, $msg); + tracing::info!($msg); + }; + ($fmt:literal, $($arg:tt)*) => { + $crate::client::log_message(tower_lsp_server::lsp_types::MessageType::INFO, format!($fmt, $($arg)*)); + tracing::info!($fmt, $($arg)*); + }; +} + +#[macro_export] +macro_rules! log_warn { + ($msg:literal) => { + $crate::client::log_message(tower_lsp_server::lsp_types::MessageType::WARNING, $msg); + tracing::warn!($msg); + }; + ($fmt:literal, $($arg:tt)*) => { + $crate::client::log_message(tower_lsp_server::lsp_types::MessageType::WARNING, format!($fmt, $($arg)*)); + tracing::warn!($fmt, $($arg)*); + }; +} + +#[macro_export] +macro_rules! log_error { + ($msg:literal) => { + $crate::client::log_message(tower_lsp_server::lsp_types::MessageType::ERROR, $msg); + tracing::error!($msg); + }; + ($fmt:literal, $($arg:tt)*) => { + $crate::client::log_message(tower_lsp_server::lsp_types::MessageType::ERROR, format!($fmt, $($arg)*)); + tracing::error!($fmt, $($arg)*); + }; +} diff --git a/crates/djls-server/src/server.rs b/crates/djls-server/src/server.rs index 07fe300..7a106ca 100644 --- a/crates/djls-server/src/server.rs +++ b/crates/djls-server/src/server.rs @@ -13,7 +13,6 @@ use tower_lsp_server::lsp_types::DidOpenTextDocumentParams; use tower_lsp_server::lsp_types::InitializeParams; use tower_lsp_server::lsp_types::InitializeResult; use tower_lsp_server::lsp_types::InitializedParams; -use tower_lsp_server::lsp_types::MessageType; use tower_lsp_server::lsp_types::OneOf; use tower_lsp_server::lsp_types::SaveOptions; use tower_lsp_server::lsp_types::ServerCapabilities; @@ -25,7 +24,8 @@ use tower_lsp_server::lsp_types::WorkspaceFoldersServerCapabilities; use tower_lsp_server::lsp_types::WorkspaceServerCapabilities; use tower_lsp_server::LanguageServer; -use crate::client; +use crate::log_error; +use crate::log_info; use crate::queue::Queue; use crate::session::Session; @@ -55,10 +55,7 @@ impl DjangoLanguageServer { if let Some(s) = &*session { f(s) } else { - client::log_message( - MessageType::ERROR, - "Attempted to access session before initialization", - ); + log_error!("Attempted to access session before initialization"); R::default() } } @@ -72,10 +69,7 @@ impl DjangoLanguageServer { if let Some(s) = &mut *session { f(s) } else { - client::log_message( - MessageType::ERROR, - "Attempted to access session before initialization", - ); + log_error!("Attempted to access session before initialization"); R::default() } } @@ -88,16 +82,16 @@ impl DjangoLanguageServer { let session_arc = Arc::clone(&self.session); if let Err(e) = self.queue.submit(async move { f(session_arc).await }).await { - client::log_message(MessageType::ERROR, format!("Failed to submit task: {e}")); + log_error!("Failed to submit task: {}", e); } else { - client::log_message(MessageType::INFO, "Task submitted successfully"); + log_info!("Task submitted successfully"); } } } impl LanguageServer for DjangoLanguageServer { async fn initialize(&self, params: InitializeParams) -> LspResult { - client::log_message(MessageType::INFO, "Initializing server..."); + log_info!("Initializing server..."); let session = Session::new(¶ms); @@ -145,10 +139,7 @@ impl LanguageServer for DjangoLanguageServer { #[allow(clippy::too_many_lines)] async fn initialized(&self, _params: InitializedParams) { - client::log_message( - MessageType::INFO, - "Server received initialized notification.", - ); + log_info!("Server received initialized notification."); self.with_session_task(|session_arc| async move { let project_path_and_venv = { @@ -168,16 +159,13 @@ impl LanguageServer for DjangoLanguageServer { }; if let Some((path_display, venv_path)) = project_path_and_venv { - client::log_message( - MessageType::INFO, - format!("Task: Starting initialization for project at: {path_display}"), + log_info!( + "Task: Starting initialization for project at: {}", + path_display ); if let Some(ref path) = venv_path { - client::log_message( - MessageType::INFO, - format!("Using virtual environment from config: {path}"), - ); + log_info!("Using virtual environment from config: {}", path); } let init_result = { @@ -197,17 +185,13 @@ impl LanguageServer for DjangoLanguageServer { match init_result { Ok(()) => { - client::log_message( - MessageType::INFO, - format!("Task: Successfully initialized project: {path_display}"), - ); + log_info!("Task: Successfully initialized project: {}", path_display); } Err(e) => { - client::log_message( - MessageType::ERROR, - format!( - "Task: Failed to initialize Django project at {path_display}: {e}" - ), + log_error!( + "Task: Failed to initialize Django project at {}: {}", + path_display, + e ); // Clear project on error @@ -218,10 +202,7 @@ impl LanguageServer for DjangoLanguageServer { } } } else { - client::log_message( - MessageType::INFO, - "Task: No project instance found to initialize.", - ); + log_info!("Task: No project instance found to initialize."); } Ok(()) }) @@ -233,10 +214,7 @@ impl LanguageServer for DjangoLanguageServer { } async fn did_open(&self, params: DidOpenTextDocumentParams) { - client::log_message( - MessageType::INFO, - format!("Opened document: {:?}", params.text_document.uri), - ); + log_info!("Opened document: {:?}", params.text_document.uri); self.with_session_mut(|session| { let db = session.db(); @@ -246,10 +224,7 @@ impl LanguageServer for DjangoLanguageServer { } async fn did_change(&self, params: DidChangeTextDocumentParams) { - client::log_message( - MessageType::INFO, - format!("Changed document: {:?}", params.text_document.uri), - ); + log_info!("Changed document: {:?}", params.text_document.uri); self.with_session_mut(|session| { let db = session.db(); @@ -259,10 +234,7 @@ impl LanguageServer for DjangoLanguageServer { } async fn did_close(&self, params: DidCloseTextDocumentParams) { - client::log_message( - MessageType::INFO, - format!("Closed document: {:?}", params.text_document.uri), - ); + log_info!("Closed document: {:?}", params.text_document.uri); self.with_session_mut(|session| { session.documents_mut().handle_did_close(¶ms); @@ -290,10 +262,7 @@ impl LanguageServer for DjangoLanguageServer { } async fn did_change_configuration(&self, _params: DidChangeConfigurationParams) { - client::log_message( - MessageType::INFO, - "Configuration change detected. Reloading settings...", - ); + log_info!("Configuration change detected. Reloading settings..."); let project_path = self .with_session(|session| session.project().map(|p| p.path().to_path_buf())) @@ -305,7 +274,7 @@ impl LanguageServer for DjangoLanguageServer { session.set_settings(new_settings); } Err(e) => { - client::log_message(MessageType::ERROR, format!("Error loading settings: {e}")); + log_error!("Error loading settings: {}", e); } }) .await;