Better Panic Hooks
アプリケーションは、さまざまな理由でパニックになることがあります (たとえば、None
で .unwrap()
を呼び出す場合)。そして、このような状況が発生した場合、良き市民として次のことを行う必要があります。
- ユーザーがエラーを報告できるように、役立つスタック トレースを提供する。
- ユーザーのターミナル状態を不完全な状態のままにせず、元の状態に戻す。
better-panic
main
は、パニックのためのかなりのバックトレースを提供します。
cargo add better-panic
以下は、better-panic
を使用してデフォルトでよりきれいなバックトレースを提供する initialize_panic_handler()
の例です。
use better_panic::Settings;
pub fn initialize_panic_handler() { std::panic::set_hook(Box::new(|panic_info| { crossterm::execute!(std::io::stderr(), crossterm::terminal::LeaveAlternateScreen).unwrap(); crossterm::terminal::disable_raw_mode().unwrap(); Settings::auto().most_recent_first(false).lineno_suffix(true).create_panic_handler()(panic_info); }));}
私は個人的に、パニック ハンドラで Tui
構造体を再利用することを好みます。
こうすることで、将来 crossterm
から termion
に移行することに決めた場合、プロジェクト内でリファクタリングについて心配しなければならない場所が 1 つ減ります。
以下は、デフォルトでよりきれいなバックトレースを提供するために better_panic
と libc
を使用する initialize_panic_handler()
の例です。
use better_panic::Settings;
pub fn initialize_panic_handler() { std::panic::set_hook(Box::new(|panic_info| { match crate::tui::Tui::new() { Ok(t) => { if let Err(r) = t.exit() { error!("Unable to exit Terminal: {r:?}"); } }, Err(r) => error!("Unable to exit Terminal: {r:?}"), } better_panic::Settings::auto() .most_recent_first(false) .lineno_suffix(true) .verbosity(better_panic::Verbosity::Full) .create_panic_handler()(panic_info); std::process::exit(libc::EXIT_FAILURE); }));}
さて、例として panic!
をアプリケーションに追加したとしましょう。
diff --git a/src/components/app.rs b/src/components/app.rsindex 289e40b..de48392 100644--- a/src/components/app.rs+++ b/src/components/app.rs@@ -77,6 +77,7 @@ impl App { }
pub fn increment(&mut self, i: usize) {+ panic!("At the disco"); self.counter = self.counter.saturating_add(i); }
これは、 better-panic
で、きれいなStacktraceがどのように見えるかです。
Backtrace (most recent call last): File "/Users/kd/gitrepos/myapp/src/main.rs:46", in ratatui_async_template::main Ok(()) File "/Users/kd/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/runtime.rs:304", in tokio::runtime::runtime::Runtime::block_on Scheduler::MultiThread(exec) => exec.block_on(&self.handle.inner, future), File "/Users/kd/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/scheduler/multi_thread/mod.rs:66", in tokio::runtime::scheduler::multi_thread::MultiThread::block_on enter File "/Users/kd/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/context.rs:315", in tokio::runtime::context::BlockingRegionGuard::block_on park.block_on(f) File "/Users/kd/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/park.rs:283", in tokio::runtime::park::CachedParkThread::block_on if let Ready(v) = crate::runtime::coop::budget(|| f.as_mut().poll(&mut cx)) { File "/Users/kd/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/coop.rs:73", in tokio::runtime::coop::budget with_budget(Budget::initial(), f) File "/Users/kd/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/coop.rs:107", in tokio::runtime::coop::with_budget f() File "/Users/kd/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.28.2/src/runtime/park.rs:283", in tokio::runtime::park::CachedParkThread::block_on::{{closure}} if let Ready(v) = crate::runtime::coop::budget(|| f.as_mut().poll(&mut cx)) { File "/Users/kd/gitrepos/myapp/src/main.rs:44", in ratatui_async_template::main::{{closure}} runner.run().await?; File "/Users/kd/gitrepos/myapp/src/runner.rs:80", in ratatui_async_template::runner::Runner::run::{{closure}} if let Some(action) = component.update(action.clone())? { File "/Users/kd/gitrepos/myapp/src/components/app.rs:132", in <ratatui_async_template::components::app::App as ratatui_async_template::components::Component>::update Action::Increment(i) => self.increment(i), File "/Users/kd/gitrepos/myapp/src/components/app.rs:80", in ratatui_async_template::components::app::App::increment panic!("At the disco");
The application panicked (crashed). At the discoin src/components/app.rs:80thread: main
.most_recent_first(false)
を使用すると、スタックトレースの最後の行は通常、エラーが発生した場所です。これにより、ターミナル履歴を上にスクロールしなくてもエラーをすばやく簡単に見つけることができ、開発中にアプリケーションを迅速に反復処理できます。
このような詳細なスタックトレースは、デバッグビルドでのみ使用できます。リリースビルドでは、スタックトレースがインライン化または切り捨てられる場合があります。
たとえば、すべての最適化をオンにしてコンパイルすると、次のようになります。
Backtrace (most recent call last): File "<unknown>:0", in __mh_execute_header File "<unknown>:0", in __mh_execute_header File "<unknown>:0", in __mh_execute_header File "<unknown>:0", in __mh_execute_header File "<unknown>:0", in __mh_execute_header File "<unknown>:0", in __mh_execute_header File "<unknown>:0", in __mh_execute_header File "<unknown>:0", in __mh_execute_header File "<unknown>:0", in __mh_execute_header File "<unknown>:0", in __mh_execute_header File "<unknown>:0", in __mh_execute_header File "<unknown>:0", in __mh_execute_header File "<unknown>:0", in __mh_execute_header
The application panicked (crashed). At the discoin src/components/app.rs:80thread: main
これを一般ユーザーに表示しても特に役に立ちません。次のサブセクションでは、アプリケーションのユーザーに何を表示するかというより良い解決策について説明します。
人間 - パニック
human-panic を使用するには、依存関係としてインストールする必要があります。
cargo add human-panic
個人的には、human-panic
は、ユーザーが予期せぬパニックに陥ったときに、すぐに使える最もユーザーフレンドリーなパニック処理機能を提供すると思います。
Well, this is embarrassing.
myapp had a problem and crashed. To help us diagnose the problem you can send us a crash report.
We have generated a report file at "/var/folders/l4/bnjjc6p15zd3jnty8c_qkrtr0000gn/T/report-ce1e29cb-c17c-4684-b9d4-92d9678242b7.toml". Submit an issue or email with the subject of "myapp Crash Report" and include the report as an attachment.
- Authors: Dheepak Krishnamurthy
We take privacy seriously, and do not perform any automated error collection. In order to improve the software, we rely on people to submit reports.
Thank you kindly!
クラッシュに関連する情報が記録されるレポートを生成します。 human-panic
が作成する一時的なレポートファイルのコンテンツは次のとおりです (最適化をオンにします) :
name = "myapp"operating_system = "Mac OS 13.5.2 [64-bit]"crate_version = "0.1.0"explanation = """Panic occurred in file 'src/components/app.rs' at line 80"""cause = "At the disco"method = "Panic"backtrace = """
0: 0x10448f5f8 - __mh_execute_header 1: 0x1044a43c8 - __mh_execute_header 2: 0x1044a01ac - __mh_execute_header 3: 0x10446f8c0 - __mh_execute_header 4: 0x1044ac850 - __mh_execute_header"""
デバッグモードでは、Stacktraceは以前と同じように説明的です。
構成
デバッグ ビルドには better-panic
、リリース ビルドには color-eyre
と human-panic
を使用して、これらの異なるパニック ハンドラーを組み合わせて使用できます。以下のコードでは、念のため color-eyre
スタック トレースを log::error!
に出力します (ANSI エスケープ シーケンスを削除した後)。
cargo add color-eyre human-panic libc better-panic strip-ansi-escapes
プロジェクトにコピーして貼り付けることができるコードは次のとおりです (ターミナル終了を処理するために Tui
構造体を使用する場合)。
pub fn initialize_panic_handler() -> Result<()> { let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default() .panic_section(format!("This is a bug. Consider reporting it at {}", env!("CARGO_PKG_REPOSITORY"))) .display_location_section(true) .display_env_section(true) .into_hooks(); eyre_hook.install()?; std::panic::set_hook(Box::new(move |panic_info| { if let Ok(t) = crate::tui::Tui::new() { if let Err(r) = t.exit() { error!("Unable to exit Terminal: {:?}", r); } }
let msg = format!("{}", panic_hook.panic_report(panic_info)); #[cfg(not(debug_assertions))] { eprintln!("{msg}"); use human_panic::{handle_dump, print_msg, Metadata}; let author = format!("authored by {}", env!("CARGO_PKG_AUTHORS")); let support = format!( "You can open a support request at {}", env!("CARGO_PKG_REPOSITORY") ); let meta = Metadata::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")) .authors(author) .support(support);
let file_path = handle_dump(&meta, panic_info); print_msg(file_path, &meta).expect("human-panic: printing error message to console failed"); } log::error!("Error: {}", strip_ansi_escapes::strip_str(msg));
#[cfg(debug_assertions)] { // Better Panic stacktrace that is only enabled when debugging. better_panic::Settings::auto() .most_recent_first(false) .lineno_suffix(true) .verbosity(better_panic::Verbosity::Full) .create_panic_handler()(panic_info); }
std::process::exit(libc::EXIT_FAILURE); })); Ok(())}