diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c221b41..2d4f40f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project attempts to adhere to [Semantic Versioning](https://semver.org/ ### Changed - Changed user configuration directory paths to use application name only, removing organization identifiers +- Changed log directory to use XDG cache directories (e.g., `~/.cache/djls` on Linux) with `/tmp` fallback - **Internal**: Refactored workspace to use domain types (`FileKind`) instead of LSP types (`LanguageId`) - **Internal**: Added client detection for LSP-specific workarounds (e.g., Sublime Text's `html` language ID handling) diff --git a/crates/djls-conf/src/lib.rs b/crates/djls-conf/src/lib.rs index 76efe98b..11573707 100644 --- a/crates/djls-conf/src/lib.rs +++ b/crates/djls-conf/src/lib.rs @@ -3,7 +3,9 @@ pub mod tagspecs; use std::fs; use std::path::Path; +use anyhow::Context; use camino::Utf8Path; +use camino::Utf8PathBuf; use config::Config; use config::ConfigError as ExternalConfigError; use config::File; @@ -19,6 +21,28 @@ pub use crate::tagspecs::SimpleArgTypeDef; pub use crate::tagspecs::TagArgDef; pub use crate::tagspecs::TagSpecDef; +pub(crate) fn project_dirs() -> Option { + ProjectDirs::from("", "", "djls") +} + +/// Get the log directory for the application and ensure it exists. +/// +/// Returns the XDG cache directory (e.g., ~/.cache/djls on Linux) if available, +/// otherwise falls back to /tmp. Creates the directory if it doesn't exist. +/// +/// # Errors +/// +/// Returns an error if the directory cannot be created. +pub fn log_dir() -> anyhow::Result { + let dir = project_dirs() + .and_then(|proj_dirs| Utf8PathBuf::from_path_buf(proj_dirs.cache_dir().to_path_buf()).ok()) + .unwrap_or_else(|| Utf8PathBuf::from("/tmp")); + + fs::create_dir_all(&dir).with_context(|| format!("Failed to create log directory: {dir}"))?; + + Ok(dir) +} + #[derive(Error, Debug)] pub enum ConfigError { #[error("Configuration build/deserialize error")] @@ -45,8 +69,8 @@ pub struct Settings { impl Settings { pub fn new(project_root: &Utf8Path, overrides: Option) -> Result { - let user_config_file = ProjectDirs::from("", "", "djls") - .map(|proj_dirs| proj_dirs.config_dir().join("djls.toml")); + let user_config_file = + project_dirs().map(|proj_dirs| proj_dirs.config_dir().join("djls.toml")); let mut settings = Self::load_from_paths(project_root, user_config_file.as_deref())?; diff --git a/crates/djls-server/src/lib.rs b/crates/djls-server/src/lib.rs index 55ee43d5..3cf685a1 100644 --- a/crates/djls-server/src/lib.rs +++ b/crates/djls-server/src/lib.rs @@ -15,6 +15,7 @@ use tower_lsp_server::Server; pub use crate::server::DjangoLanguageServer; pub use crate::session::Session; +/// Run the Django language server. pub fn run() -> Result<()> { if std::io::stdin().is_terminal() { eprintln!( diff --git a/crates/djls-server/src/logging.rs b/crates/djls-server/src/logging.rs index 030af946..ca9e88a6 100644 --- a/crates/djls-server/src/logging.rs +++ b/crates/djls-server/src/logging.rs @@ -105,20 +105,32 @@ where /// Initialize the dual-layer tracing subscriber. /// /// Sets up: -/// - File layer: writes to /tmp/djls.log with daily rotation +/// - File layer: writes to XDG cache directory (e.g., ~/.cache/djls/djls.log on Linux) with daily rotation. +/// Falls back to /tmp/djls.log if XDG cache directory is not available. +/// If file logging cannot be initialized, falls back to stderr. /// - LSP layer: forwards INFO+ messages to the client /// - `EnvFilter`: respects `RUST_LOG` env var, defaults to "info" /// -/// Returns a `WorkerGuard` that must be kept alive for the file logging to work. +/// Returns a `WorkerGuard` that must be kept alive for the logging to work. pub fn init_tracing(send_message: F) -> WorkerGuard where F: Fn(lsp_types::MessageType, String) + Send + Sync + 'static, { - let file_appender = tracing_appender::rolling::daily("/tmp", "djls.log"); - let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); - let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); - let file_layer = fmt::layer() + + let (non_blocking, guard) = match djls_conf::log_dir() { + Ok(log_dir) => { + let file_appender = tracing_appender::rolling::daily(log_dir.as_std_path(), "djls.log"); + tracing_appender::non_blocking(file_appender) + } + Err(e) => { + eprintln!("Warning: Failed to initialize file logging: {e}"); + eprintln!("Falling back to stderr logging..."); + tracing_appender::non_blocking(std::io::stderr()) + } + }; + + let log_layer = fmt::layer() .with_writer(non_blocking) .with_ansi(false) .with_thread_ids(true) @@ -131,7 +143,7 @@ where let lsp_layer = LspLayer::new(send_message).with_filter(tracing_subscriber::filter::LevelFilter::INFO); - Registry::default().with(file_layer).with(lsp_layer).init(); + Registry::default().with(log_layer).with(lsp_layer).init(); guard }