ssh: Log error when remote server panics (#18853)

Bennet Bo Fenner created

Release Notes:

- N/A

Change summary

Cargo.lock                       |  1 
crates/remote_server/Cargo.toml  |  1 
crates/remote_server/src/main.rs |  6 ++--
crates/remote_server/src/unix.rs | 47 +++++++++++++++++++++++++++++++++
4 files changed, 51 insertions(+), 4 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9163,6 +9163,7 @@ name = "remote_server"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "backtrace",
  "cargo_toml",
  "clap",
  "client",

crates/remote_server/Cargo.toml 🔗

@@ -22,6 +22,7 @@ test-support = ["fs/test-support"]
 
 [dependencies]
 anyhow.workspace = true
+backtrace = "0.3"
 clap.workspace = true
 client.workspace = true
 env_logger.workspace = true

crates/remote_server/src/main.rs 🔗

@@ -37,7 +37,7 @@ fn main() {
 
 #[cfg(not(windows))]
 fn main() -> Result<()> {
-    use remote_server::unix::{execute_proxy, execute_run, init_logging};
+    use remote_server::unix::{execute_proxy, execute_run, init};
 
     let cli = Cli::parse();
 
@@ -48,11 +48,11 @@ fn main() -> Result<()> {
             stdin_socket,
             stdout_socket,
         }) => {
-            init_logging(Some(log_file))?;
+            init(Some(log_file))?;
             execute_run(pid_file, stdin_socket, stdout_socket)
         }
         Some(Commands::Proxy { identifier }) => {
-            init_logging(None)?;
+            init(None)?;
             execute_proxy(identifier)
         }
         Some(Commands::Version) => {

crates/remote_server/src/unix.rs 🔗

@@ -20,7 +20,13 @@ use std::{
     sync::Arc,
 };
 
-pub fn init_logging(log_file: Option<PathBuf>) -> Result<()> {
+pub fn init(log_file: Option<PathBuf>) -> Result<()> {
+    init_logging(log_file)?;
+    init_panic_hook();
+    Ok(())
+}
+
+fn init_logging(log_file: Option<PathBuf>) -> Result<()> {
     if let Some(log_file) = log_file {
         let target = Box::new(if log_file.exists() {
             std::fs::OpenOptions::new()
@@ -46,6 +52,45 @@ pub fn init_logging(log_file: Option<PathBuf>) -> Result<()> {
     Ok(())
 }
 
+fn init_panic_hook() {
+    std::panic::set_hook(Box::new(|info| {
+        let payload = info
+            .payload()
+            .downcast_ref::<&str>()
+            .map(|s| s.to_string())
+            .or_else(|| info.payload().downcast_ref::<String>().cloned())
+            .unwrap_or_else(|| "Box<Any>".to_string());
+
+        let backtrace = backtrace::Backtrace::new();
+        let mut backtrace = backtrace
+            .frames()
+            .iter()
+            .flat_map(|frame| {
+                frame
+                    .symbols()
+                    .iter()
+                    .filter_map(|frame| Some(format!("{:#}", frame.name()?)))
+            })
+            .collect::<Vec<_>>();
+
+        // Strip out leading stack frames for rust panic-handling.
+        if let Some(ix) = backtrace
+            .iter()
+            .position(|name| name == "rust_begin_unwind")
+        {
+            backtrace.drain(0..=ix);
+        }
+
+        log::error!(
+            "server: panic occurred: {}\nBacktrace:\n{}",
+            payload,
+            backtrace.join("\n")
+        );
+
+        std::process::abort();
+    }));
+}
+
 fn start_server(
     stdin_listener: UnixListener,
     stdout_listener: UnixListener,