use log::{Level, LevelFilter, Log, Metadata, Record};
use std::collections::VecDeque;
use std::io::{self, IsTerminal};
use std::sync::{Mutex, OnceLock};
use tokio::sync::mpsc::{self, Sender};
use tower_lsp::Client;
use tower_lsp::lsp_types::MessageType;
const BUFFER_SIZE: usize = 1000;
pub struct LspLogger {
sender: Mutex<Option<Sender<LogMessage>>>,
level: Mutex<LevelFilter>,
buffer: Mutex<VecDeque<LogMessage>>,
stderr_is_tty: bool,
}
struct LogMessage {
level: Level,
message: String,
}
impl LogMessage {
fn clone(&self) -> Self {
LogMessage {
level: self.level,
message: self.message.clone(),
}
}
}
static LOGGER: OnceLock<&'static LspLogger> = OnceLock::new();
impl LspLogger {
pub fn init_early(level: LevelFilter) -> Result<(), log::SetLoggerError> {
let stderr_is_tty = io::stderr().is_terminal();
let logger = Box::leak(Box::new(LspLogger {
sender: Mutex::new(None),
level: Mutex::new(level),
buffer: Mutex::new(VecDeque::with_capacity(BUFFER_SIZE)),
stderr_is_tty,
}));
let _ = LOGGER.set(logger);
let logger = LOGGER.get().expect("Logger should be initialized");
log::set_logger(*logger).map(|()| log::set_max_level(level))
}
pub fn attach_client(client: Client, level: LevelFilter) {
if let Some(logger) = LOGGER.get() {
*logger.level.lock().unwrap() = level;
log::set_max_level(level);
let (sender, mut receiver) = mpsc::channel::<LogMessage>(BUFFER_SIZE);
let buffered_messages: Vec<LogMessage> = {
let buffer = logger.buffer.lock().unwrap();
buffer.iter().map(|msg| msg.clone()).collect()
};
*logger.sender.lock().unwrap() = Some(sender.clone());
let client_clone = client.clone();
tokio::spawn(async move {
while let Some(log_msg) = receiver.recv().await {
let lsp_level = match log_msg.level {
Level::Error => MessageType::ERROR,
Level::Warn => MessageType::WARNING,
Level::Info => MessageType::INFO,
Level::Debug | Level::Trace => MessageType::LOG,
};
let _ = client_clone.log_message(lsp_level, log_msg.message).await;
}
});
for msg in buffered_messages {
let _ = sender.try_send(msg);
}
logger.buffer.lock().unwrap().clear();
}
}
}
impl Log for LspLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= *self.level.lock().unwrap()
}
fn log(&self, record: &Record) {
if !self.enabled(record.metadata()) {
return;
}
let message = format!(
"[{}] {}: {}",
record.target(),
record.level(),
record.args()
);
let log_msg = LogMessage {
level: record.level(),
message: message.clone(),
};
let sender_guard = self.sender.lock().unwrap();
if let Some(sender) = sender_guard.as_ref() {
let _ = sender.try_send(log_msg);
} else {
if self.stderr_is_tty || matches!(log_msg.level, Level::Warn | Level::Error) {
eprintln!("{}", message);
}
drop(sender_guard);
let mut buffer = self.buffer.lock().unwrap();
if buffer.len() >= BUFFER_SIZE {
buffer.pop_front();
}
buffer.push_back(log_msg);
}
}
fn flush(&self) {}
}