aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-08-26 11:21:12 +0100
committerAleksey Kladov <[email protected]>2020-08-26 12:20:46 +0100
commita53c6f6feef231ecfb4e66d0e446e4148e816a2c (patch)
treee2cbb7e27e4fb667ace5d59ca785c2eb24869e62
parentf647edcb080f50e01762a31eebd9ca94c982c768 (diff)
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!
-rw-r--r--crates/rust-analyzer/src/bin/args.rs14
-rw-r--r--crates/rust-analyzer/src/bin/logger.rs73
-rw-r--r--crates/rust-analyzer/src/bin/main.rs25
-rw-r--r--docs/user/manual.adoc2
4 files changed, 103 insertions, 11 deletions
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;
13 13
14pub(crate) struct Args { 14pub(crate) struct Args {
15 pub(crate) verbosity: Verbosity, 15 pub(crate) verbosity: Verbosity,
16 pub(crate) log_file: Option<PathBuf>,
16 pub(crate) command: Command, 17 pub(crate) command: Command,
17} 18}
18 19
@@ -53,7 +54,11 @@ impl Args {
53 54
54 if matches.contains("--version") { 55 if matches.contains("--version") {
55 matches.finish().or_else(handle_extra_flags)?; 56 matches.finish().or_else(handle_extra_flags)?;
56 return Ok(Args { verbosity: Verbosity::Normal, command: Command::Version }); 57 return Ok(Args {
58 verbosity: Verbosity::Normal,
59 log_file: None,
60 command: Command::Version,
61 });
57 } 62 }
58 63
59 let verbosity = match ( 64 let verbosity = match (
@@ -68,8 +73,9 @@ impl Args {
68 (false, true, false) => Verbosity::Verbose, 73 (false, true, false) => Verbosity::Verbose,
69 (false, true, true) => bail!("Invalid flags: -q conflicts with -v"), 74 (false, true, true) => bail!("Invalid flags: -q conflicts with -v"),
70 }; 75 };
76 let log_file = matches.opt_value_from_str("--log-file")?;
71 77
72 let help = Ok(Args { verbosity, command: Command::Help }); 78 let help = Ok(Args { verbosity, log_file: None, command: Command::Help });
73 let subcommand = match matches.subcommand()? { 79 let subcommand = match matches.subcommand()? {
74 Some(it) => it, 80 Some(it) => it,
75 None => { 81 None => {
@@ -78,7 +84,7 @@ impl Args {
78 return help; 84 return help;
79 } 85 }
80 matches.finish().or_else(handle_extra_flags)?; 86 matches.finish().or_else(handle_extra_flags)?;
81 return Ok(Args { verbosity, command: Command::RunServer }); 87 return Ok(Args { verbosity, log_file, command: Command::RunServer });
82 } 88 }
83 }; 89 };
84 let command = match subcommand.as_str() { 90 let command = match subcommand.as_str() {
@@ -345,7 +351,7 @@ ARGS:
345 return help; 351 return help;
346 } 352 }
347 }; 353 };
348 Ok(Args { verbosity, command }) 354 Ok(Args { verbosity, log_file, command })
349 } 355 }
350} 356}
351 357
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 @@
1//! Simple logger that logs either to stderr or to a file, using `env_logger`
2//! filter syntax. Amusingly, there's no crates.io crate that can do this and
3//! only this.
4
5use std::{
6 fs::File,
7 io::{BufWriter, Write},
8};
9
10use env_logger::filter::{Builder, Filter};
11use log::{Log, Metadata, Record};
12use parking_lot::Mutex;
13
14pub(crate) struct Logger {
15 filter: Filter,
16 file: Option<Mutex<BufWriter<File>>>,
17}
18
19impl Logger {
20 pub(crate) fn new(log_file: Option<File>, filter: Option<&str>) -> Logger {
21 let filter = {
22 let mut builder = Builder::new();
23 if let Some(filter) = filter {
24 builder.parse(filter);
25 }
26 builder.build()
27 };
28
29 let file = log_file.map(|it| Mutex::new(BufWriter::new(it)));
30
31 Logger { filter, file }
32 }
33
34 pub(crate) fn install(self) {
35 let max_level = self.filter.filter();
36 let _ = log::set_boxed_logger(Box::new(self)).map(|()| log::set_max_level(max_level));
37 }
38}
39
40impl Log for Logger {
41 fn enabled(&self, metadata: &Metadata) -> bool {
42 self.filter.enabled(metadata)
43 }
44
45 fn log(&self, record: &Record) {
46 if !self.filter.matches(record) {
47 return;
48 }
49 match &self.file {
50 Some(w) => {
51 let _ = writeln!(
52 w.lock(),
53 "[{} {}] {}",
54 record.level(),
55 record.module_path().unwrap_or_default(),
56 record.args(),
57 );
58 }
59 None => eprintln!(
60 "[{} {}] {}",
61 record.level(),
62 record.module_path().unwrap_or_default(),
63 record.args(),
64 ),
65 }
66 }
67
68 fn flush(&self) {
69 if let Some(w) = &self.file {
70 let _ = w.lock().flush();
71 }
72 }
73}
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 @@
2//! 2//!
3//! Based on cli flags, either spawns an LSP server, or runs a batch analysis 3//! Based on cli flags, either spawns an LSP server, or runs a batch analysis
4mod args; 4mod args;
5mod logger;
5 6
6use std::{convert::TryFrom, process}; 7use std::{convert::TryFrom, env, fs, path::PathBuf, process};
7 8
8use lsp_server::Connection; 9use lsp_server::Connection;
9use project_model::ProjectManifest; 10use project_model::ProjectManifest;
@@ -26,8 +27,8 @@ fn main() {
26} 27}
27 28
28fn try_main() -> Result<()> { 29fn try_main() -> Result<()> {
29 setup_logging()?;
30 let args = args::Args::parse()?; 30 let args = args::Args::parse()?;
31 setup_logging(args.log_file)?;
31 match args.command { 32 match args.command {
32 args::Command::RunServer => run_server()?, 33 args::Command::RunServer => run_server()?,
33 args::Command::ProcMacro => proc_macro_srv::cli::run()?, 34 args::Command::ProcMacro => proc_macro_srv::cli::run()?,
@@ -52,9 +53,21 @@ fn try_main() -> Result<()> {
52 Ok(()) 53 Ok(())
53} 54}
54 55
55fn setup_logging() -> Result<()> { 56fn setup_logging(log_file: Option<PathBuf>) -> Result<()> {
56 std::env::set_var("RUST_BACKTRACE", "short"); 57 env::set_var("RUST_BACKTRACE", "short");
57 env_logger::try_init_from_env("RA_LOG")?; 58
59 let log_file = match log_file {
60 Some(path) => {
61 if let Some(parent) = path.parent() {
62 let _ = fs::create_dir_all(parent);
63 }
64 Some(fs::File::create(path)?)
65 }
66 None => None,
67 };
68 let filter = env::var("RA_LOG").ok();
69 logger::Logger::new(log_file, filter.as_deref()).install();
70
58 profile::init(); 71 profile::init();
59 Ok(()) 72 Ok(())
60} 73}
@@ -95,7 +108,7 @@ fn run_server() -> Result<()> {
95 { 108 {
96 Some(it) => it, 109 Some(it) => it,
97 None => { 110 None => {
98 let cwd = std::env::current_dir()?; 111 let cwd = env::current_dir()?;
99 AbsPathBuf::assert(cwd) 112 AbsPathBuf::assert(cwd)
100 } 113 }
101 }; 114 };
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
351 351
352See https://github.com/rust-analyzer/rust-project.json-example for a small example. 352See https://github.com/rust-analyzer/rust-project.json-example for a small example.
353 353
354You can set `RA_LOG` environmental variable to `"'rust_analyzer=info"` to inspect how rust-analyzer handles config and project loading. 354You can set `RA_LOG` environmental variable to `rust_analyzer=info` to inspect how rust-analyzer handles config and project loading.
355 355
356== Features 356== Features
357 357