Redirect panics to a specified buffer

倖福魔咒の 提交于 2019-12-10 19:15:33

问题


Is there any way to do this? In a terminal graphics library, if an exception occurs, the exception will be flushed away before being displayed, making programming very hard to debug using this library.

impl Drop for Terminal {
    fn drop(&mut self) {
        self.outbuffer.write_all(&self.driver.get(DevFn::ShowCursor)).unwrap();
        self.outbuffer.write_all(&self.driver.get(DevFn::Reset)).unwrap();
        self.outbuffer.write_all(&self.driver.get(DevFn::Clear)).unwrap();
        self.outbuffer.write_all(&self.driver.get(DevFn::ExitCa)).unwrap();
        self.flush().unwrap(); // If an exception occurs, this will reclear the screen and remove the output
        self.termctl.reset().unwrap();
        SIGWINCH_STATUS.store(false, Ordering::SeqCst);
        RUSTTY_STATUS.store(false, Ordering::SeqCst);
    }
}

If I were to comment out self.flush().unwrap(); the exception would print, however the terminal would not correctly flush the screen and leave graphics present on the terminal even after the program has ended.

Is it possible to, at the beginning of the program, specify a custom buffer panic will use for writing to? Or possibly write a hacky trick to do this? That way, after the flush we can check to see if anything is inside this buffer, if so we know an exception occurred and can print it out.


Running a program that purposely crashes with an arithmetic overflow, currently the output is only

By commenting out self.flush().unwrap(); however, we're greeted with the actual exception, but a very ugly terminal now. This solution will not work as program that execute correctly still need to be flushed since no error is needed to be displayed


回答1:


Panics messages are currently written to stderr so the hacky way to do this is to redirect stderr to a file (cargo run 2>/path/to/panic.log).

Alternatively, you can do this in your program itself by using gag (disclaimer, I wrote this library). Unfortunately, it doesn't work on Windows.

The following will redirect stderr until stderr_redirect is dropped:

use std::fs::OpenOptions;
use gag::Redirect;

let log = OpenOptions::new()
    .truncate(true)
    .read(true)
    .create(true)
    .write(true)
    .open("/path/to/panic.log")
    .unwrap();

let stderr_redirect = Redirect::stderr(log).unwrap();
// Your code here...

You can also buffer stderr in a temporary file by doing:

use gag::BufferRedirect;

let mut stderr_buffer = BufferRedirect::stderr().unwrap();



回答2:


You need to register a panic hook with std::panic::set_hook that captures the output. You can then catch the panic with std::panic::catch_unwind:

use std::{
    panic,
    sync::{Arc, Mutex},
};

fn main() {
    let global_buffer = Arc::new(Mutex::new(String::new()));

    let old_hook = panic::take_hook();
    panic::set_hook({
        let global_buffer = global_buffer.clone();
        Box::new(move |info| {
            let mut global_buffer = global_buffer.lock().unwrap();

            if let Some(s) = info.payload().downcast_ref::<&str>() {
                global_buffer.push_str(s);
            }
        })
    });

    let result = panic::catch_unwind(|| {
        panic!("test panic");
    });

    panic::set_hook(old_hook);

    match result {
        Ok(res) => res,
        Err(_) => {
            println!("caught panic!");
            println!("I captured:\n\n{}", global_buffer.lock().unwrap())
        }
    }
}

See also:

  • Suppress panic output in Rust when using panic::catch_unwind


来源:https://stackoverflow.com/questions/33151317/redirect-panics-to-a-specified-buffer

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!