mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-05 01:40:51 +00:00
Add unified file and LSP logging using tracing to server (#178)
This commit is contained in:
parent
fd0fc0a8d2
commit
352c50d1b4
4 changed files with 149 additions and 3 deletions
|
@ -21,6 +21,8 @@ serde_json = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tower-lsp-server = { workspace = true }
|
tower-lsp-server = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
tracing-appender = { workspace = true }
|
||||||
|
tracing-subscriber = { workspace = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
djls-dev = { workspace = true }
|
djls-dev = { workspace = true }
|
||||||
|
|
|
@ -48,7 +48,12 @@ pub fn run() -> Result<()> {
|
||||||
|
|
||||||
let (service, socket) = LspService::build(|client| {
|
let (service, socket) = LspService::build(|client| {
|
||||||
client::init_client(client);
|
client::init_client(client);
|
||||||
DjangoLanguageServer::new()
|
|
||||||
|
let log_guard = logging::init_tracing(|message_type, message| {
|
||||||
|
client::log_message(message_type, message);
|
||||||
|
});
|
||||||
|
|
||||||
|
DjangoLanguageServer::new(log_guard)
|
||||||
})
|
})
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,16 @@
|
||||||
//! Temporary logging macros for dual-dispatch to both LSP client and tracing.
|
//! Logging infrastructure bridging tracing events to LSP client messages.
|
||||||
|
//!
|
||||||
|
//! This module provides both temporary dual-dispatch macros and the permanent
|
||||||
|
//! `LspLayer` implementation for forwarding tracing events to the LSP client.
|
||||||
|
//!
|
||||||
|
//! ## `LspLayer`
|
||||||
|
//!
|
||||||
|
//! The `LspLayer` is a tracing `Layer` that intercepts tracing events and
|
||||||
|
//! forwards appropriate ones to the LSP client. It filters events by level:
|
||||||
|
//! - ERROR, WARN, INFO, DEBUG → forwarded to LSP client
|
||||||
|
//! - TRACE → kept server-side only (for performance)
|
||||||
|
//!
|
||||||
|
//! ## Temporary Macros
|
||||||
//!
|
//!
|
||||||
//! These macros bridge the gap during our migration from `client::log_message`
|
//! These macros bridge the gap during our migration from `client::log_message`
|
||||||
//! to the tracing infrastructure. They ensure messages are sent to both systems
|
//! to the tracing infrastructure. They ensure messages are sent to both systems
|
||||||
|
@ -27,6 +39,130 @@
|
||||||
//! - For format strings, we format once for the client but pass the original
|
//! - For format strings, we format once for the client but pass the original
|
||||||
//! format string and args to tracing to preserve structured data
|
//! format string and args to tracing to preserve structured data
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use tower_lsp_server::lsp_types::MessageType;
|
||||||
|
use tracing::field::Visit;
|
||||||
|
use tracing::Level;
|
||||||
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
|
use tracing_subscriber::fmt;
|
||||||
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
use tracing_subscriber::Layer;
|
||||||
|
use tracing_subscriber::Registry;
|
||||||
|
|
||||||
|
/// A tracing Layer that forwards events to the LSP client.
|
||||||
|
///
|
||||||
|
/// This layer intercepts tracing events and converts them to LSP log messages
|
||||||
|
/// that are sent to the client. It filters events by level to avoid overwhelming
|
||||||
|
/// the client with verbose trace logs.
|
||||||
|
pub struct LspLayer {
|
||||||
|
send_message: Arc<dyn Fn(MessageType, String) + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LspLayer {
|
||||||
|
pub fn new<F>(send_message: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(MessageType, String) + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
send_message: Arc::new(send_message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visitor that extracts the message field from tracing events.
|
||||||
|
struct MessageVisitor {
|
||||||
|
message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageVisitor {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self { message: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Visit for MessageVisitor {
|
||||||
|
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
|
||||||
|
if field.name() == "message" {
|
||||||
|
self.message = Some(format!("{value:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
|
||||||
|
if field.name() == "message" {
|
||||||
|
self.message = Some(value.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Layer<S> for LspLayer
|
||||||
|
where
|
||||||
|
S: tracing::Subscriber,
|
||||||
|
{
|
||||||
|
fn on_event(
|
||||||
|
&self,
|
||||||
|
event: &tracing::Event<'_>,
|
||||||
|
_ctx: tracing_subscriber::layer::Context<'_, S>,
|
||||||
|
) {
|
||||||
|
let metadata = event.metadata();
|
||||||
|
|
||||||
|
let message_type = match *metadata.level() {
|
||||||
|
Level::ERROR => MessageType::ERROR,
|
||||||
|
Level::WARN => MessageType::WARNING,
|
||||||
|
Level::INFO => MessageType::INFO,
|
||||||
|
Level::DEBUG => MessageType::LOG,
|
||||||
|
Level::TRACE => {
|
||||||
|
// Skip TRACE level - too verbose for LSP client
|
||||||
|
// TODO: Add MessageType::Debug in LSP 3.18.0
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut visitor = MessageVisitor::new();
|
||||||
|
event.record(&mut visitor);
|
||||||
|
|
||||||
|
if let Some(message) = visitor.message {
|
||||||
|
(self.send_message)(message_type, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the dual-layer tracing subscriber.
|
||||||
|
///
|
||||||
|
/// Sets up:
|
||||||
|
/// - File layer: writes to /tmp/djls.log with daily rotation
|
||||||
|
/// - 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.
|
||||||
|
pub fn init_tracing<F>(send_message: F) -> WorkerGuard
|
||||||
|
where
|
||||||
|
F: Fn(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()
|
||||||
|
.with_writer(non_blocking)
|
||||||
|
.with_ansi(false)
|
||||||
|
.with_thread_ids(true)
|
||||||
|
.with_thread_names(true)
|
||||||
|
.with_target(true)
|
||||||
|
.with_file(true)
|
||||||
|
.with_line_number(true)
|
||||||
|
.with_filter(env_filter);
|
||||||
|
|
||||||
|
let lsp_layer =
|
||||||
|
LspLayer::new(send_message).with_filter(tracing_subscriber::filter::LevelFilter::INFO);
|
||||||
|
|
||||||
|
Registry::default().with(file_layer).with(lsp_layer).init();
|
||||||
|
|
||||||
|
guard
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! log_info {
|
macro_rules! log_info {
|
||||||
($msg:literal) => {
|
($msg:literal) => {
|
||||||
|
|
|
@ -23,6 +23,7 @@ use tower_lsp_server::lsp_types::TextDocumentSyncOptions;
|
||||||
use tower_lsp_server::lsp_types::WorkspaceFoldersServerCapabilities;
|
use tower_lsp_server::lsp_types::WorkspaceFoldersServerCapabilities;
|
||||||
use tower_lsp_server::lsp_types::WorkspaceServerCapabilities;
|
use tower_lsp_server::lsp_types::WorkspaceServerCapabilities;
|
||||||
use tower_lsp_server::LanguageServer;
|
use tower_lsp_server::LanguageServer;
|
||||||
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
|
|
||||||
use crate::log_error;
|
use crate::log_error;
|
||||||
use crate::log_info;
|
use crate::log_info;
|
||||||
|
@ -35,14 +36,16 @@ const SERVER_VERSION: &str = "0.1.0";
|
||||||
pub struct DjangoLanguageServer {
|
pub struct DjangoLanguageServer {
|
||||||
session: Arc<RwLock<Option<Session>>>,
|
session: Arc<RwLock<Option<Session>>>,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
|
_log_guard: WorkerGuard,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DjangoLanguageServer {
|
impl DjangoLanguageServer {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new(log_guard: WorkerGuard) -> Self {
|
||||||
Self {
|
Self {
|
||||||
session: Arc::new(RwLock::new(None)),
|
session: Arc::new(RwLock::new(None)),
|
||||||
queue: Queue::new(),
|
queue: Queue::new(),
|
||||||
|
_log_guard: log_guard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue