I am trying to log information through stderr. It seems 'cargo test' does not capture stderr in the same way as it does to stdout. So when I run tests, the logging from stderr messes up with the output information from cargo. Is there any options that can control this?
You should probably not be using stderr, but the log
crate, which is much more expressive
Not that I know of. AFAIKT cargo test
does print on stdout
though, so you should be able to filter it out.
Maybe others will know better than me.
Yes. I am using log
crate along with stderrlog
crate, which outputs logs through stderr
. I could use something like env_logger
or simple_logger
which outputs to stdout
. However, I would like to keep stdout
clean. But stderr
output messes up with cargo test
.
Currently I am using shell output redirection (2> /dev/null
). However, it is not the best solution I guess, since it will omit any stderr
output from not only the tested program but also cargo
. I am wondering if cargo
provides any options to filter out stderr
of tested programs.
This may be bug. libtest.rs does stipulate that the test harness should capture both stdout
and stderr
. The functions responsible for that are std::io::{set_print, set_panic}
but they may be a bit unreliable (see #31334).
There seems to be a difference of behavior between the print{ln}!
and write{ln}!
macros because println!(...)
is captured in the tests but writeln!(io::stdout(), ...)
is not. Nor is writeln!(io::stderr(), ...)
.
Thanks a lot. That well explained my question. I checked stderrlog
, and it does use writeln!()
for stderr
output.
I am wondering, if libtest
uses set_print()
to capture the print!()
output, does that mean I am not supposed to use set_print()
in my code? Also how should I output to stderr
that can be captured by cargo test
, is there a viable way?
Well they're unstable so you/your users will need a nightly compiler. Also you can't promise stability given that unstable APIs can break at any time. Whether that's a problem depends on what you have in mind. I wouldn't expect them to disappear any time soon though, given that the compiler relies on them. Not until another proper capture mechanism is written.
If I understand the code properly, I don't think that's possible at the moment. write!
will use .write_fmt()
directly on stdout()
/ stderr()
whereas print!
and panic!
will try to use the LOCAL_STDOUT
and LOCAL_STDERR
sinks. What set_print
and set_panic
do is they swap out those sinks for their own so they get to decide what to do with the contents and wether to actually print them or not. write!
completely bypasses that.
AFAICT with a quick search in the rust repo, the only way to use LOCAL_STDOUT
is through print!
and the only way to use LOCAL_STDERR
is through panic!
. There's no way to write to stderr
and have it captured.
Hopefully it shouldn't take too long to get eprint!
and then stabilize it. Since the proposed implementation uses LOCAL_STDERR
that should solve your problem.
Thank you very much. It is a bit surprising that the implementation around this is so hacky. I guess there is no good solution for me then. I will just work around that.
I implemented customizable log targets for env_logger
: https://github.com/rust-lang-nursery/log/pull/105
When setting it to stdout
, the log output is captured by Cargo.
In the meantime the PR was merged, but later the code was changed again to use write!
which doesn't seem to be captured by libtest.
In case you use log4rs
, this appender seems to work:
/// An appender that uses println! for logging
/// so that the calls are captured by libtest.
#[derive(Debug)]
struct CapturedConsoleAppender {
encoder: Box<Encode>,
}
impl CapturedConsoleAppender {
fn new() -> Self {
CapturedConsoleAppender {
encoder: Box::new(PatternEncoder::default()),
}
}
}
impl Append for CapturedConsoleAppender {
fn append(&self, record: &Record) -> Result<(), Box<StdError + Sync + Send>> {
let mut writer = SimpleWriter(Vec::<u8>::new());
self.encoder.encode(&mut writer, record)?;
let line = str::from_utf8(&writer.0).unwrap();
println!("{}", line);
Ok(())
}
fn flush(&self) {}
}