Skip to content

Commit 7dbd12a

Browse files
committed
auto merge of #11353 : alexcrichton/rust/improve-logging, r=brson
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
2 parents 3912a87 + ac2a24e commit 7dbd12a

File tree

9 files changed

+328
-158
lines changed

9 files changed

+328
-158
lines changed

src/libgreen/task.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,17 @@ impl GreenTask {
118118
f: proc()) -> ~GreenTask {
119119
let TaskOpts {
120120
watched: _watched,
121-
notify_chan, name, stack_size
121+
notify_chan, name, stack_size,
122+
stderr, stdout, logger,
122123
} = opts;
123124

124125
let mut green = GreenTask::new(pool, stack_size, f);
125126
{
126127
let task = green.task.get_mut_ref();
127128
task.name = name;
129+
task.logger = logger;
130+
task.stderr = stderr;
131+
task.stdout = stdout;
128132
match notify_chan {
129133
Some(chan) => {
130134
let on_exit = proc(task_result) { chan.send(task_result) };

src/libnative/task.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,15 @@ pub fn spawn(f: proc()) {
5555
pub fn spawn_opts(opts: TaskOpts, f: proc()) {
5656
let TaskOpts {
5757
watched: _watched,
58-
notify_chan, name, stack_size
58+
notify_chan, name, stack_size,
59+
logger, stderr, stdout,
5960
} = opts;
6061

6162
let mut task = ~Task::new();
6263
task.name = name;
64+
task.logger = logger;
65+
task.stderr = stderr;
66+
task.stdout = stdout;
6367
match notify_chan {
6468
Some(chan) => {
6569
let on_exit = proc(task_result) { chan.send(task_result) };

src/libstd/io/stdio.rs

+105-27
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ use io::{Reader, Writer, io_error, IoError, OtherIoError,
3232
standard_error, EndOfFile};
3333
use libc;
3434
use option::{Option, Some, None};
35+
use prelude::drop;
3536
use result::{Ok, Err};
37+
use rt::local::Local;
3638
use rt::rtio::{DontClose, IoFactory, LocalIo, RtioFileStream, RtioTTY};
39+
use rt::task::Task;
40+
use util;
3741

3842
// And so begins the tale of acquiring a uv handle to a stdio stream on all
3943
// platforms in all situations. Our story begins by splitting the world into two
@@ -101,6 +105,44 @@ pub fn stderr() -> StdWriter {
101105
src(libc::STDERR_FILENO, false, |src| StdWriter { inner: src })
102106
}
103107

108+
fn reset_helper(w: ~Writer,
109+
f: |&mut Task, ~Writer| -> Option<~Writer>) -> Option<~Writer> {
110+
let mut t = Local::borrow(None::<Task>);
111+
// Be sure to flush any pending output from the writer
112+
match f(t.get(), w) {
113+
Some(mut w) => {
114+
drop(t);
115+
w.flush();
116+
Some(w)
117+
}
118+
None => None
119+
}
120+
}
121+
122+
/// Resets the task-local stdout handle to the specified writer
123+
///
124+
/// This will replace the current task's stdout handle, returning the old
125+
/// handle. All future calls to `print` and friends will emit their output to
126+
/// this specified handle.
127+
///
128+
/// Note that this does not need to be called for all new tasks; the default
129+
/// output handle is to the process's stdout stream.
130+
pub fn set_stdout(stdout: ~Writer) -> Option<~Writer> {
131+
reset_helper(stdout, |t, w| util::replace(&mut t.stdout, Some(w)))
132+
}
133+
134+
/// Resets the task-local stderr handle to the specified writer
135+
///
136+
/// This will replace the current task's stderr handle, returning the old
137+
/// handle. Currently, the stderr handle is used for printing failure messages
138+
/// during task failure.
139+
///
140+
/// Note that this does not need to be called for all new tasks; the default
141+
/// output handle is to the process's stderr stream.
142+
pub fn set_stderr(stderr: ~Writer) -> Option<~Writer> {
143+
reset_helper(stderr, |t, w| util::replace(&mut t.stderr, Some(w)))
144+
}
145+
104146
// Helper to access the local task's stdout handle
105147
//
106148
// Note that this is not a safe function to expose because you can create an
@@ -112,38 +154,49 @@ pub fn stderr() -> StdWriter {
112154
// })
113155
// })
114156
fn with_task_stdout(f: |&mut Writer|) {
115-
use rt::local::Local;
116-
use rt::task::Task;
117-
118-
unsafe {
119-
let task: Option<*mut Task> = Local::try_unsafe_borrow();
120-
match task {
121-
Some(task) => {
122-
match (*task).stdout_handle {
123-
Some(ref mut handle) => f(*handle),
124-
None => {
125-
let handle = ~LineBufferedWriter::new(stdout());
126-
let mut handle = handle as ~Writer;
127-
f(handle);
128-
(*task).stdout_handle = Some(handle);
129-
}
130-
}
157+
let task: Option<~Task> = Local::try_take();
158+
match task {
159+
Some(mut task) => {
160+
// Printing may run arbitrary code, so ensure that the task is in
161+
// TLS to allow all std services. Note that this means a print while
162+
// printing won't use the task's normal stdout handle, but this is
163+
// necessary to ensure safety (no aliasing).
164+
let mut my_stdout = task.stdout.take();
165+
Local::put(task);
166+
167+
if my_stdout.is_none() {
168+
my_stdout = Some(~LineBufferedWriter::new(stdout()) as ~Writer);
131169
}
170+
f(*my_stdout.get_mut_ref());
171+
172+
// Note that we need to be careful when putting the stdout handle
173+
// back into the task. If the handle was set to `Some` while
174+
// printing, then we can run aribitrary code when destroying the
175+
// previous handle. This means that the local task needs to be in
176+
// TLS while we do this.
177+
//
178+
// To protect against this, we do a little dance in which we
179+
// temporarily take the task, swap the handles, put the task in TLS,
180+
// and only then drop the previous handle.
181+
let mut t = Local::borrow(None::<Task>);
182+
let prev = util::replace(&mut t.get().stdout, my_stdout);
183+
drop(t);
184+
drop(prev);
185+
}
132186

133-
None => {
134-
struct Stdout;
135-
impl Writer for Stdout {
136-
fn write(&mut self, data: &[u8]) {
137-
unsafe {
138-
libc::write(libc::STDOUT_FILENO,
139-
data.as_ptr() as *libc::c_void,
140-
data.len() as libc::size_t);
141-
}
187+
None => {
188+
struct Stdout;
189+
impl Writer for Stdout {
190+
fn write(&mut self, data: &[u8]) {
191+
unsafe {
192+
libc::write(libc::STDOUT_FILENO,
193+
data.as_ptr() as *libc::c_void,
194+
data.len() as libc::size_t);
142195
}
143196
}
144-
let mut io = Stdout;
145-
f(&mut io as &mut Writer);
146197
}
198+
let mut io = Stdout;
199+
f(&mut io as &mut Writer);
147200
}
148201
}
149202
}
@@ -313,4 +366,29 @@ mod tests {
313366
stdout();
314367
stderr();
315368
})
369+
370+
iotest!(fn capture_stdout() {
371+
use io::comm_adapters::{PortReader, ChanWriter};
372+
373+
let (p, c) = Chan::new();
374+
let (mut r, w) = (PortReader::new(p), ChanWriter::new(c));
375+
do spawn {
376+
set_stdout(~w as ~Writer);
377+
println!("hello!");
378+
}
379+
assert_eq!(r.read_to_str(), ~"hello!\n");
380+
})
381+
382+
iotest!(fn capture_stderr() {
383+
use io::comm_adapters::{PortReader, ChanWriter};
384+
385+
let (p, c) = Chan::new();
386+
let (mut r, w) = (PortReader::new(p), ChanWriter::new(c));
387+
do spawn {
388+
set_stderr(~w as ~Writer);
389+
fail!("my special message");
390+
}
391+
let s = r.read_to_str();
392+
assert!(s.contains("my special message"));
393+
})
316394
}

src/libstd/logging.rs

+52-6
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,15 @@ start, print out all modules registered for logging, and then exit.
9696
*/
9797

9898
use fmt;
99-
use option::*;
99+
use io::buffered::LineBufferedWriter;
100+
use io;
101+
use io::Writer;
102+
use ops::Drop;
103+
use option::{Some, None, Option};
104+
use prelude::drop;
100105
use rt::local::Local;
101-
use rt::logging::{Logger, StdErrLogger};
102106
use rt::task::Task;
107+
use util;
103108

104109
/// Debug log level
105110
pub static DEBUG: u32 = 4;
@@ -110,24 +115,65 @@ pub static WARN: u32 = 2;
110115
/// Error log level
111116
pub static ERROR: u32 = 1;
112117

118+
/// A trait used to represent an interface to a task-local logger. Each task
119+
/// can have its own custom logger which can respond to logging messages
120+
/// however it likes.
121+
pub trait Logger {
122+
/// Logs a single message described by the `args` structure. The level is
123+
/// provided in case you want to do things like color the message, etc.
124+
fn log(&mut self, level: u32, args: &fmt::Arguments);
125+
}
126+
127+
struct DefaultLogger {
128+
handle: LineBufferedWriter<io::stdio::StdWriter>,
129+
}
130+
131+
impl Logger for DefaultLogger {
132+
// by default, just ignore the level
133+
fn log(&mut self, _level: u32, args: &fmt::Arguments) {
134+
fmt::writeln(&mut self.handle, args);
135+
}
136+
}
137+
138+
impl Drop for DefaultLogger {
139+
fn drop(&mut self) {
140+
self.handle.flush();
141+
}
142+
}
143+
113144
/// This function is called directly by the compiler when using the logging
114145
/// macros. This function does not take into account whether the log level
115146
/// specified is active or not, it will always log something if this method is
116147
/// called.
117148
///
118149
/// It is not recommended to call this function directly, rather it should be
119150
/// invoked through the logging family of macros.
120-
pub fn log(_level: u32, args: &fmt::Arguments) {
151+
pub fn log(level: u32, args: &fmt::Arguments) {
152+
// See io::stdio::with_task_stdout for why there's a few dances here. The
153+
// gist of it is that arbitrary code can run during logging (and set an
154+
// arbitrary logging handle into the task) so we need to be careful that the
155+
// local task is in TLS while we're running arbitrary code.
121156
let mut logger = {
122157
let mut task = Local::borrow(None::<Task>);
123158
task.get().logger.take()
124159
};
125160

126161
if logger.is_none() {
127-
logger = Some(StdErrLogger::new());
162+
logger = Some(~DefaultLogger {
163+
handle: LineBufferedWriter::new(io::stderr()),
164+
} as ~Logger);
128165
}
129-
logger.get_mut_ref().log(args);
166+
logger.get_mut_ref().log(level, args);
167+
168+
let mut task = Local::borrow(None::<Task>);
169+
let prev = util::replace(&mut task.get().logger, logger);
170+
drop(task);
171+
drop(prev);
172+
}
130173

174+
/// Replaces the task-local logger with the specified logger, returning the old
175+
/// logger.
176+
pub fn set_logger(logger: ~Logger) -> Option<~Logger> {
131177
let mut task = Local::borrow(None::<Task>);
132-
task.get().logger = logger;
178+
util::replace(&mut task.get().logger, Some(logger))
133179
}

src/libstd/rt/logging.rs

-26
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,9 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
use fmt;
1211
use from_str::from_str;
1312
use libc::exit;
1413
use option::{Some, None, Option};
15-
use io;
16-
use io::stdio::StdWriter;
17-
use io::buffered::LineBufferedWriter;
1814
use rt::crate_map::{ModEntry, CrateMap, iter_crate_map, get_crate_map};
1915
use str::StrSlice;
2016
use vec::{ImmutableVector, MutableTotalOrdVector};
@@ -168,28 +164,6 @@ fn update_log_settings(crate_map: &CrateMap, settings: ~str) {
168164
}
169165
}
170166

171-
pub trait Logger {
172-
fn log(&mut self, args: &fmt::Arguments);
173-
}
174-
175-
/// This logger emits output to the stderr of the process, and contains a lazily
176-
/// initialized event-loop driven handle to the stream.
177-
pub struct StdErrLogger {
178-
priv handle: LineBufferedWriter<StdWriter>,
179-
}
180-
181-
impl StdErrLogger {
182-
pub fn new() -> StdErrLogger {
183-
StdErrLogger { handle: LineBufferedWriter::new(io::stderr()) }
184-
}
185-
}
186-
187-
impl Logger for StdErrLogger {
188-
fn log(&mut self, args: &fmt::Arguments) {
189-
fmt::writeln(&mut self.handle as &mut io::Writer, args);
190-
}
191-
}
192-
193167
/// Configure logging by traversing the crate map and setting the
194168
/// per-module global logging flags based on the logging spec
195169
pub fn init() {

0 commit comments

Comments
 (0)