在 catch_unwind 期间将回溯转换为字符串

Transform backtrace to string during catch_unwind

我正在用 Rust 编写跨平台 (Linux/iOS/Android) 库。万一我的库出现恐慌,我希望应用程序继续正常工作而不是崩溃。为此,我使用 catch_unwind;它的结果包含恐慌信息并且没有包含足够的关于问题根源的信息,这里有一段代码解释了我在做什么:

fn calculate(json: &str) -> String {
    unimplemented!()
}

#[no_mangle]
pub fn rust_calculation(json: &str) -> String {
    let r = std::panic::catch_unwind(||{
        // rust calculation
        let calc: String = calculate(json).into();
        calc
    });
    let r_str = match r {
        Ok(v) => v,
        Err(e) => {
            let panic_information = match e.downcast::<String>() {
                Ok(v) => *v,
                _ => "Unknown Source of Error".to_owned()
            };
            panic_information
        }
    };
    return r_str;
}

fn main() {
    println!("{}", rust_calculation("test"));
}

Playground

如果发生错误,返回的消息是不够的,有时只包含消息

Unknown Source of Error

我想将回溯发送到调用源,以便它可以记录它,然后我们可以在之后调试 Rust 库。我该怎么做?

some times it countains the message:

Unknown Source of Error

这可能是因为e不仅可以是String,还可以是&str(好吧,基本上可以是任何类型,但这两个是最常见的, 你只能在使用 panic_any() 时得到其他类型)。当在 catch_unwind() 中调用 panic!() 时,如果您使用参数 panic!,您只会得到 String,否则,恐慌消息是 &str(如不需要分配 String).

I want to send the backtrace to the calling source so that he can log it and then we can debug the rust library afterwards, how can I do that ?

夜间 Rust 的解决方案

在 nightly Rust 上,您可以通过 std::backtrace::Backtrace:

在 panic 处理程序中生成回溯
#![feature(backtrace)]
use lazy_static::lazy_static;
use std::sync::{Arc, Mutex};

// on panic, save the backtrace here (as string)
lazy_static! {
    static ref BACKTRACE: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
}

#[no_mangle]
pub fn rust_calculation(json: &str) -> String {
    let r = std::panic::catch_unwind(||{
        panic!("Panic");
    });
    let r_str = match r {
        Ok(v) => v,
        Err(e) => {
            let panic_information = match e.downcast::<String>() {
                Ok(v) => *v,
                Err(e) => match e.downcast::<&str>() {
                    Ok(v) => v.to_string(),
                    _ => "Unknown Source of Error".to_owned()
                }
            };
            format!("{}\nBacktrace:\n{}", panic_information, BACKTRACE.lock().unwrap().take().unwrap_or("<Backtrace not found>".to_string()))
        }
    };
    return r_str;
}

fn panic_hook(_: &std::panic::PanicInfo) {
    *BACKTRACE.lock().unwrap() = Some(std::backtrace::Backtrace::force_capture().to_string());
}

fn main() {
    std::panic::set_hook(Box::new(panic_hook));
    println!("{}", rust_calculation(""));
}

Playground link

输出:

Panic
Backtrace:
   0: playground::panic_hook
             at ./src/main.rs:32:39
   1: core::ops::function::Fn::call
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/core/src/ops/function.rs:70:5
   2: std::panicking::rust_panic_with_hook
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:626:17
   3: std::panicking::begin_panic::{{closure}}
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:542:9
   4: std::sys_common::backtrace::__rust_end_short_backtrace
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/sys_common/backtrace.rs:141:18
   5: std::panicking::begin_panic
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:541:12
   6: playground::rust_calculation::{{closure}}
             at ./src/main.rs:13:9
   7: std::panicking::try::do_call
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:401:40
   8: __rust_try
   9: std::panicking::try
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:365:19
  10: std::panic::catch_unwind
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panic.rs:434:14
  11: rust_calculation
             at ./src/main.rs:12:13
  12: playground::main
             at ./src/main.rs:37:20
  13: core::ops::function::FnOnce::call_once
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/core/src/ops/function.rs:227:5
  14: std::sys_common::backtrace::__rust_begin_short_backtrace
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/sys_common/backtrace.rs:125:18
  15: std::rt::lang_start::{{closure}}
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/rt.rs:63:18
  16: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/core/src/ops/function.rs:259:13
  17: std::panicking::try::do_call
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:401:40
  18: std::panicking::try
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:365:19
  19: std::panic::catch_unwind
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panic.rs:434:14
  20: std::rt::lang_start_internal::{{closure}}
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/rt.rs:45:48
  21: std::panicking::try::do_call
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:401:40
  22: std::panicking::try
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panicking.rs:365:19
  23: std::panic::catch_unwind
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/panic.rs:434:14
  24: std::rt::lang_start_internal
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/rt.rs:45:20
  25: std::rt::lang_start
             at /rustc/2faabf579323f5252329264cc53ba9ff803429a3/library/std/src/rt.rs:62:5
  26: main
  27: __libc_start_main
  28: _start

