From 2daa362c320b7dcb06339bf3076ce45458e309e0 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sun, 4 Jan 2026 13:15:15 +0100 Subject: [PATCH 1/2] Rewatch: log errors to stderr --- rewatch/src/build.rs | 10 ++-- rewatch/src/main.rs | 47 +++++++++++++++++-- rewatch/tests/lock.sh | 2 +- .../build_tests/build_warn_as_error/input.js | 10 ++-- 4 files changed, 53 insertions(+), 16 deletions(-) diff --git a/rewatch/src/build.rs b/rewatch/src/build.rs index 5731048e7b..13d272cf8a 100644 --- a/rewatch/src/build.rs +++ b/rewatch/src/build.rs @@ -270,7 +270,7 @@ pub fn incremental_build( logs::finalize(&build_state.packages); if !plain_output && show_progress { - println!( + eprintln!( "{}{} {}Error parsing source files in {:.2}s", LINE_CLEAR, format_step(current_step, total_steps), @@ -280,7 +280,7 @@ pub fn incremental_build( pb.finish(); } - println!("{}", &err); + eprintln!("{}", &err); return Err(IncrementalBuildError { kind: IncrementalBuildErrorKind::SourceFileParseError, plain_output, @@ -358,9 +358,9 @@ pub fn incremental_build( if !compile_errors.is_empty() { if show_progress { if plain_output { - println!("Compiled {num_compiled_modules} modules") + eprintln!("Compiled {num_compiled_modules} modules") } else { - println!( + eprintln!( "{}{} {}Compiled {} modules in {:.2}s", LINE_CLEAR, format_step(current_step, total_steps), @@ -377,7 +377,7 @@ pub fn incremental_build( log_config_warnings(build_state); } if helpers::contains_ascii_characters(&compile_errors) { - println!("{}", &compile_errors); + eprintln!("{}", &compile_errors); } Err(IncrementalBuildError { kind: IncrementalBuildErrorKind::CompileError(None), diff --git a/rewatch/src/main.rs b/rewatch/src/main.rs index 4c9ece6018..f6451a5e88 100644 --- a/rewatch/src/main.rs +++ b/rewatch/src/main.rs @@ -10,11 +10,24 @@ fn main() -> Result<()> { let log_level_filter = cli.verbose.log_level_filter(); - env_logger::Builder::new() + let stdout_logger = env_logger::Builder::new() .format(|buf, record| writeln!(buf, "{}:\n{}", record.level(), record.args())) .filter_level(log_level_filter) .target(env_logger::fmt::Target::Stdout) - .init(); + .build(); + + let stderr_logger = env_logger::Builder::new() + .format(|buf, record| writeln!(buf, "{}:\n{}", record.level(), record.args())) + .filter_level(log_level_filter) + .target(env_logger::fmt::Target::Stderr) + .build(); + + log::set_max_level(log_level_filter); + log::set_boxed_logger(Box::new(SplitLogger { + stdout: stdout_logger, + stderr: stderr_logger, + })) + .expect("Failed to initialize logger"); let mut command = cli.command; @@ -62,7 +75,7 @@ fn main() -> Result<()> { (*build_args.warn_error).clone(), ) { Err(e) => { - println!("{:#}", e); + eprintln!("{:#}", e); std::process::exit(1) } Ok(_) => { @@ -98,7 +111,7 @@ fn main() -> Result<()> { (*watch_args.warn_error).clone(), ) { Err(e) => { - println!("{:#}", e); + eprintln!("{:#}", e); std::process::exit(1) } Ok(_) => Ok(()), @@ -134,9 +147,33 @@ fn main() -> Result<()> { fn get_lock(folder: &str) -> lock::Lock { match lock::get(folder) { lock::Lock::Error(error) => { - println!("Could not start ReScript build: {error}"); + eprintln!("Could not start ReScript build: {error}"); std::process::exit(1); } acquired_lock => acquired_lock, } } + +struct SplitLogger { + stdout: env_logger::Logger, + stderr: env_logger::Logger, +} + +impl log::Log for SplitLogger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + self.stdout.enabled(metadata) || self.stderr.enabled(metadata) + } + + fn log(&self, record: &log::Record) { + if record.level() == log::Level::Error { + self.stderr.log(record); + } else { + self.stdout.log(record); + } + } + + fn flush(&self) { + self.stdout.flush(); + self.stderr.flush(); + } +} diff --git a/rewatch/tests/lock.sh b/rewatch/tests/lock.sh index 9e56060340..41b12a506f 100755 --- a/rewatch/tests/lock.sh +++ b/rewatch/tests/lock.sh @@ -25,7 +25,7 @@ success "Watcher Started" sleep 2 -if rewatch build | grep 'Could not start ReScript build:' &> /dev/null; +if rewatch build 2>&1 | grep 'Could not start ReScript build:' &> /dev/null; then success "Lock is correctly set" exit_watcher diff --git a/tests/build_tests/build_warn_as_error/input.js b/tests/build_tests/build_warn_as_error/input.js index a23b667429..2d8fbe4733 100644 --- a/tests/build_tests/build_warn_as_error/input.js +++ b/tests/build_tests/build_warn_as_error/input.js @@ -14,32 +14,32 @@ const first_message = stripAnsi(o1.stdout) .find(s => s === "Warning number 110"); if (!first_message) { - assert.fail(o1.stdout); + assert.fail(o1.stdout + o1.stderr); } // Second build using --warn-error +110 const o2 = await execBuild(["--warn-error", "+110"]); -const second_message = stripAnsi(o2.stdout) +const second_message = stripAnsi(o2.stderr) .split("\n") .map(s => s.trim()) .find(s => s === "Warning number 110 (configured as error)"); if (!second_message) { - assert.fail(o2.stdout); + assert.fail(o2.stdout + o2.stderr); } // Third build, without --warn-error +110 // The result should not be a warning as error const o3 = await execBuild(); -const third_message = stripAnsi(o3.stdout) +const third_message = stripAnsi(o3.stderr) .split("\n") .map(s => s.trim()) .find(s => s === "Warning number 110 (configured as error)"); if (o3.status !== 0 || third_message) { - assert.fail(o3.stdout); + assert.fail(o3.stdout + o3.stderr); } await execClean(); From 368d110e813870b33d245c32bc5f100dd5c12ba8 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sun, 4 Jan 2026 13:27:47 +0100 Subject: [PATCH 2/2] CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e09352643..3a598484ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ #### :bug: Bug fix - Fix rewatch swallowing parse warnings (%todo). https://github.com/rescript-lang/rescript/pull/8135 +- Rewatch: log errors to `stderr`. https://github.com/rescript-lang/rescript/pull/8147 #### :memo: Documentation