]> git.lizzy.rs Git - rust.git/commitdiff
Allow redirecting logs to a specific file
authorAleksey Kladov <aleksey.kladov@gmail.com>
Wed, 26 Aug 2020 10:21:12 +0000 (12:21 +0200)
committerAleksey Kladov <aleksey.kladov@gmail.com>
Wed, 26 Aug 2020 11:20:46 +0000 (13:20 +0200)
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
crates/rust-analyzer/src/bin/logger.rs [new file with mode: 0644]
crates/rust-analyzer/src/bin/main.rs
docs/user/manual.adoc

index 0bc92431a9a0b7d69c15f11478fed5e56e61cfc9..45dc62ea79a488435a4055764bcc112984f740df 100644 (file)
@@ -13,6 +13,7 @@
 
 pub(crate) struct Args {
     pub(crate) verbosity: Verbosity,
+    pub(crate) log_file: Option<PathBuf>,
     pub(crate) command: Command,
 }
 
@@ -53,7 +54,11 @@ pub(crate) fn parse() -> Result<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 @@ pub(crate) fn parse() -> Result<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 @@ pub(crate) fn parse() -> Result<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 @@ pub(crate) fn parse() -> Result<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 (file)
index 0000000..3bcb1ae
--- /dev/null
@@ -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<Mutex<BufWriter<File>>>,
+}
+
+impl Logger {
+    pub(crate) fn new(log_file: Option<File>, 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();
+        }
+    }
+}
index 0e03a0ca8b5d2476bba5b811d392f4482935e1e8..26676897092211531768764e6f2d71caddbd869c 100644 (file)
@@ -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<PathBuf>) -> 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)
             }
         };
index 144130b51b35b7ef14e11a799dc2680e70a95844..8c966288b75c6d1b162ea906f7057c65d49fc756 100644 (file)
@@ -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