Skip to content

Commit 0010a71

Browse files
Setup server logging to file using standard XDG directory (#334)
Co-authored-by: joshuadavidthomas <[email protected]> Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: Josh Thomas <[email protected]>
1 parent b951058 commit 0010a71

File tree

4 files changed

+47
-9
lines changed

4 files changed

+47
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ and this project attempts to adhere to [Semantic Versioning](https://semver.org/
2929
### Changed
3030

3131
- Changed user configuration directory paths to use application name only, removing organization identifiers
32+
- Changed log directory to use XDG cache directories (e.g., `~/.cache/djls` on Linux) with `/tmp` fallback
3233
- **Internal**: Refactored workspace to use domain types (`FileKind`) instead of LSP types (`LanguageId`)
3334
- **Internal**: Added client detection for LSP-specific workarounds (e.g., Sublime Text's `html` language ID handling)
3435

crates/djls-conf/src/lib.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ pub mod tagspecs;
33
use std::fs;
44
use std::path::Path;
55

6+
use anyhow::Context;
67
use camino::Utf8Path;
8+
use camino::Utf8PathBuf;
79
use config::Config;
810
use config::ConfigError as ExternalConfigError;
911
use config::File;
@@ -19,6 +21,28 @@ pub use crate::tagspecs::SimpleArgTypeDef;
1921
pub use crate::tagspecs::TagArgDef;
2022
pub use crate::tagspecs::TagSpecDef;
2123

24+
pub(crate) fn project_dirs() -> Option<ProjectDirs> {
25+
ProjectDirs::from("", "", "djls")
26+
}
27+
28+
/// Get the log directory for the application and ensure it exists.
29+
///
30+
/// Returns the XDG cache directory (e.g., ~/.cache/djls on Linux) if available,
31+
/// otherwise falls back to /tmp. Creates the directory if it doesn't exist.
32+
///
33+
/// # Errors
34+
///
35+
/// Returns an error if the directory cannot be created.
36+
pub fn log_dir() -> anyhow::Result<Utf8PathBuf> {
37+
let dir = project_dirs()
38+
.and_then(|proj_dirs| Utf8PathBuf::from_path_buf(proj_dirs.cache_dir().to_path_buf()).ok())
39+
.unwrap_or_else(|| Utf8PathBuf::from("/tmp"));
40+
41+
fs::create_dir_all(&dir).with_context(|| format!("Failed to create log directory: {dir}"))?;
42+
43+
Ok(dir)
44+
}
45+
2246
#[derive(Error, Debug)]
2347
pub enum ConfigError {
2448
#[error("Configuration build/deserialize error")]
@@ -45,8 +69,8 @@ pub struct Settings {
4569

4670
impl Settings {
4771
pub fn new(project_root: &Utf8Path, overrides: Option<Settings>) -> Result<Self, ConfigError> {
48-
let user_config_file = ProjectDirs::from("", "", "djls")
49-
.map(|proj_dirs| proj_dirs.config_dir().join("djls.toml"));
72+
let user_config_file =
73+
project_dirs().map(|proj_dirs| proj_dirs.config_dir().join("djls.toml"));
5074

5175
let mut settings = Self::load_from_paths(project_root, user_config_file.as_deref())?;
5276

crates/djls-server/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use tower_lsp_server::Server;
1515
pub use crate::server::DjangoLanguageServer;
1616
pub use crate::session::Session;
1717

18+
/// Run the Django language server.
1819
pub fn run() -> Result<()> {
1920
if std::io::stdin().is_terminal() {
2021
eprintln!(

crates/djls-server/src/logging.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,20 +105,32 @@ where
105105
/// Initialize the dual-layer tracing subscriber.
106106
///
107107
/// Sets up:
108-
/// - File layer: writes to /tmp/djls.log with daily rotation
108+
/// - File layer: writes to XDG cache directory (e.g., ~/.cache/djls/djls.log on Linux) with daily rotation.
109+
/// Falls back to /tmp/djls.log if XDG cache directory is not available.
110+
/// If file logging cannot be initialized, falls back to stderr.
109111
/// - LSP layer: forwards INFO+ messages to the client
110112
/// - `EnvFilter`: respects `RUST_LOG` env var, defaults to "info"
111113
///
112-
/// Returns a `WorkerGuard` that must be kept alive for the file logging to work.
114+
/// Returns a `WorkerGuard` that must be kept alive for the logging to work.
113115
pub fn init_tracing<F>(send_message: F) -> WorkerGuard
114116
where
115117
F: Fn(lsp_types::MessageType, String) + Send + Sync + 'static,
116118
{
117-
let file_appender = tracing_appender::rolling::daily("/tmp", "djls.log");
118-
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
119-
120119
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
121-
let file_layer = fmt::layer()
120+
121+
let (non_blocking, guard) = match djls_conf::log_dir() {
122+
Ok(log_dir) => {
123+
let file_appender = tracing_appender::rolling::daily(log_dir.as_std_path(), "djls.log");
124+
tracing_appender::non_blocking(file_appender)
125+
}
126+
Err(e) => {
127+
eprintln!("Warning: Failed to initialize file logging: {e}");
128+
eprintln!("Falling back to stderr logging...");
129+
tracing_appender::non_blocking(std::io::stderr())
130+
}
131+
};
132+
133+
let log_layer = fmt::layer()
122134
.with_writer(non_blocking)
123135
.with_ansi(false)
124136
.with_thread_ids(true)
@@ -131,7 +143,7 @@ where
131143
let lsp_layer =
132144
LspLayer::new(send_message).with_filter(tracing_subscriber::filter::LevelFilter::INFO);
133145

134-
Registry::default().with(file_layer).with(lsp_layer).init();
146+
Registry::default().with(log_layer).with(lsp_layer).init();
135147

136148
guard
137149
}

0 commit comments

Comments
 (0)