7457: Add no-buffering file logging and wait for a debugger option. r=vsrs a=vsrs

Adds two command line flags: `--no-buffering` and `--wait-dbg`. 

Not  sure if someone else needs this, but personally I found both flags extremely useful trying to figure out why RA does not work with Visual Studio. Or better to say why Visual Studio does not work with RA.

Co-authored-by: vsrs <vit@conrlab.com>
This commit is contained in:
bors[bot] 2021-01-26 22:37:11 +00:00 committed by GitHub
commit fc08fdaf5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 97 additions and 21 deletions

6
.vscode/launch.json vendored
View File

@ -120,6 +120,12 @@
"sourceMaps": true, "sourceMaps": true,
"outFiles": [ "${workspaceFolder}/editors/code/out/tests/unit/**/*.js" ], "outFiles": [ "${workspaceFolder}/editors/code/out/tests/unit/**/*.js" ],
"preLaunchTask": "Pretest" "preLaunchTask": "Pretest"
},
{
"name": "Win Attach to Server",
"type": "cppvsdbg",
"processId":"${command:pickProcess}",
"request": "attach"
} }
] ]
} }

View File

@ -14,7 +14,9 @@ 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) log_file: Option<PathBuf>,
pub(crate) no_buffering: bool,
pub(crate) command: Command, pub(crate) command: Command,
pub(crate) wait_dbg: bool,
} }
pub(crate) enum Command { pub(crate) enum Command {
@ -47,11 +49,17 @@ FLAGS:
-vv, --spammy -vv, --spammy
-q, --quiet Set verbosity -q, --quiet Set verbosity
--log-file <PATH> Log to the specified filed instead of stderr --log-file <PATH> Log to the specified file instead of stderr
--no-log-buffering
Flush log records to the file immediately
--wait-dbg Wait until a debugger is attached to.
The flag is valid for debug builds only
ENVIRONMENTAL VARIABLES: ENVIRONMENTAL VARIABLES:
RA_LOG Set log filter in env_logger format RA_LOG Set log filter in env_logger format
RA_PROFILE Enable hierarchical profiler RA_PROFILE Enable hierarchical profiler
RA_WAIT_DBG If set acts like a --wait-dbg flag
COMMANDS: COMMANDS:
@ -114,6 +122,8 @@ impl Args {
verbosity: Verbosity::Normal, verbosity: Verbosity::Normal,
log_file: None, log_file: None,
command: Command::Version, command: Command::Version,
no_buffering: false,
wait_dbg: false,
}); });
} }
@ -130,21 +140,41 @@ impl Args {
(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")?; let log_file = matches.opt_value_from_str("--log-file")?;
let no_buffering = matches.contains("--no-log-buffering");
let wait_dbg = matches.contains("--wait-dbg");
if matches.contains(["-h", "--help"]) { if matches.contains(["-h", "--help"]) {
eprintln!("{}", HELP); eprintln!("{}", HELP);
return Ok(Args { verbosity, log_file: None, command: Command::Help }); return Ok(Args {
verbosity,
log_file: None,
command: Command::Help,
no_buffering,
wait_dbg,
});
} }
if matches.contains("--print-config-schema") { if matches.contains("--print-config-schema") {
return Ok(Args { verbosity, log_file, command: Command::PrintConfigSchema }); return Ok(Args {
verbosity,
log_file,
command: Command::PrintConfigSchema,
no_buffering,
wait_dbg,
});
} }
let subcommand = match matches.subcommand()? { let subcommand = match matches.subcommand()? {
Some(it) => it, Some(it) => it,
None => { None => {
finish_args(matches)?; finish_args(matches)?;
return Ok(Args { verbosity, log_file, command: Command::RunServer }); return Ok(Args {
verbosity,
log_file,
command: Command::RunServer,
no_buffering,
wait_dbg,
});
} }
}; };
let command = match subcommand.as_str() { let command = match subcommand.as_str() {
@ -219,11 +249,17 @@ impl Args {
}, },
_ => { _ => {
eprintln!("{}", HELP); eprintln!("{}", HELP);
return Ok(Args { verbosity, log_file: None, command: Command::Help }); return Ok(Args {
verbosity,
log_file: None,
command: Command::Help,
no_buffering,
wait_dbg,
});
} }
}; };
finish_args(matches)?; finish_args(matches)?;
Ok(Args { verbosity, log_file, command }) Ok(Args { verbosity, log_file, command, no_buffering, wait_dbg })
} }
} }

View File

