From a53c6f6feef231ecfb4e66d0e446e4148e816a2c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 26 Aug 2020 12:21:12 +0200 Subject: Allow redirecting logs to a specific file There's a surprising lack of crates which are like env_logger, but also allow writing to a file. Let's write our own then! --- crates/rust-analyzer/src/bin/args.rs | 14 +++++-- crates/rust-analyzer/src/bin/logger.rs | 73 ++++++++++++++++++++++++++++++++++ crates/rust-analyzer/src/bin/main.rs | 25 +++++++++--- docs/user/manual.adoc | 2 +- 4 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 crates/rust-analyzer/src/bin/logger.rs diff --git a/crates/rust-analyzer/src/bin/args.rs b/crates/rust-analyzer/src/bin/args.rs index 0bc92431a..45dc62ea7 100644 --- a/crates/rust-analyzer/src/bin/args.rs +++ b/crates/rust-analyzer/src/bin/args.rs @@ -13,6 +13,7 @@ use vfs::AbsPathBuf; pub(crate) struct Args { pub(crate) verbosity: Verbosity, + pub(crate) log_file: Option, pub(crate) command: Command, } @@ -53,7 +54,11 @@ impl Args { if matches.contains("--version") { matches.finish().or_else(handle_extra_flags)?; - return Ok(Args { verbosity: Verbosity::Normal, command: Command::Version }); + return Ok(Args { + verbosity: Verbosity::Normal, + log_file: None, + command: Command::Version, + }); } let verbosity = match ( @@ -68,8 +73,9 @@ impl Args { (false, true, false) => Verbosity::Verbose, (false, true, true) => bail!("Invalid flags: -q conflicts with -v"), }; + let log_file = matches.opt_value_from_str("--log-file")?; - let help = Ok(Args { verbosity, command: Command::Help }); + let help = Ok(Args { verbosity, log_file: None, command: Command::Help }); let subcommand = match matches.subcommand()? { Some(it) => it, None => { @@ -78,7 +84,7 @@ impl Args { return help; } matches.finish().or_else(handle_extra_flags)?; - return Ok(Args { verbosity, command: Command::RunServer }); + return Ok(Args { verbosity, log_file, command: Command::RunServer }); } }; let command = match subcommand.as_str() { @@ -345,7 +351,7 @@ ARGS: return help; } }; - Ok(Args { verbosity, command }) + Ok(Args { verbosity, log_file, command }) } } diff --git a/crates/rust-analyzer/src/bin/logger.rs b/crates/rust-analyzer/src/bin/logger.rs new file mode 100644 index 000000000..3bcb1ae37 --- /dev/null +++ b/crates/rust-analyzer/src/bin/logger.rs @@ -0,0 +1,73 @@ +//! Simple logger that logs either to stderr or to a file, using `env_logger` +//! filter syntax. Amusingly, there's no crates.io crate that can do this and +//! only this. + +use std::{ + fs::File, + io::{BufWriter, Write}, +}; + +use env_logger::filter::{Builder, Filter}; +use log::{Log, Metadata, Record}; +use parking_lot::Mutex; + +pub(crate) struct Logger { + filter: Filter, + file: Option>>, +} + +impl Logger { + pub(crate) fn new(log_file: Option, filter: Option<&str>) -> Logger { + let filter = { + let mut builder = Builder::new(); + if let Some(filter) = filter { + builder.parse(filter); + } + builder.build() + }; + + let file = log_file.map(|it| Mutex::new(BufWriter::new(it))); + + Logger { filter, file } + } + + pub(crate) fn install(self) { + let max_level = self.filter.filter(); + let _ = log::set_boxed_logger(Box::new(self)).map(|()| log::set_max_level(max_level)); + } +} + +impl Log for Logger { + fn enabled(&self, metadata: &Metadata) -> bool { + self.filter.enabled(metadata) + } + + fn log(&self, record: &Record) { + if !self.filter.matches(record) { + return; + } + match &self.file { + Some(w) => { + let _ = writeln!( + w.lock(), + "[{} {}] {}", + record.level(), + record.module_path().unwrap_or_default(), + record.args(), + ); + } + None => eprintln!( + "[{} {}] {}", + record.level(), + record.module_path().unwrap_or_default(), + record.args(), + ), + } + } + + fn flush(&self) { + if let Some(w) = &self.file { + let _ = w.lock().flush(); + } + } +} diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index 0e03a0ca8..266768970 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -2,8 +2,9 @@ //! //! Based on cli flags, either spawns an LSP server, or runs a batch analysis mod args; +mod logger; -use std::{convert::TryFrom, process}; +use std::{convert::TryFrom, env, fs, path::PathBuf, process}; use lsp_server::Connection; use project_model::ProjectManifest; @@ -26,8 +27,8 @@ fn main() { } fn try_main() -> Result<()> { - setup_logging()?; let args = args::Args::parse()?; + setup_logging(args.log_file)?; match args.command { args::Command::RunServer => run_server()?, args::Command::ProcMacro => proc_macro_srv::cli::run()?, @@ -52,9 +53,21 @@ fn try_main() -> Result<()> { Ok(()) } -fn setup_logging() -> Result<()> { - std::env::set_var("RUST_BACKTRACE", "short"); - env_logger::try_init_from_env("RA_LOG")?; +fn setup_logging(log_file: Option) -> Result<()> { + env::set_var("RUST_BACKTRACE", "short"); + + let log_file = match log_file { + Some(path) => { + if let Some(parent) = path.parent() { + let _ = fs::create_dir_all(parent); + } + Some(fs::File::create(path)?) + } + None => None, + }; + let filter = env::var("RA_LOG").ok(); + logger::Logger::new(log_file, filter.as_deref()).install(); + profile::init(); Ok(()) } @@ -95,7 +108,7 @@ fn run_server() -> Result<()> { { Some(it) => it, None => { - let cwd = std::env::current_dir()?; + let cwd = env::current_dir()?; AbsPathBuf::assert(cwd) } }; diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc index 144130b51..8c966288b 100644 --- a/docs/user/manual.adoc +++ b/docs/user/manual.adoc @@ -351,7 +351,7 @@ Relative paths are interpreted relative to `rust-project.json` file location or See https://github.com/rust-analyzer/rust-project.json-example for a small example. -You can set `RA_LOG` environmental variable to `"'rust_analyzer=info"` to inspect how rust-analyzer handles config and project loading. +You can set `RA_LOG` environmental variable to `rust_analyzer=info` to inspect how rust-analyzer handles config and project loading. == Features -- cgit v1.2.3