请注意,由于 Rust 的内部恐慌处理代码,此回溯在调用 panic! 之后包含一些额外的帧。

具有依赖关系的解决方案

如果你不使用 nightly Rust,但愿意使用额外的依赖项,你可以对 backtrace crate 做同样的事情(感谢 @BashirAbdelwahed 提醒我注意这一点):

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

// on panic, save the backtrace here (as string)
lazy_static! {
    static ref BACKTRACE: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
}

#[no_mangle]
pub fn rust_calculation(json: &str) -> String {
    let r = std::panic::catch_unwind(||{
        panic!("Panic");
    });
    let r_str = match r {
        Ok(v) => v,
        Err(e) => {
            let panic_information = match e.downcast::<String>() {
                Ok(v) => *v,
                Err(e) => match e.downcast::<&str>() {
                    Ok(v) => v.to_string(),
                    _ => "Unknown Source of Error".to_owned()
                }
            };
            format!("{}\nBacktrace:\n{}", panic_information, BACKTRACE.lock().unwrap().take().unwrap_or("<Backtrace not found>".to_string()))
        }
    };
    return r_str;
}

fn panic_hook(_: &std::panic::PanicInfo) {
    *BACKTRACE.lock().unwrap() = Some(format!("{:?}", backtrace::Backtrace::new()));
}

fn main() {
    std::panic::set_hook(Box::new(panic_hook));
    println!("{}", rust_calculation(""));
}

Playground link

输出:

Panic
Backtrace:
   0: playground::panic_hook
             at src/main.rs:31:55
   1: core::ops::function::Fn::call
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/core/src/ops/function.rs:70:5
   2: std::panicking::rust_panic_with_hook
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:595:17
   3: std::panicking::begin_panic::{{closure}}
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:520:9
   4: std::sys_common::backtrace::__rust_end_short_backtrace
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/sys_common/backtrace.rs:141:18
   5: std::panicking::begin_panic
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:519:12
   6: playground::rust_calculation::{{closure}}
             at src/main.rs:12:9
   7: std::panicking::try::do_call
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:379:40
   8: __rust_try
   9: std::panicking::try
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:343:19
  10: std::panic::catch_unwind
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panic.rs:431:14
  11: rust_calculation
             at src/main.rs:11:13
  12: playground::main
             at src/main.rs:36:20
  13: core::ops::function::FnOnce::call_once
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/core/src/ops/function.rs:227:5
  14: std::sys_common::backtrace::__rust_begin_short_backtrace
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/sys_common/backtrace.rs:125:18
  15: std::rt::lang_start::{{closure}}
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/rt.rs:49:18
  16: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/core/src/ops/function.rs:259:13
      std::panicking::try::do_call
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:379:40
      std::panicking::try
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panicking.rs:343:19
      std::panic::catch_unwind
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/panic.rs:431:14
      std::rt::lang_start_internal
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/rt.rs:34:21
  17: std::rt::lang_start
             at /rustc/53cb7b09b00cbea8754ffb78e7e3cb521cb8af4b/library/std/src/rt.rs:48:5
  18: main
  19: __libc_start_main
  20: _start