Stop showing backtraces in default logging (#54660)

When using `.log_err()` or `.detach_and_log_err(cx)` or similar.

See also https://github.com/zed-industries/zed/pull/36404,
https://github.com/zed-industries/zed/pull/46383,
https://github.com/zed-industries/zed/pull/44896,
https://github.com/zed-industries/zed/pull/43917 and many more.

Before, a
https://github.com/zed-industries/zed/blob/77c74db82e35b2e3aeebea9629b644cf43137f16/crates/languages/src/go.rs#L568
line would show a backtrace even for `.context("no cached binary")?;`
case, same as many other usages around the code:

<img width="2032" height="1162" alt="before"
src="https://github.com/user-attachments/assets/ef2188b3-74c9-4c86-82b8-9fdaed3c26ae"
/>

After:

<img width="1896" height="157" alt="after"
src="https://github.com/user-attachments/assets/a1067d9f-61f4-4833-aeab-9f1042d2514a"
/>



To show a backtrace as before, use `log_err_with_backtrace`.

Release Notes:

- Improved Zed's log output on errors
This commit is contained in:
Kirill Bulatov authored and GitHub committed 2026-04-23 14:14:11 +00:00
1 parent 2a52236f9a
commit e9d727015b
11 files changed
+156 -18

No files matched your search

+1 -1
View File
@@ -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");
}
+3 -1
View File
@@ -1309,7 +1309,9 @@ impl NativeAgentConnection {
{
response
.send(outcome)
.map(|_| anyhow!("authorization receiver was dropped"))
.map_err(|_| {
anyhow!("authorization receiver was dropped")
})
.log_err();
}
})
+1 -1
View File
@@ -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(())
}
+1 -1
View File
@@ -738,7 +738,7 @@ pub trait InteractiveElement: Sized {
fn key_context<C, E>(mut self, key_context: C) -> Self
where
C: TryInto<KeyContext, Error = E>,
E: Debug,
E: std::fmt::Display,
{
if let Some(key_context) = key_context.try_into().log_err() {
self.interactivity().key_context = Some(key_context);
+18 -2
View File
@@ -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<T> Task<T> {
impl<T, E> Task<Result<T, E>>
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<T, E> Task<Result<T, E>>
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<T> std::future::Future for Task<T> {
type Output = T;
@@ -2237,7 +2237,13 @@ impl Dispatch<wl_data_device::WlDataDevice, ()> 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());
+7 -1
View File
@@ -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,
+108 -7
View File
@@ -79,6 +79,12 @@ pub trait ResultExt<E> {
type Ok;
fn log_err(self) -> Option<Self::Ok>;
/// 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<Self::Ok>
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<Self::Ok>;
@@ -90,7 +96,7 @@ pub trait ResultExt<E> {
impl<T, E> ResultExt<E> for Result<T, E>
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<T>
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<E>(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<E: std::fmt::Debug>(error: &E) {
pub fn log_err<E: std::fmt::Display>(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<E: std::fmt::Debug> 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<Self>
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<Self>
where
Self: Sized;
fn log_tracked_err_with_backtrace(
self,
location: core::panic::Location<'static>,
) -> LogErrorWithBacktraceFuture<Self>
where
Self: Sized;
}
impl<F, T, E> TryFutureExt for F
where
F: Future<Output = Result<T, E>>,
E: std::fmt::Debug,
E: std::fmt::Display,
{
#[track_caller]
fn log_err(self) -> LogErrorFuture<Self>
@@ -223,10 +272,62 @@ where
}
}
impl<F, T, E> TryFutureExtBacktrace for F
where
F: Future<Output = Result<T, E>>,
E: std::fmt::Debug,
{
#[track_caller]
fn log_err_with_backtrace(self) -> LogErrorWithBacktraceFuture<Self>
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<Self>
where
Self: Sized,
{
LogErrorWithBacktraceFuture(self, log::Level::Error, location)
}
}
#[must_use]
pub struct LogErrorFuture<F>(F, log::Level, core::panic::Location<'static>);
impl<F, T, E> Future for LogErrorFuture<F>
where
F: Future<Output = Result<T, E>>,
E: std::fmt::Display,
{
type Output = Option<T>;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
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>(F, log::Level, core::panic::Location<'static>);
impl<F, T, E> Future for LogErrorWithBacktraceFuture<F>
where
F: Future<Output = Result<T, E>>,
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
}
}),
+1 -1
View File
@@ -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();
+8
View File
@@ -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;
+1 -2
View File
@@ -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<T>(&self) -> Option<usize>