1
Fork 0

Support arbitrary stdout/stderr/logger handles

This will allow capturing of common things like logging messages, stdout prints
(using stdio println), and failure messages (printed to stderr).  Any new prints
added to libstd should be funneled through these task handles to allow capture
as well.

Additionally, this commit redirects logging back through a `Logger` trait so the
log level can be usefully consumed by an arbitrary logger.

This commit also introduces methods to set the task-local stdout handles:

* std::io::stdio::set_stdout
* std::io::stdio::set_stderr
* std::io::logging::set_logger

These methods all return the previous logger just in case it needs to be used
for inspection.

I plan on using this infrastructure for extra::test soon, but we don't quite
have the primitives that I'd like to use for it, so it doesn't migrate
extra::test at this time.

Closes #6369
This commit is contained in:
Alex Crichton 2014-01-06 10:26:11 -08:00
parent a6d3e57dca
commit ac2a24ecc9
10 changed files with 330 additions and 160 deletions

View file

@ -118,13 +118,17 @@ impl GreenTask {
f: proc()) -> ~GreenTask { f: proc()) -> ~GreenTask {
let TaskOpts { let TaskOpts {
watched: _watched, watched: _watched,
notify_chan, name, stack_size notify_chan, name, stack_size,
stderr, stdout, logger,
} = opts; } = opts;
let mut green = GreenTask::new(pool, stack_size, f); let mut green = GreenTask::new(pool, stack_size, f);
{ {
let task = green.task.get_mut_ref(); let task = green.task.get_mut_ref();
task.name = name; task.name = name;
task.logger = logger;
task.stderr = stderr;
task.stdout = stdout;
match notify_chan { match notify_chan {
Some(chan) => { Some(chan) => {
let on_exit = proc(task_result) { chan.send(task_result) }; let on_exit = proc(task_result) { chan.send(task_result) };

View file

@ -34,7 +34,7 @@ pub mod io;
pub mod task; pub mod task;
// XXX: this should not exist here // XXX: this should not exist here
#[cfg(stage0)] #[cfg(stage0, nativestart)]
#[lang = "start"] #[lang = "start"]
pub fn lang_start(main: *u8, argc: int, argv: **u8) -> int { pub fn lang_start(main: *u8, argc: int, argv: **u8) -> int {
use std::cast; use std::cast;

View file

@ -55,11 +55,15 @@ pub fn spawn(f: proc()) {
pub fn spawn_opts(opts: TaskOpts, f: proc()) { pub fn spawn_opts(opts: TaskOpts, f: proc()) {
let TaskOpts { let TaskOpts {
watched: _watched, watched: _watched,
notify_chan, name, stack_size notify_chan, name, stack_size,
logger, stderr, stdout,
} = opts; } = opts;
let mut task = ~Task::new(); let mut task = ~Task::new();
task.name = name; task.name = name;
task.logger = logger;
task.stderr = stderr;
task.stdout = stdout;
match notify_chan { match notify_chan {
Some(chan) => { Some(chan) => {
let on_exit = proc(task_result) { chan.send(task_result) }; let on_exit = proc(task_result) { chan.send(task_result) };

View file

@ -32,8 +32,12 @@ use io::{Reader, Writer, io_error, IoError, OtherIoError,
standard_error, EndOfFile}; standard_error, EndOfFile};
use libc; use libc;
use option::{Option, Some, None}; use option::{Option, Some, None};
use prelude::drop;
use result::{Ok, Err}; use result::{Ok, Err};
use rt::local::Local;
use rt::rtio::{DontClose, IoFactory, LocalIo, RtioFileStream, RtioTTY}; use rt::rtio::{DontClose, IoFactory, LocalIo, RtioFileStream, RtioTTY};
use rt::task::Task;
use util;
// And so begins the tale of acquiring a uv handle to a stdio stream on all // And so begins the tale of acquiring a uv handle to a stdio stream on all
// platforms in all situations. Our story begins by splitting the world into two // platforms in all situations. Our story begins by splitting the world into two
@ -101,6 +105,44 @@ pub fn stderr() -> StdWriter {
src(libc::STDERR_FILENO, false, |src| StdWriter { inner: src }) src(libc::STDERR_FILENO, false, |src| StdWriter { inner: src })
} }
fn reset_helper(w: ~Writer,
f: |&mut Task, ~Writer| -> Option<~Writer>) -> Option<~Writer> {
let mut t = Local::borrow(None::<Task>);
// Be sure to flush any pending output from the writer
match f(t.get(), w) {
Some(mut w) => {
drop(t);
w.flush();
Some(w)
}
None => None
}
}
/// Resets the task-local stdout handle to the specified writer
///
/// This will replace the current task's stdout handle, returning the old
/// handle. All future calls to `print` and friends will emit their output to
/// this specified handle.
///
/// Note that this does not need to be called for all new tasks; the default
/// output handle is to the process's stdout stream.
pub fn set_stdout(stdout: ~Writer) -> Option<~Writer> {
reset_helper(stdout, |t, w| util::replace(&mut t.stdout, Some(w)))
}
/// Resets the task-local stderr handle to the specified writer
///
/// This will replace the current task's stderr handle, returning the old
/// handle. Currently, the stderr handle is used for printing failure messages
/// during task failure.
///
/// Note that this does not need to be called for all new tasks; the default
/// output handle is to the process's stderr stream.
pub fn set_stderr(stderr: ~Writer) -> Option<~Writer> {
reset_helper(stderr, |t, w| util::replace(&mut t.stderr, Some(w)))
}
// Helper to access the local task's stdout handle // Helper to access the local task's stdout handle
// //
// Note that this is not a safe function to expose because you can create an // Note that this is not a safe function to expose because you can create an
@ -112,38 +154,49 @@ pub fn stderr() -> StdWriter {
// }) // })
// }) // })
fn with_task_stdout(f: |&mut Writer|) { fn with_task_stdout(f: |&mut Writer|) {
use rt::local::Local; let task: Option<~Task> = Local::try_take();
use rt::task::Task; match task {
Some(mut task) => {
// Printing may run arbitrary code, so ensure that the task is in
// TLS to allow all std services. Note that this means a print while
// printing won't use the task's normal stdout handle, but this is
// necessary to ensure safety (no aliasing).
let mut my_stdout = task.stdout.take();
Local::put(task);
unsafe { if my_stdout.is_none() {
let task: Option<*mut Task> = Local::try_unsafe_borrow(); my_stdout = Some(~LineBufferedWriter::new(stdout()) as ~Writer);
match task { }
Some(task) => { f(*my_stdout.get_mut_ref());
match (*task).stdout_handle {
Some(ref mut handle) => f(*handle), // Note that we need to be careful when putting the stdout handle
None => { // back into the task. If the handle was set to `Some` while
let handle = ~LineBufferedWriter::new(stdout()); // printing, then we can run aribitrary code when destroying the
let mut handle = handle as ~Writer; // previous handle. This means that the local task needs to be in
f(handle); // TLS while we do this.
(*task).stdout_handle = Some(handle); //
// To protect against this, we do a little dance in which we
// temporarily take the task, swap the handles, put the task in TLS,
// and only then drop the previous handle.
let mut t = Local::borrow(None::<Task>);
let prev = util::replace(&mut t.get().stdout, my_stdout);
drop(t);
drop(prev);
}
None => {
struct Stdout;
impl Writer for Stdout {
fn write(&mut self, data: &[u8]) {
unsafe {
libc::write(libc::STDOUT_FILENO,
data.as_ptr() as *libc::c_void,
data.len() as libc::size_t);
} }
} }
} }
let mut io = Stdout;
None => { f(&mut io as &mut Writer);
struct Stdout;
impl Writer for Stdout {
fn write(&mut self, data: &[u8]) {
unsafe {
libc::write(libc::STDOUT_FILENO,
data.as_ptr() as *libc::c_void,
data.len() as libc::size_t);
}
}
}
let mut io = Stdout;
f(&mut io as &mut Writer);
}
} }
} }
} }
@ -313,4 +366,29 @@ mod tests {
stdout(); stdout();
stderr(); stderr();
}) })
iotest!(fn capture_stdout() {
use io::comm_adapters::{PortReader, ChanWriter};
let (p, c) = Chan::new();
let (mut r, w) = (PortReader::new(p), ChanWriter::new(c));
do spawn {
set_stdout(~w as ~Writer);
println!("hello!");
}
assert_eq!(r.read_to_str(), ~"hello!\n");
})
iotest!(fn capture_stderr() {
use io::comm_adapters::{PortReader, ChanWriter};
let (p, c) = Chan::new();
let (mut r, w) = (PortReader::new(p), ChanWriter::new(c));
do spawn {
set_stderr(~w as ~Writer);
fail!("my special message");
}
let s = r.read_to_str();
assert!(s.contains("my special message"));
})
} }

View file

@ -96,10 +96,15 @@ start, print out all modules registered for logging, and then exit.
*/ */
use fmt; use fmt;
use option::*; use io::buffered::LineBufferedWriter;
use io;
use io::Writer;
use ops::Drop;
use option::{Some, None, Option};
use prelude::drop;
use rt::local::Local; use rt::local::Local;
use rt::logging::{Logger, StdErrLogger};
use rt::task::Task; use rt::task::Task;
use util;
/// Debug log level /// Debug log level
pub static DEBUG: u32 = 4; pub static DEBUG: u32 = 4;
@ -110,6 +115,32 @@ pub static WARN: u32 = 2;
/// Error log level /// Error log level
pub static ERROR: u32 = 1; pub static ERROR: u32 = 1;
/// A trait used to represent an interface to a task-local logger. Each task
/// can have its own custom logger which can respond to logging messages
/// however it likes.
pub trait Logger {
/// Logs a single message described by the `args` structure. The level is
/// provided in case you want to do things like color the message, etc.
fn log(&mut self, level: u32, args: &fmt::Arguments);
}
struct DefaultLogger {
handle: LineBufferedWriter<io::stdio::StdWriter>,
}
impl Logger for DefaultLogger {
// by default, just ignore the level
fn log(&mut self, _level: u32, args: &fmt::Arguments) {
fmt::writeln(&mut self.handle, args);
}
}
impl Drop for DefaultLogger {
fn drop(&mut self) {
self.handle.flush();
}
}
/// This function is called directly by the compiler when using the logging /// This function is called directly by the compiler when using the logging
/// macros. This function does not take into account whether the log level /// macros. This function does not take into account whether the log level
/// specified is active or not, it will always log something if this method is /// specified is active or not, it will always log something if this method is
@ -117,17 +148,32 @@ pub static ERROR: u32 = 1;
/// ///
/// It is not recommended to call this function directly, rather it should be /// It is not recommended to call this function directly, rather it should be
/// invoked through the logging family of macros. /// invoked through the logging family of macros.
pub fn log(_level: u32, args: &fmt::Arguments) { pub fn log(level: u32, args: &fmt::Arguments) {
// See io::stdio::with_task_stdout for why there's a few dances here. The
// gist of it is that arbitrary code can run during logging (and set an
// arbitrary logging handle into the task) so we need to be careful that the
// local task is in TLS while we're running arbitrary code.
let mut logger = { let mut logger = {
let mut task = Local::borrow(None::<Task>); let mut task = Local::borrow(None::<Task>);
task.get().logger.take() task.get().logger.take()
}; };
if logger.is_none() { if logger.is_none() {
logger = Some(StdErrLogger::new()); logger = Some(~DefaultLogger {
handle: LineBufferedWriter::new(io::stderr()),
} as ~Logger);
} }
logger.get_mut_ref().log(args); logger.get_mut_ref().log(level, args);
let mut task = Local::borrow(None::<Task>); let mut task = Local::borrow(None::<Task>);
task.get().logger = logger; let prev = util::replace(&mut task.get().logger, logger);
drop(task);
drop(prev);
}
/// Replaces the task-local logger with the specified logger, returning the old
/// logger.
pub fn set_logger(logger: ~Logger) -> Option<~Logger> {
let mut task = Local::borrow(None::<Task>);
util::replace(&mut task.get().logger, Some(logger))
} }

View file

@ -8,13 +8,9 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
use fmt;
use from_str::from_str; use from_str::from_str;
use libc::exit; use libc::exit;
use option::{Some, None, Option}; use option::{Some, None, Option};
use io;
use io::stdio::StdWriter;
use io::buffered::LineBufferedWriter;
use rt::crate_map::{ModEntry, CrateMap, iter_crate_map, get_crate_map}; use rt::crate_map::{ModEntry, CrateMap, iter_crate_map, get_crate_map};
use str::StrSlice; use str::StrSlice;
use vec::{ImmutableVector, MutableTotalOrdVector}; use vec::{ImmutableVector, MutableTotalOrdVector};
@ -168,28 +164,6 @@ fn update_log_settings(crate_map: &CrateMap, settings: ~str) {
} }
} }
pub trait Logger {
fn log(&mut self, args: &fmt::Arguments);
}
/// This logger emits output to the stderr of the process, and contains a lazily
/// initialized event-loop driven handle to the stream.
pub struct StdErrLogger {
priv handle: LineBufferedWriter<StdWriter>,
}
impl StdErrLogger {
pub fn new() -> StdErrLogger {
StdErrLogger { handle: LineBufferedWriter::new(io::stderr()) }
}
}
impl Logger for StdErrLogger {
fn log(&mut self, args: &fmt::Arguments) {
fmt::writeln(&mut self.handle as &mut io::Writer, args);
}
}
/// Configure logging by traversing the crate map and setting the /// Configure logging by traversing the crate map and setting the
/// per-module global logging flags based on the logging spec /// per-module global logging flags based on the logging spec
pub fn init() { pub fn init() {

View file

@ -20,6 +20,7 @@ use cleanup;
use io::Writer; use io::Writer;
use iter::{Iterator, Take}; use iter::{Iterator, Take};
use local_data; use local_data;
use logging::Logger;
use ops::Drop; use ops::Drop;
use option::{Option, Some, None}; use option::{Option, Some, None};
use prelude::drop; use prelude::drop;
@ -29,7 +30,6 @@ use rt::borrowck::BorrowRecord;
use rt::borrowck; use rt::borrowck;
use rt::local::Local; use rt::local::Local;
use rt::local_heap::LocalHeap; use rt::local_heap::LocalHeap;
use rt::logging::StdErrLogger;
use rt::rtio::LocalIo; use rt::rtio::LocalIo;
use rt::unwind::Unwinder; use rt::unwind::Unwinder;
use send_str::SendStr; use send_str::SendStr;
@ -58,8 +58,9 @@ pub struct Task {
// Dynamic borrowck debugging info // Dynamic borrowck debugging info
borrow_list: Option<~[BorrowRecord]>, borrow_list: Option<~[BorrowRecord]>,
logger: Option<StdErrLogger>, logger: Option<~Logger>,
stdout_handle: Option<~Writer>, stdout: Option<~Writer>,
stderr: Option<~Writer>,
priv imp: Option<~Runtime>, priv imp: Option<~Runtime>,
} }
@ -97,7 +98,8 @@ impl Task {
name: None, name: None,
borrow_list: None, borrow_list: None,
logger: None, logger: None,
stdout_handle: None, stdout: None,
stderr: None,
imp: None, imp: None,
} }
} }
@ -126,13 +128,21 @@ impl Task {
// Run the task main function, then do some cleanup. // Run the task main function, then do some cleanup.
f.finally(|| { f.finally(|| {
fn flush(w: Option<~Writer>) { fn close_outputs() {
match w { let mut task = Local::borrow(None::<Task>);
Some(mut w) => { w.flush(); } let logger = task.get().logger.take();
None => {} let stderr = task.get().stderr.take();
} let stdout = task.get().stdout.take();
drop(task);
drop(logger); // loggers are responsible for flushing
match stdout { Some(mut w) => w.flush(), None => {} }
match stderr { Some(mut w) => w.flush(), None => {} }
} }
// First, flush/destroy the user stdout/logger because these
// destructors can run arbitrary code.
close_outputs();
// First, destroy task-local storage. This may run user dtors. // First, destroy task-local storage. This may run user dtors.
// //
// FIXME #8302: Dear diary. I'm so tired and confused. // FIXME #8302: Dear diary. I'm so tired and confused.
@ -164,16 +174,12 @@ impl Task {
// Destroy remaining boxes. Also may run user dtors. // Destroy remaining boxes. Also may run user dtors.
unsafe { cleanup::annihilate(); } unsafe { cleanup::annihilate(); }
// Finally flush and destroy any output handles which the task // Finally, just in case user dtors printed/logged during TLS
// owns. There are no boxes here, and no user destructors should // cleanup and annihilation, re-destroy stdout and the logger.
// run after this any more. // Note that these will have been initialized with a
let mut task = Local::borrow(None::<Task>); // runtime-provided type which we have control over what the
let stdout = task.get().stdout_handle.take(); // destructor does.
let logger = task.get().logger.take(); close_outputs();
drop(task);
flush(stdout);
drop(logger);
}) })
}; };

View file

@ -61,12 +61,14 @@ use cast;
use kinds::Send; use kinds::Send;
use libc::{c_void, c_char, size_t}; use libc::{c_void, c_char, size_t};
use option::{Some, None, Option}; use option::{Some, None, Option};
use prelude::drop;
use result::{Err, Ok}; use result::{Err, Ok};
use rt::local::Local; use rt::local::Local;
use rt::task::Task; use rt::task::Task;
use str::Str; use str::Str;
use task::TaskResult; use task::TaskResult;
use unstable::intrinsics; use unstable::intrinsics;
use util;
use uw = self::libunwind; use uw = self::libunwind;
@ -385,58 +387,90 @@ pub fn begin_unwind_raw(msg: *c_char, file: *c_char, line: size_t) -> ! {
/// This is the entry point of unwinding for fail!() and assert!(). /// This is the entry point of unwinding for fail!() and assert!().
pub fn begin_unwind<M: Any + Send>(msg: M, file: &'static str, line: uint) -> ! { pub fn begin_unwind<M: Any + Send>(msg: M, file: &'static str, line: uint) -> ! {
unsafe { // Note that this should be the only allocation performed in this block.
let task: *mut Task; // Currently this means that fail!() on OOM will invoke this code path,
// Note that this should be the only allocation performed in this block. // but then again we're not really ready for failing on OOM anyway. If
// Currently this means that fail!() on OOM will invoke this code path, // we do start doing this, then we should propagate this allocation to
// but then again we're not really ready for failing on OOM anyway. If // be performed in the parent of this task instead of the task that's
// we do start doing this, then we should propagate this allocation to // failing.
// be performed in the parent of this task instead of the task that's let msg = ~msg as ~Any;
// failing.
let msg = ~msg as ~Any;
let mut task;
{
let msg_s = match msg.as_ref::<&'static str>() {
Some(s) => *s,
None => match msg.as_ref::<~str>() {
Some(s) => s.as_slice(),
None => "~Any",
}
};
// It is assumed that all reasonable rust code will have a local task at
// all times. This means that this `try_take` will succeed almost all of
// the time. There are border cases, however, when the runtime has
// *almost* set up the local task, but hasn't quite gotten there yet. In
// order to get some better diagnostics, we print on failure and
// immediately abort the whole process if there is no local task
// available.
let opt_task: Option<~Task> = Local::try_take();
task = match opt_task {
Some(t) => t,
None => {
rterrln!("failed at '{}', {}:{}", msg_s, file, line);
unsafe { intrinsics::abort() }
}
};
// See comments in io::stdio::with_task_stdout as to why we have to be
// careful when using an arbitrary I/O handle from the task. We
// essentially need to dance to make sure when a task is in TLS when
// running user code.
let name = task.name.take();
{ {
let msg_s = match msg.as_ref::<&'static str>() { let n = name.as_ref().map(|n| n.as_slice()).unwrap_or("<unnamed>");
Some(s) => *s,
None => match msg.as_ref::<~str>() { match task.stderr.take() {
Some(s) => s.as_slice(), Some(mut stderr) => {
None => "~Any", Local::put(task);
format_args!(|args| ::fmt::writeln(stderr, args),
"task '{}' failed at '{}', {}:{}",
n, msg_s, file, line);
task = Local::take();
match util::replace(&mut task.stderr, Some(stderr)) {
Some(prev) => {
Local::put(task);
drop(prev);
task = Local::take();
}
None => {}
}
} }
}; None => {
// It is assumed that all reasonable rust code will have a local
// task at all times. This means that this `try_unsafe_borrow` will
// succeed almost all of the time. There are border cases, however,
// when the runtime has *almost* set up the local task, but hasn't
// quite gotten there yet. In order to get some better diagnostics,
// we print on failure and immediately abort the whole process if
// there is no local task available.
match Local::try_unsafe_borrow() {
Some(t) => {
task = t;
let n = (*task).name.as_ref()
.map(|n| n.as_slice()).unwrap_or("<unnamed>");
rterrln!("task '{}' failed at '{}', {}:{}", n, msg_s, rterrln!("task '{}' failed at '{}', {}:{}", n, msg_s,
file, line); file, line);
} }
None => {
rterrln!("failed at '{}', {}:{}", msg_s, file, line);
intrinsics::abort();
}
}
if (*task).unwinder.unwinding {
// If a task fails while it's already unwinding then we
// have limited options. Currently our preference is to
// just abort. In the future we may consider resuming
// unwinding or otherwise exiting the task cleanly.
rterrln!("task failed during unwinding (double-failure - total drag!)")
rterrln!("rust must abort now. so sorry.");
intrinsics::abort();
} }
} }
task.name = name;
if task.unwinder.unwinding {
// If a task fails while it's already unwinding then we
// have limited options. Currently our preference is to
// just abort. In the future we may consider resuming
// unwinding or otherwise exiting the task cleanly.
rterrln!("task failed during unwinding (double-failure - total drag!)")
rterrln!("rust must abort now. so sorry.");
unsafe { intrinsics::abort() }
}
}
// The unwinder won't actually use the task at all, so we put the task back
// into TLS right before we invoke the unwinder, but this means we need an
// unsafe reference back to the unwinder once it's in TLS.
Local::put(task);
unsafe {
let task: *mut Task = Local::unsafe_borrow();
(*task).unwinder.begin_unwind(msg); (*task).unwinder.begin_unwind(msg);
} }
} }

View file

@ -55,7 +55,9 @@
use any::Any; use any::Any;
use comm::{Chan, Port}; use comm::{Chan, Port};
use io::Writer;
use kinds::Send; use kinds::Send;
use logging::Logger;
use option::{None, Some, Option}; use option::{None, Some, Option};
use result::{Result, Ok, Err}; use result::{Result, Ok, Err};
use rt::local::Local; use rt::local::Local;
@ -103,7 +105,10 @@ pub struct TaskOpts {
watched: bool, watched: bool,
notify_chan: Option<Chan<TaskResult>>, notify_chan: Option<Chan<TaskResult>>,
name: Option<SendStr>, name: Option<SendStr>,
stack_size: Option<uint> stack_size: Option<uint>,
logger: Option<~Logger>,
stdout: Option<~Writer>,
stderr: Option<~Writer>,
} }
/** /**
@ -138,22 +143,6 @@ pub fn task() -> TaskBuilder {
} }
impl TaskBuilder { impl TaskBuilder {
fn consume(mut self) -> TaskBuilder {
let gen_body = self.gen_body.take();
let notify_chan = self.opts.notify_chan.take();
let name = self.opts.name.take();
TaskBuilder {
opts: TaskOpts {
watched: self.opts.watched,
notify_chan: notify_chan,
name: name,
stack_size: self.opts.stack_size
},
gen_body: gen_body,
can_not_copy: None,
}
}
/// Cause the parent task to collect the child's exit status (and that of /// Cause the parent task to collect the child's exit status (and that of
/// all transitively-watched grandchildren) before reporting its own. /// all transitively-watched grandchildren) before reporting its own.
pub fn watched(&mut self) { pub fn watched(&mut self) {
@ -250,26 +239,12 @@ impl TaskBuilder {
*/ */
pub fn spawn(mut self, f: proc()) { pub fn spawn(mut self, f: proc()) {
let gen_body = self.gen_body.take(); let gen_body = self.gen_body.take();
let notify_chan = self.opts.notify_chan.take();
let name = self.opts.name.take();
let x = self.consume();
let opts = TaskOpts {
watched: x.opts.watched,
notify_chan: notify_chan,
name: name,
stack_size: x.opts.stack_size
};
let f = match gen_body { let f = match gen_body {
Some(gen) => { Some(gen) => gen(f),
gen(f) None => f
}
None => {
f
}
}; };
let t: ~Task = Local::take(); let t: ~Task = Local::take();
t.spawn_sibling(opts, f); t.spawn_sibling(self.opts, f);
} }
/** /**
@ -316,7 +291,10 @@ impl TaskOpts {
watched: true, watched: true,
notify_chan: None, notify_chan: None,
name: None, name: None,
stack_size: None stack_size: None,
logger: None,
stdout: None,
stderr: None,
} }
} }
} }

View file

@ -0,0 +1,46 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// xfail-fast
// exec-env:RUST_LOG=info
#[no_uv];
extern mod native;
use std::fmt;
use std::io::comm_adapters::{PortReader, ChanWriter};
use std::logging::{set_logger, Logger};
struct MyWriter(ChanWriter);
impl Logger for MyWriter {
fn log(&mut self, _level: u32, args: &fmt::Arguments) {
let MyWriter(ref mut inner) = *self;
fmt::writeln(inner as &mut Writer, args);
}
}
#[start]
fn start(argc: int, argv: **u8) -> int {
do native::start(argc, argv) {
main();
}
}
fn main() {
let (p, c) = Chan::new();
let (mut r, w) = (PortReader::new(p), ChanWriter::new(c));
do spawn {
set_logger(~MyWriter(w) as ~Logger);
debug!("debug");
info!("info");
}
assert_eq!(r.read_to_str(), ~"info\n");
}