Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a friendly panic hook #45

Merged
merged 4 commits into from
Dec 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion crates/amaru/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ homepage = "https://github.com/pragma-org/amaru"
documentation = "https://docs.rs/amaru"
readme = "README.md"
rust-version = "1.81.0"
build = "build.rs"

[dependencies]
async-trait = "0.1.83"
clap = { version = "4.5.20", features = ["derive"] }
gasket = { version = "0.8.0", features = ["derive"] }
hex = "0.4.3"
miette = "7.2.0"
indoc = "2.0"
ouroboros = { git = "https://github.com/pragma-org/ouroboros", rev = "ca1d447a6c106e421e6c2b1c7d9d59abf5ca9589" }
ouroboros-praos = { git = "https://github.com/pragma-org/ouroboros", rev = "ca1d447a6c106e421e6c2b1c7d9d59abf5ca9589" }
pallas-addresses = "0.31.0"
Expand All @@ -39,10 +41,17 @@ serde = "1.0.215"
bech32 = "0.11.0"
opentelemetry = { version = "0.27.1" }
opentelemetry_sdk = { version = "0.27.1", features = ["async-std", "rt-tokio"] }
opentelemetry-otlp = { version = "0.27.0", features = ["grpc-tonic", "http-proto", "reqwest-client"] }
opentelemetry-otlp = { version = "0.27.0", features = [
"grpc-tonic",
"http-proto",
"reqwest-client",
] }
tracing-opentelemetry = { version = "0.28.0" }

[dev-dependencies]
envpath = { version = "0.0.1-beta.3", features = ["rand"] }
insta = { version = "1.41.1", features = ["json"] }
proptest = "1.5.0"

[build-dependencies]
built = { version = "0.7.1", features = ["git2"] }
3 changes: 3 additions & 0 deletions crates/amaru/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
built::write_built_file().expect("Failed to acquire build-time information");
}
4 changes: 4 additions & 0 deletions crates/amaru/src/bin/amaru/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use clap::{Parser, Subcommand};
use opentelemetry::metrics::Counter;
use panic::panic_handler;
use std::env;

mod cmd;
mod config;
mod exit;
mod panic;

pub const SERVICE_NAME: &str = "amaru";

Expand All @@ -28,6 +30,8 @@ struct Cli {

#[tokio::main]
async fn main() -> miette::Result<()> {
panic_handler();

let counter = setup_tracing();

let args = Cli::parse();
Expand Down
106 changes: 106 additions & 0 deletions crates/amaru/src/bin/amaru/panic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/// Installs a panic handler that prints some useful diagnostics and
/// asks the user to report the issue.
pub fn panic_handler() {
std::panic::set_hook(Box::new(move |info| {
let message = info
.payload()
.downcast_ref::<&str>()
.map(|s| (*s).to_string())
.or_else(|| {
info.payload()
.downcast_ref::<String>()
.map(|s| s.to_string())
})
.unwrap_or_else(|| "unknown error".to_string());

let location = info.location().map_or_else(
|| "".into(),
|location| {
format!(
"{}:{}:{}\n\n ",
location.file(),
location.line(),
location.column(),
)
},
);

// We present the user with a helpful and welcoming error message;
// Block producing nodes should be considered mission critical software, and so
// They should endeavor *never* to crash, and should always handle and recover from errors.
// So if the process panics, it should be treated as a bug, and we very much want the user to report it.
// TODO(pi): We could go a step further, and prefill some issue details like title, body, labels, etc.
// using query parameters: https://github.com/sindresorhus/new-github-issue-url?tab=readme-ov-file#api
let error_message = indoc::formatdoc! {
r#"{fatal}
Whoops! The Amaru process panicked, rather than handling the error it encountered gracefully.

This is almost certainly a bug, and we'd appreciate a report so we can improve Amaru.

Please report this error at https://github.com/pragma-org/amaru/issues/new.

In your bug report please provide the information below and if possible the code
that produced it.
{info}

{location}{message}"#,
info = node_info(),
fatal = "amaru::fatal::error",
location = location,
};

println!("\n{}", indent(&error_message, 3));
}));
}

// TODO: pulled from aiken; should we have our own utility crate for pretty printing?
// https://github.com/aiken-lang/aiken/blob/main/crates/aiken-project/src/pretty.rs#L126C1-L134C2
pub fn indent(lines: &str, n: usize) -> String {
let tab = pad_left(String::new(), n, " ");
lines
.lines()
.map(|line| format!("{tab}{line}"))
.collect::<Vec<_>>()
.join("\n")
}

pub fn pad_left(mut text: String, n: usize, delimiter: &str) -> String {
let diff = n as i32 - text.len() as i32;
if diff.is_positive() {
for _ in 0..diff {
text.insert_str(0, delimiter);
}
}
text
}

// TODO: pulled from aiken; should we have our own config utility crate?
// https://github.com/aiken-lang/aiken/blob/main/crates/aiken-project/src/config.rs#L382C1-L393C2
mod built_info {
include!(concat!(env!("OUT_DIR"), "/built.rs"));
}

pub fn node_info() -> String {
format!(
r#"
Operating System: {}
Architecture: {}
Version: {}"#,
built_info::CFG_OS,
built_info::CFG_TARGET_ARCH,
node_version(true),
)
}

pub fn node_version(include_commit_hash: bool) -> String {
let version = built_info::PKG_VERSION;
let suffix = if include_commit_hash {
format!(
"+{}",
built_info::GIT_COMMIT_HASH_SHORT.unwrap_or("unknown")
)
} else {
"".to_string()
};
format!("v{version}{suffix}")
}
Loading