diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index cf4693beba..bf1d933acd 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -2125,7 +2125,7 @@ impl AcpThread { let curr_status = mem::replace(&mut call.status, new_status); if let ToolCallStatus::WaitingForConfirmation { respond_tx, .. } = curr_status { - respond_tx.send(outcome).log_err(); + respond_tx.send(outcome).ok(); } else if cfg!(debug_assertions) { panic!("tried to authorize an already authorized tool call"); } diff --git a/crates/agent/src/agent.rs b/crates/agent/src/agent.rs index 9eb0f84b6f..c5bc4c5828 100644 --- a/crates/agent/src/agent.rs +++ b/crates/agent/src/agent.rs @@ -1309,7 +1309,9 @@ impl NativeAgentConnection { { response .send(outcome) - .map(|_| anyhow!("authorization receiver was dropped")) + .map_err(|_| { + anyhow!("authorization receiver was dropped") + }) .log_err(); } }) diff --git a/crates/askpass/src/askpass.rs b/crates/askpass/src/askpass.rs index 4e84d38946..c550d95979 100644 --- a/crates/askpass/src/askpass.rs +++ b/crates/askpass/src/askpass.rs @@ -124,7 +124,7 @@ impl AskPassSession { ControlFlow::Continue(Ok(password)) } else { if let Some(kill_tx) = kill_tx.lock().await.take() { - kill_tx.send(()).log_err(); + kill_tx.send(()).ok(); } ControlFlow::Break(()) } diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index bdda213dfd..df6403460d 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -738,7 +738,7 @@ pub trait InteractiveElement: Sized { fn key_context(mut self, key_context: C) -> Self where C: TryInto, - E: Debug, + E: std::fmt::Display, { if let Some(key_context) = key_context.try_into().log_err() { self.interactivity().key_context = Some(key_context); diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index f66f584478..ab253472ad 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -1,7 +1,7 @@ use crate::{App, PlatformDispatcher, PlatformScheduler}; use futures::channel::mpsc; use futures::prelude::*; -use gpui_util::TryFutureExt; +use gpui_util::{TryFutureExt, TryFutureExtBacktrace}; use scheduler::Instant; use scheduler::Scheduler; use std::{ @@ -84,7 +84,7 @@ impl Task { impl Task> where T: 'static, - E: 'static + Debug, + E: 'static + std::fmt::Display, { /// Run the task to completion in the background and log any errors that occur. #[track_caller] @@ -96,6 +96,22 @@ where } } +impl Task> +where + T: 'static, + E: 'static + std::fmt::Debug, +{ + /// Like [`Self::detach_and_log_err`], but uses `{:?}` formatting on failure so `anyhow::Error` + /// values emit their full backtrace. Prefer `detach_and_log_err` unless a backtrace is wanted. + #[track_caller] + pub fn detach_and_log_err_with_backtrace(self, cx: &App) { + let location = *core::panic::Location::caller(); + cx.foreground_executor() + .spawn(self.log_tracked_err_with_backtrace(location)) + .detach(); + } +} + impl std::future::Future for Task { type Output = T; diff --git a/crates/gpui_linux/src/linux/wayland/client.rs b/crates/gpui_linux/src/linux/wayland/client.rs index 10f4aab0db..3b8ca0f450 100644 --- a/crates/gpui_linux/src/linux/wayland/client.rs +++ b/crates/gpui_linux/src/linux/wayland/client.rs @@ -2237,7 +2237,13 @@ impl Dispatch for WaylandClientStatePtr { let paths: SmallVec<[_; 2]> = file_list .lines() .filter_map(|path| Url::parse(path).log_err()) - .filter_map(|url| url.to_file_path().log_err()) + .filter_map(|url| match url.to_file_path() { + Ok(url) => Some(url), + Err(()) => { + log::error!("Failed turn {url:?} into a file path"); + None + } + }) .collect(); let position = Point::new(x.into(), y.into()); diff --git a/crates/gpui_linux/src/linux/x11/client.rs b/crates/gpui_linux/src/linux/x11/client.rs index 70c0392d43..07d1596a2f 100644 --- a/crates/gpui_linux/src/linux/x11/client.rs +++ b/crates/gpui_linux/src/linux/x11/client.rs @@ -908,7 +908,13 @@ impl X11Client { let paths: SmallVec<[_; 2]> = file_list .lines() .filter_map(|path| Url::parse(path).log_err()) - .filter_map(|url| url.to_file_path().log_err()) + .filter_map(|url| match url.to_file_path() { + Ok(url) => Some(url), + Err(()) => { + log::error!("Failed turn {url:?} into a file path"); + None + } + }) .collect(); let input = PlatformInput::FileDrop(FileDropEvent::Entered { position: state.xdnd_state.position, diff --git a/crates/gpui_util/src/lib.rs b/crates/gpui_util/src/lib.rs index ae56c2ccd0..eac1e2559a 100644 --- a/crates/gpui_util/src/lib.rs +++ b/crates/gpui_util/src/lib.rs @@ -79,6 +79,12 @@ pub trait ResultExt { type Ok; fn log_err(self) -> Option; + /// Like [`ResultExt::log_err`], but uses `{:?}` formatting so `anyhow::Error` values emit their + /// full backtrace. Reach for this only when a backtrace is genuinely wanted — most call sites + /// should stick with `log_err` / `warn_on_err`, whose output is a single chained error message. + fn log_err_with_backtrace(self) -> Option + where + E: std::fmt::Debug; /// Assert that this result should never be an error in development or tests. fn debug_assert_ok(self, reason: &str) -> Self; fn warn_on_err(self) -> Option; @@ -90,7 +96,7 @@ pub trait ResultExt { impl ResultExt for Result where - E: std::fmt::Debug, + E: std::fmt::Display, { type Ok = T; @@ -99,10 +105,28 @@ where self.log_with_level(log::Level::Error) } + #[track_caller] + fn log_err_with_backtrace(self) -> Option + where + E: std::fmt::Debug, + { + match self { + Ok(value) => Some(value), + Err(error) => { + log_error_with_caller( + *Location::caller(), + DebugAsDisplay(&error), + log::Level::Error, + ); + None + } + } + } + #[track_caller] fn debug_assert_ok(self, reason: &str) -> Self { if let Err(error) = &self { - debug_panic!("{reason} - {error:?}"); + debug_panic!("{reason} - {error:#}"); } self } @@ -133,7 +157,7 @@ where fn log_error_with_caller(caller: core::panic::Location<'_>, error: E, level: log::Level) where - E: std::fmt::Debug, + E: std::fmt::Display, { #[cfg(not(windows))] let file = caller.file(); @@ -156,7 +180,7 @@ where &log::Record::builder() .target(module_path.as_deref().unwrap_or("")) .module_path(file.as_deref()) - .args(format_args!("{:?}", error)) + .args(format_args!("{:#}", error)) .file(Some(caller.file())) .line(Some(caller.line())) .level(level) @@ -164,10 +188,20 @@ where ); } -pub fn log_err(error: &E) { +pub fn log_err(error: &E) { log_error_with_caller(*Location::caller(), error, log::Level::Error); } +// Forces `{:?}` formatting through a `Display`-bounded logging helper so `anyhow::Error` emits a +// backtrace instead of the single-line chained message produced by its `Display`/`{:#}` forms. +struct DebugAsDisplay<'a, E>(&'a E); + +impl std::fmt::Display for DebugAsDisplay<'_, E> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + pub trait TryFutureExt { fn log_err(self) -> LogErrorFuture where @@ -185,10 +219,25 @@ pub trait TryFutureExt { Self: Sized; } +/// `{:?}`-formatting companion to [`TryFutureExt`]; emits a backtrace for `anyhow::Error`. Prefer +/// [`TryFutureExt`] unless a backtrace is genuinely wanted. +pub trait TryFutureExtBacktrace { + fn log_err_with_backtrace(self) -> LogErrorWithBacktraceFuture + where + Self: Sized; + + fn log_tracked_err_with_backtrace( + self, + location: core::panic::Location<'static>, + ) -> LogErrorWithBacktraceFuture + where + Self: Sized; +} + impl TryFutureExt for F where F: Future>, - E: std::fmt::Debug, + E: std::fmt::Display, { #[track_caller] fn log_err(self) -> LogErrorFuture @@ -223,10 +272,62 @@ where } } +impl TryFutureExtBacktrace for F +where + F: Future>, + E: std::fmt::Debug, +{ + #[track_caller] + fn log_err_with_backtrace(self) -> LogErrorWithBacktraceFuture + where + Self: Sized, + { + let location = Location::caller(); + LogErrorWithBacktraceFuture(self, log::Level::Error, *location) + } + + fn log_tracked_err_with_backtrace( + self, + location: core::panic::Location<'static>, + ) -> LogErrorWithBacktraceFuture + where + Self: Sized, + { + LogErrorWithBacktraceFuture(self, log::Level::Error, location) + } +} + #[must_use] pub struct LogErrorFuture(F, log::Level, core::panic::Location<'static>); impl Future for LogErrorFuture +where + F: Future>, + E: std::fmt::Display, +{ + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let level = self.1; + let location = self.2; + let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) }; + match inner.poll(cx) { + Poll::Ready(output) => Poll::Ready(match output { + Ok(output) => Some(output), + Err(error) => { + log_error_with_caller(location, error, level); + None + } + }), + Poll::Pending => Poll::Pending, + } + } +} + +#[must_use] +pub struct LogErrorWithBacktraceFuture(F, log::Level, core::panic::Location<'static>); + +impl Future for LogErrorWithBacktraceFuture where F: Future>, E: std::fmt::Debug, @@ -241,7 +342,7 @@ where Poll::Ready(output) => Poll::Ready(match output { Ok(output) => Some(output), Err(error) => { - log_error_with_caller(location, error, level); + log_error_with_caller(location, DebugAsDisplay(&error), level); None } }), diff --git a/crates/project/src/task_store.rs b/crates/project/src/task_store.rs index 5b91a3a890..34beb9a8e1 100644 --- a/crates/project/src/task_store.rs +++ b/crates/project/src/task_store.rs @@ -130,7 +130,7 @@ impl TaskStore { .payload .task_variables .into_iter() - .filter_map(|(k, v)| Some((k.parse().log_err()?, v))), + .filter_map(|(k, v)| Some((k.parse().ok()?, v))), ); let snapshot = location.buffer.read(cx).snapshot(); diff --git a/crates/util/src/rel_path.rs b/crates/util/src/rel_path.rs index 8916236925..7b17094e08 100644 --- a/crates/util/src/rel_path.rs +++ b/crates/util/src/rel_path.rs @@ -254,6 +254,14 @@ impl RelPath { #[derive(Debug)] pub struct StripPrefixError; +impl std::fmt::Display for StripPrefixError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("prefix not found") + } +} + +impl std::error::Error for StripPrefixError {} + impl ToOwned for RelPath { type Owned = RelPathBuf; diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index e5a9d5ce0f..fbaa1b50a0 100644 --- a/crates/workspace/src/status_bar.rs +++ b/crates/workspace/src/status_bar.rs @@ -9,7 +9,6 @@ use gpui::{ use std::any::TypeId; use theme::CLIENT_SIDE_DECORATION_ROUNDING; use ui::{Divider, Indicator, Tooltip, prelude::*}; -use util::ResultExt; pub trait StatusItemView: Render { /// Event callback that is triggered when the active pane item changes. @@ -246,7 +245,7 @@ impl StatusBar { self.left_items .iter() .chain(self.right_items.iter()) - .find_map(|item| item.to_any().downcast().log_err()) + .find_map(|item| item.to_any().downcast().ok()) } pub fn position_of_item(&self) -> Option