Redirect stdout and stderr streams of a thread

I am building a cli tool where application would perform multiple tasks by spawning threads. One of these threads would handle the rendering of state of application. The way rendering works is simply an infinite loop where state is rendered and the screen is cleared using ASCII escape codes.

What I want to add is also show a "logs" section in my rendering that shows any stdout and stderr (and even panic traces) messages written by any of the other thread because currently whatever is written is wiped out because of the clear screen logic of the rendering, so I need to redirect any output from threads to a dedicated field in application state.

I know I can create a custom buf writer and pass it to each thread where it can write all logs to it instead of std streams but I don't know how that would capture panics and stacktraces.

Cross posted at multithreading - Redirect stdout and stderr streams of a thread - Stack Overflow

There is user exposed way to redirect stdout/stderr for individual threads. There is a hook for libtest that does this, but it is unstable and not meant to be used by the end user. It also doesn't affect any C code printing to stdout/stderr.

If you only want to redirect logs and panic messages, you probably want to set a custom logger (are you using log or tracing as logging framework?) and a custom panic hook using std::panic::set_hook().

You may also want to consider not rendering to stdout at all by default - I've written a library that makes it really easy to build REPLs, and that renders to W: std::io::Write rather than to stdout/stderr.

Incidentally, that also makes it a lot easier to test since I can just render to a String, which makes it easy to do something like assert_eq!(actual_output_string, expected_output);.

To render in terminal, I will have to eventually write to stdout, right?

Of course. But when you make the sink generic over std::io::Write, you can just plug in std::io::Stdout, like this:

use std::io::{Stdout, Write};

pub struct Renderer<W: Write> {
    sink: W,
    // other fields 
}

impl Default for Renderer<Stdout> {
    fn default() -> Renderer<Stdout> {
        Renderer {
            sink: std::io::stdout(),
            // initialize other fields
        }
    }
}

Once you have that, you can just call Renderer::default() to get an instance of the renderer that renders to stdout, while e.g. for testing you initialize the struct manually, or with a private ctor fn.

If you'd like a real-world example, have a look at my repl-block library.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.