@ -4,7 +4,7 @@
use std::{ use std::{
fs::File, fs::File,
io::{BufWriter, Write}, io::{self, BufWriter, Write},
}; };
use env_logger::filter::{Builder, Filter}; use env_logger::filter::{Builder, Filter};
@ -14,10 +14,11 @@ use parking_lot::Mutex;
pub(crate) struct Logger { pub(crate) struct Logger {
filter: Filter, filter: Filter,
file: Option<Mutex<BufWriter<File>>>, file: Option<Mutex<BufWriter<File>>>,
no_buffering: bool,
} }
impl Logger { impl Logger {
pub(crate) fn new(log_file: Option<File>, filter: Option<&str>) -> Logger { pub(crate) fn new(log_file: Option<File>, no_buffering: bool, filter: Option<&str>) -> Logger {
let filter = { let filter = {
let mut builder = Builder::new(); let mut builder = Builder::new();
if let Some(filter) = filter { if let Some(filter) = filter {
@ -28,7 +29,7 @@ impl Logger {
let file = log_file.map(|it| Mutex::new(BufWriter::new(it))); let file = log_file.map(|it| Mutex::new(BufWriter::new(it)));
Logger { filter, file } Logger { filter, file, no_buffering }
} }
pub(crate) fn install(self) { pub(crate) fn install(self) {
@ -46,7 +47,8 @@ impl Log for Logger {
if !self.filter.matches(record) { if !self.filter.matches(record) {
return; return;
} }
match &self.file {
let should_flush = match &self.file {
Some(w) => { Some(w) => {
let _ = writeln!( let _ = writeln!(
w.lock(), w.lock(),
@ -55,19 +57,32 @@ impl Log for Logger {
record.module_path().unwrap_or_default(), record.module_path().unwrap_or_default(),
record.args(), record.args(),
); );
self.no_buffering
} }
None => eprintln!( None => {
eprintln!(
"[{} {}] {}", "[{} {}] {}",
record.level(), record.level(),
record.module_path().unwrap_or_default(), record.module_path().unwrap_or_default(),
record.args(), record.args(),
), );
true // flush stderr unconditionally
}
};
if should_flush {
self.flush();
} }
} }
fn flush(&self) { fn flush(&self) {
if let Some(w) = &self.file { match &self.file {
Some(w) => {
let _ = w.lock().flush(); let _ = w.lock().flush();
} }
None => {
let _ = io::stderr().flush();
}
}
} }
} }

View File

@ -21,6 +21,7 @@ static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
fn main() { fn main() {
if let Err(err) = try_main() { if let Err(err) = try_main() {
log::error!("Unexpected error: {}", err);
eprintln!("{}", err); eprintln!("{}", err);
process::exit(101); process::exit(101);
} }
@ -28,7 +29,17 @@ fn main() {
fn try_main() -> Result<()> { fn try_main() -> Result<()> {
let args = args::Args::parse()?; let args = args::Args::parse()?;
setup_logging(args.log_file)?;
#[cfg(debug_assertions)]
if args.wait_dbg || env::var("RA_WAIT_DBG").is_ok() {
#[allow(unused_mut)]
let mut d = 4;
while d == 4 {
d = 4;
}
}
setup_logging(args.log_file, args.no_buffering)?;
match args.command { match args.command {
args::Command::RunServer => run_server()?, args::Command::RunServer => run_server()?,
args::Command::PrintConfigSchema => { args::Command::PrintConfigSchema => {
@ -56,7 +67,7 @@ fn try_main() -> Result<()> {
Ok(()) Ok(())
} }
fn setup_logging(log_file: Option<PathBuf>) -> Result<()> { fn setup_logging(log_file: Option<PathBuf>, no_buffering: bool) -> Result<()> {
env::set_var("RUST_BACKTRACE", "short"); env::set_var("RUST_BACKTRACE", "short");
let log_file = match log_file { let log_file = match log_file {
@ -69,7 +80,7 @@ fn setup_logging(log_file: Option<PathBuf>) -> Result<()> {
None => None, None => None,
}; };
let filter = env::var("RA_LOG").ok(); let filter = env::var("RA_LOG").ok();
logger::Logger::new(log_file, filter.as_deref()).install(); logger::Logger::new(log_file, no_buffering, filter.as_deref()).install();
tracing_setup::setup_tracing()?; tracing_setup::setup_tracing()?;

View File

@ -57,6 +57,14 @@ To apply changes to an already running debug process, press <kbd>Ctrl+Shift+P</k
- Go back to the `[Extension Development Host]` instance and hover over a Rust variable and your breakpoint should hit. - Go back to the `[Extension Development Host]` instance and hover over a Rust variable and your breakpoint should hit.
If you need to debug the server from the very beginning, including its initialization code, you can use the `--wait-dbg` command line argument or `RA_WAIT_DBG` environment variable. The server will spin at the beginning of the `try_main` function (see `crates\rust-analyzer\src\bin\main.rs`)
```rust
let mut d = 4;
while d == 4 { // set a breakpoint here and change the value
d = 4;
}
```
## Demo ## Demo
- [Debugging TypeScript VScode extension](https://www.youtube.com/watch?v=T-hvpK6s4wM). - [Debugging TypeScript VScode extension](https://www.youtube.com/watch?v=T-hvpK6s4wM).