Skip to content

Commit a53c6f6

Browse files
committed
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!
1 parent f647edc commit a53c6f6

File tree

4 files changed

+103
-11
lines changed

4 files changed

+103
-11
lines changed

crates/rust-analyzer/src/bin/args.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use vfs::AbsPathBuf;
1313

1414
pub(crate) struct Args {
1515
pub(crate) verbosity: Verbosity,
16+
pub(crate) log_file: Option<PathBuf>,
1617
pub(crate) command: Command,
1718
}
1819

@@ -53,7 +54,11 @@ impl Args {
5354

5455
if matches.contains("--version") {
5556
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+
});
5762
}
5863

5964
let verbosity = match (
@@ -68,8 +73,9 @@ impl Args {
6873
(false, true, false) => Verbosity::Verbose,
6974
(false, true, true) => bail!("Invalid flags: -q conflicts with -v"),
7075
};
76+
let log_file = matches.opt_value_from_str("--log-file")?;
7177

72-
let help = Ok(Args { verbosity, command: Command::Help });
78+
let help = Ok(Args { verbosity, log_file: None, command: Command::Help });
7379
let subcommand = match matches.subcommand()? {
7480
Some(it) => it,
7581
None => {
@@ -78,7 +84,7 @@ impl Args {
7884
return help;
7985
}
8086
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 });
8288
}
8389
};
8490
let command = match subcommand.as_str() {
@@ -345,7 +351,7 @@ ARGS:
345351
return help;
346352
}
347353
};
348-
Ok(Args { verbosity, command })
354+
Ok(Args { verbosity, log_file, command })
349355
}
350356
}
351357

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
5+
use std::{
6+
fs::File,
7+
io::{BufWriter, Write},
8+
};
9+
10+
use env_logger::filter::{Builder, Filter};
11+
use log::{Log, Metadata, Record};
12+
use parking_lot::Mutex;
13+
14+
pub(crate) struct Logger {
15+
filter: Filter,
16+
file: Option<Mutex<BufWriter<File>>>,
17+
}
18+
19+
impl 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+
40+
impl 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+
}

crates/rust-analyzer/src/bin/main.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
//!
33
//! Based on cli flags, either spawns an LSP server, or runs a batch analysis
44
mod args;
5+
mod logger;
56

6-
use std::{convert::TryFrom, process};
7+
use std::{convert::TryFrom, env, fs, path::PathBuf, process};
78

89
use lsp_server::Connection;
910
use project_model::ProjectManifest;
@@ -26,8 +27,8 @@ fn main() {
2627
}
2728

2829
fn try_main() -> Result<()> {
29-
setup_logging()?;
3030
let args = args::Args::parse()?;
31+
setup_logging(args.log_file)?;
3132
match args.command {
3233
args::Command::RunServer => run_server()?,
3334
args::Command::ProcMacro => proc_macro_srv::cli::run()?,
@@ -52,9 +53,21 @@ fn try_main() -> Result<()> {
5253
Ok(())
5354
}
5455

55-
fn setup_logging() -> Result<()> {
56-
std::env::set_var("RUST_BACKTRACE", "short");
57-
env_logger::try_init_from_env("RA_LOG")?;
56+
fn setup_logging(log_file: Option<PathBuf>) -> Result<()> {
57+
env::set_var("RUST_BACKTRACE", "short");
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+
5871
profile::init();
5972
Ok(())
6073
}
@@ -95,7 +108,7 @@ fn run_server() -> Result<()> {
95108
{
96109
Some(it) => it,
97110
None => {
98-
let cwd = std::env::current_dir()?;
111+
let cwd = env::current_dir()?;
99112
AbsPathBuf::assert(cwd)
100113
}
101114
};

docs/user/manual.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ Relative paths are interpreted relative to `rust-project.json` file location or
351351

352352
See https://github.com/rust-analyzer/rust-project.json-example for a small example.
353353

354-
You can set `RA_LOG` environmental variable to `"'rust_analyzer=info"` to inspect how rust-analyzer handles config and project loading.
354+
You can set `RA_LOG` environmental variable to `rust_analyzer=info` to inspect how rust-analyzer handles config and project loading.
355355

356356
== Features
357357

0 commit comments

Comments
 (0)