5889: Allow logging to file r=matklad a=matklad

bors r+
🤖

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2020-08-26 11:21:17 +00:00 committed by GitHub
commit 868aaf2bc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 258 additions and 328 deletions

View File

@ -5,7 +5,7 @@
use std::{env, fmt::Write, path::PathBuf}; use std::{env, fmt::Write, path::PathBuf};
use anyhow::{bail, Result}; use anyhow::{bail, format_err, Result};
use pico_args::Arguments; use pico_args::Arguments;
use rust_analyzer::cli::{AnalysisStatsCmd, BenchCmd, BenchWhat, Position, Verbosity}; use rust_analyzer::cli::{AnalysisStatsCmd, BenchCmd, BenchWhat, Position, Verbosity};
use ssr::{SsrPattern, SsrRule}; use ssr::{SsrPattern, SsrRule};
@ -13,47 +13,107 @@ use vfs::AbsPathBuf;
pub(crate) struct Args { pub(crate) struct Args {
pub(crate) verbosity: Verbosity, pub(crate) verbosity: Verbosity,
pub(crate) log_file: Option<PathBuf>,
pub(crate) command: Command, pub(crate) command: Command,
} }
pub(crate) enum Command { pub(crate) enum Command {
Parse { Parse { no_dump: bool },
no_dump: bool,
},
Symbols, Symbols,
Highlight { Highlight { rainbow: bool },
rainbow: bool,
},
AnalysisStats(AnalysisStatsCmd), AnalysisStats(AnalysisStatsCmd),
Bench(BenchCmd), Bench(BenchCmd),
Diagnostics { Diagnostics { path: PathBuf, load_output_dirs: bool, with_proc_macro: bool },
path: PathBuf, Ssr { rules: Vec<SsrRule> },
load_output_dirs: bool, StructuredSearch { debug_snippet: Option<String>, patterns: Vec<SsrPattern> },
with_proc_macro: bool,
/// Include files which are not modules. In rust-analyzer
/// this would include the parser test files.
all: bool,
},
Ssr {
rules: Vec<SsrRule>,
},
StructuredSearch {
debug_snippet: Option<String>,
patterns: Vec<SsrPattern>,
},
ProcMacro, ProcMacro,
RunServer, RunServer,
Version, Version,
Help, Help,
} }
const HELP: &str = "\
rust-analyzer
USAGE:
rust-analyzer [FLAGS] [COMMAND] [COMMAND_OPTIONS]
FLAGS:
--version Print version
-h, --help Print this help
-v, --verbose
-vv, --spammy
-q, --quiet Set verbosity
--log-file <PATH> Log to the specified filed instead of stderr
ENVIRONMENTAL VARIABLES:
RA_LOG Set log filter in env_logger format
RA_PROFILE Enable hierarchical profiler
COMMANDS:
not specified Launch LSP server
parse < main.rs Parse tree
--no-dump Suppress printing
symbols < main.rs Parse input an print the list of symbols
highlight < main.rs Highlight input as html
--rainbow Enable rainbow highlighting of identifiers
analysis-stats <PATH> Batch typecheck project and print summary statistics
<PATH> Directory with Cargo.toml
--randomize Randomize order in which crates, modules, and items are processed
--parallel Run type inference in parallel
--memory-usage Collect memory usage statistics
-o, --only <PATH> Only analyze items matching this path
--with-deps Also analyze all dependencies
--load-output-dirs
Load OUT_DIR values by running `cargo check` before analysis
--with-proc-macro Use proc-macro-srv for proc-macro expanding
analysis-bench <PATH> Benchmark specific analysis operation
<PATH> Directory with Cargo.toml
--highlight <PATH>
Compute syntax highlighting for this file
--complete <PATH:LINE:COLUMN>
Compute completions at this location
--goto-def <PATH:LINE:COLUMN>
Compute goto definition at this location
--memory-usage Collect memory usage statistics
--load-output-dirs
Load OUT_DIR values by running `cargo check` before analysis
--with-proc-macro Use proc-macro-srv for proc-macro expanding
diagnostics <PATH>
<PATH> Directory with Cargo.toml
--load-output-dirs
Load OUT_DIR values by running `cargo check` before analysis
--with-proc-macro Use proc-macro-srv for proc-macro expanding
ssr [RULE...]
<RULE> A structured search replace rule (`$a.foo($b) ==> bar($a, $b)`)
search [PATTERN..]
<PATTERN> A structured search replace pattern (`$a.foo($b)`)
--debug <snippet> Prints debug information for any nodes with source exactly
equal to <snippet>
";
impl Args { impl Args {
pub(crate) fn parse() -> Result<Args> { pub(crate) fn parse() -> Result<Args> {
let mut matches = Arguments::from_env(); let mut matches = Arguments::from_env();
if matches.contains("--version") { if matches.contains("--version") {
matches.finish().or_else(handle_extra_flags)?; 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 ( let verbosity = match (
@ -68,168 +128,45 @@ impl Args {
(false, true, false) => Verbosity::Verbose, (false, true, false) => Verbosity::Verbose,
(false, true, true) => bail!("Invalid flags: -q conflicts with -v"), (false, true, true) => bail!("Invalid flags: -q conflicts with -v"),
}; };
let log_file = matches.opt_value_from_str("--log-file")?;
if matches.contains(["-h", "--help"]) {
eprintln!("{}", HELP);
return Ok(Args { verbosity, log_file: None, command: Command::Help });
}
let help = Ok(Args { verbosity, command: Command::Help });
let subcommand = match matches.subcommand()? { let subcommand = match matches.subcommand()? {
Some(it) => it, Some(it) => it,
None => { None => {
if matches.contains(["-h", "--help"]) {
print_subcommands();
return help;
}
matches.finish().or_else(handle_extra_flags)?; 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() { let command = match subcommand.as_str() {
"parse" => { "parse" => Command::Parse { no_dump: matches.contains("--no-dump") },
if matches.contains(["-h", "--help"]) { "symbols" => Command::Symbols,
eprintln!( "highlight" => Command::Highlight { rainbow: matches.contains("--rainbow") },
"\ "analysis-stats" => Command::AnalysisStats(AnalysisStatsCmd {
rust-analyzer parse randomize: matches.contains("--randomize"),
parallel: matches.contains("--parallel"),
USAGE: memory_usage: matches.contains("--memory-usage"),
rust-analyzer parse [FLAGS] only: matches.opt_value_from_str(["-o", "--only"])?,
with_deps: matches.contains("--with-deps"),
FLAGS: load_output_dirs: matches.contains("--load-output-dirs"),
-h, --help Prints help information with_proc_macro: matches.contains("--with-proc-macro"),
--no-dump" path: matches
); .free_from_str()?
return help; .ok_or_else(|| format_err!("expected positional argument"))?,
} }),
"analysis-bench" => Command::Bench(BenchCmd {
let no_dump = matches.contains("--no-dump"); what: {
matches.finish().or_else(handle_extra_flags)?; let highlight_path: Option<String> =
Command::Parse { no_dump } matches.opt_value_from_str("--highlight")?;
} let complete_path: Option<Position> =
"symbols" => { matches.opt_value_from_str("--complete")?;
if matches.contains(["-h", "--help"]) { let goto_def_path: Option<Position> =
eprintln!( matches.opt_value_from_str("--goto-def")?;
"\ match (highlight_path, complete_path, goto_def_path) {
rust-analyzer symbols
USAGE:
rust-analyzer highlight [FLAGS]
FLAGS:
-h, --help Prints help inforamtion"
);
return help;
}
matches.finish().or_else(handle_extra_flags)?;
Command::Symbols
}
"highlight" => {
if matches.contains(["-h", "--help"]) {
eprintln!(
"\
rust-analyzer highlight
USAGE:
rust-analyzer highlight [FLAGS]
FLAGS:
-h, --help Prints help information
-r, --rainbow"
);
return help;
}
let rainbow = matches.contains(["-r", "--rainbow"]);
matches.finish().or_else(handle_extra_flags)?;
Command::Highlight { rainbow }
}
"analysis-stats" => {
if matches.contains(["-h", "--help"]) {
eprintln!(
"\
rust-analyzer analysis-stats
USAGE:
rust-analyzer analysis-stats [FLAGS] [OPTIONS] [PATH]
FLAGS:
-o, --only Only analyze items matching this path
-h, --help Prints help information
--memory-usage Collect memory usage statistics
--randomize Randomize order in which crates, modules, and items are processed
--parallel Run type inference in parallel
--load-output-dirs Load OUT_DIR values by running `cargo check` before analysis
--with-proc-macro Use ra-proc-macro-srv for proc-macro expanding
--with-deps Also analyze all dependencies
-v, --verbose
-q, --quiet
OPTIONS:
-o <ONLY>
ARGS:
<PATH>"
);
return help;
}
let randomize = matches.contains("--randomize");
let parallel = matches.contains("--parallel");
let memory_usage = matches.contains("--memory-usage");
let only: Option<String> = matches.opt_value_from_str(["-o", "--only"])?;
let with_deps: bool = matches.contains("--with-deps");
let load_output_dirs = matches.contains("--load-output-dirs");
let with_proc_macro = matches.contains("--with-proc-macro");
let path = {
let mut trailing = matches.free()?;
if trailing.len() != 1 {
bail!("Invalid flags");
}
trailing.pop().unwrap().into()
};
Command::AnalysisStats(AnalysisStatsCmd {
randomize,
parallel,
memory_usage,
only,
with_deps,
path,
load_output_dirs,
with_proc_macro,
})
}
"analysis-bench" => {
if matches.contains(["-h", "--help"]) {
eprintln!(
"\
rust-analyzer analysis-bench
USAGE:
rust-analyzer analysis-bench [FLAGS] [OPTIONS]
FLAGS:
-h, --help Prints help information
--memory-usage Collect memory usage statistics
--load-output-dirs Load OUT_DIR values by running `cargo check` before analysis
--with-proc-macro Use ra-proc-macro-srv for proc-macro expanding
-v, --verbose
OPTIONS:
--project <PATH> Path to directory with Cargo.toml
--complete <PATH:LINE:COLUMN> Compute completions at this location
--goto-def <PATH:LINE:COLUMN> Compute goto definition at this location
--highlight <PATH> Hightlight this file
ARGS:
<PATH> Project to analyse"
);
return help;
}
let path: PathBuf = matches.opt_value_from_str("--project")?.unwrap_or_default();
let highlight_path: Option<String> = matches.opt_value_from_str("--highlight")?;
let complete_path: Option<Position> = matches.opt_value_from_str("--complete")?;
let goto_def_path: Option<Position> = matches.opt_value_from_str("--goto-def")?;
let what = match (highlight_path, complete_path, goto_def_path) {
(Some(path), None, None) => { (Some(path), None, None) => {
let path = env::current_dir().unwrap().join(path); let path = env::current_dir().unwrap().join(path);
BenchWhat::Highlight { path: AbsPathBuf::assert(path) } BenchWhat::Highlight { path: AbsPathBuf::assert(path) }
@ -239,140 +176,52 @@ ARGS:
_ => panic!( _ => panic!(
"exactly one of `--highlight`, `--complete` or `--goto-def` must be set" "exactly one of `--highlight`, `--complete` or `--goto-def` must be set"
), ),
};
let memory_usage = matches.contains("--memory-usage");
let load_output_dirs = matches.contains("--load-output-dirs");
let with_proc_macro = matches.contains("--with-proc-macro");
Command::Bench(BenchCmd {
memory_usage,
path,
what,
load_output_dirs,
with_proc_macro,
})
}
"diagnostics" => {
if matches.contains(["-h", "--help"]) {
eprintln!(
"\
rust-analyzer diagnostics
USAGE:
rust-analyzer diagnostics [FLAGS] [PATH]
FLAGS:
-h, --help Prints help information
--load-output-dirs Load OUT_DIR values by running `cargo check` before analysis
--all Include all files rather than only modules
ARGS:
<PATH>"
);
return help;
}
let load_output_dirs = matches.contains("--load-output-dirs");
let with_proc_macro = matches.contains("--with-proc-macro");
let all = matches.contains("--all");
let path = {
let mut trailing = matches.free()?;
if trailing.len() != 1 {
bail!("Invalid flags");
}
trailing.pop().unwrap().into()
};
Command::Diagnostics { path, load_output_dirs, with_proc_macro, all }
} }
},
memory_usage: matches.contains("--memory-usage"),
load_output_dirs: matches.contains("--load-output-dirs"),
with_proc_macro: matches.contains("--with-proc-macro"),
path: matches
.free_from_str()?
.ok_or_else(|| format_err!("expected positional argument"))?,
}),
"diagnostics" => Command::Diagnostics {
load_output_dirs: matches.contains("--load-output-dirs"),
with_proc_macro: matches.contains("--with-proc-macro"),
path: matches
.free_from_str()?
.ok_or_else(|| format_err!("expected positional argument"))?,
},
"proc-macro" => Command::ProcMacro, "proc-macro" => Command::ProcMacro,
"ssr" => { "ssr" => Command::Ssr {
if matches.contains(["-h", "--help"]) { rules: {
eprintln!( let mut acc = Vec::new();
"\
rust-analyzer ssr
USAGE:
rust-analyzer ssr [FLAGS] [RULE...]
EXAMPLE:
rust-analyzer ssr '$a.foo($b) ==> bar($a, $b)'
FLAGS:
--debug <snippet> Prints debug information for any nodes with source exactly equal to <snippet>
-h, --help Prints help information
ARGS:
<RULE> A structured search replace rule"
);
return help;
}
let mut rules = Vec::new();
while let Some(rule) = matches.free_from_str()? { while let Some(rule) = matches.free_from_str()? {
rules.push(rule); acc.push(rule);
} }
Command::Ssr { rules } acc
} },
"search" => { },
if matches.contains(["-h", "--help"]) { "search" => Command::StructuredSearch {
eprintln!( debug_snippet: matches.opt_value_from_str("--debug")?,
"\ patterns: {
rust-analyzer search let mut acc = Vec::new();
USAGE:
rust-analyzer search [FLAGS] [PATTERN...]
EXAMPLE:
rust-analyzer search '$a.foo($b)'
FLAGS:
--debug <snippet> Prints debug information for any nodes with source exactly equal to <snippet>
-h, --help Prints help information
ARGS:
<PATTERN> A structured search pattern"
);
return help;
}
let debug_snippet = matches.opt_value_from_str("--debug")?;
let mut patterns = Vec::new();
while let Some(rule) = matches.free_from_str()? { while let Some(rule) = matches.free_from_str()? {
patterns.push(rule); acc.push(rule);
}
Command::StructuredSearch { patterns, debug_snippet }
} }
acc
},
},
_ => { _ => {
print_subcommands(); eprintln!("{}", HELP);
return help; return Ok(Args { verbosity, log_file: None, command: Command::Help });
} }
}; };
Ok(Args { verbosity, command }) matches.finish().or_else(handle_extra_flags)?;
Ok(Args { verbosity, log_file, command })
} }
} }
fn print_subcommands() {
eprintln!(
"\
rust-analyzer
USAGE:
rust-analyzer <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
SUBCOMMANDS:
analysis-bench
analysis-stats
highlight
diagnostics
proc-macro
parse
search
ssr
symbols"
)
}
fn handle_extra_flags(e: pico_args::Error) -> Result<()> { fn handle_extra_flags(e: pico_args::Error) -> Result<()> {
if let pico_args::Error::UnusedArgsLeft(flags) = e { if let pico_args::Error::UnusedArgsLeft(flags) = e {
let mut invalid_flags = String::new(); let mut invalid_flags = String::new();

View File

@ -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();
}
}
}

View File

@ -2,8 +2,9 @@
//! //!
//! Based on cli flags, either spawns an LSP server, or runs a batch analysis //! Based on cli flags, either spawns an LSP server, or runs a batch analysis
mod args; mod args;
mod logger;
use std::{convert::TryFrom, process}; use std::{convert::TryFrom, env, fs, path::PathBuf, process};
use lsp_server::Connection; use lsp_server::Connection;
use project_model::ProjectManifest; use project_model::ProjectManifest;
@ -26,8 +27,8 @@ fn main() {
} }
fn try_main() -> Result<()> { fn try_main() -> Result<()> {
setup_logging()?;
let args = args::Args::parse()?; let args = args::Args::parse()?;
setup_logging(args.log_file)?;
match args.command { match args.command {
args::Command::RunServer => run_server()?, args::Command::RunServer => run_server()?,
args::Command::ProcMacro => proc_macro_srv::cli::run()?, args::Command::ProcMacro => proc_macro_srv::cli::run()?,
@ -37,8 +38,8 @@ fn try_main() -> Result<()> {
args::Command::Highlight { rainbow } => cli::highlight(rainbow)?, args::Command::Highlight { rainbow } => cli::highlight(rainbow)?,
args::Command::AnalysisStats(cmd) => cmd.run(args.verbosity)?, args::Command::AnalysisStats(cmd) => cmd.run(args.verbosity)?,
args::Command::Bench(cmd) => cmd.run(args.verbosity)?, args::Command::Bench(cmd) => cmd.run(args.verbosity)?,
args::Command::Diagnostics { path, load_output_dirs, with_proc_macro, all } => { args::Command::Diagnostics { path, load_output_dirs, with_proc_macro } => {
cli::diagnostics(path.as_ref(), load_output_dirs, with_proc_macro, all)? cli::diagnostics(path.as_ref(), load_output_dirs, with_proc_macro)?
} }
args::Command::Ssr { rules } => { args::Command::Ssr { rules } => {
cli::apply_ssr_rules(rules)?; cli::apply_ssr_rules(rules)?;
@ -52,9 +53,21 @@ fn try_main() -> Result<()> {
Ok(()) Ok(())
} }
fn setup_logging() -> Result<()> { fn setup_logging(log_file: Option<PathBuf>) -> Result<()> {
std::env::set_var("RUST_BACKTRACE", "short"); env::set_var("RUST_BACKTRACE", "short");
env_logger::try_init_from_env("RA_LOG")?;
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(); profile::init();
Ok(()) Ok(())
} }
@ -95,7 +108,7 @@ fn run_server() -> Result<()> {
{ {
Some(it) => it, Some(it) => it,
None => { None => {
let cwd = std::env::current_dir()?; let cwd = env::current_dir()?;
AbsPathBuf::assert(cwd) AbsPathBuf::assert(cwd)
} }
}; };

View File

@ -12,12 +12,7 @@ use ide::{DiagnosticsConfig, Severity};
use crate::cli::{load_cargo::load_cargo, Result}; use crate::cli::{load_cargo::load_cargo, Result};
pub fn diagnostics( pub fn diagnostics(path: &Path, load_output_dirs: bool, with_proc_macro: bool) -> Result<()> {
path: &Path,
load_output_dirs: bool,
with_proc_macro: bool,
_all: bool,
) -> Result<()> {
let (host, _vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?; let (host, _vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?;
let db = host.raw_database(); let db = host.raw_database();
let analysis = host.analysis(); let analysis = host.analysis();

View 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. 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 == Features