Skip to content
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

## [0.24.15] - 2025-10-31

### Fixed
- Reworked telemetry flushing so tracing events retry emission when the
subscriber enables interest after an error is constructed, preserving the
expected single event in concurrent test runs.

## [0.24.14] - 2025-10-30

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[package]
name = "masterror"
version = "0.24.14"
version = "0.24.15"
rust-version = "1.90"
edition = "2024"
license = "MIT OR Apache-2.0"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ The build script keeps the full feature snippet below in sync with

~~~toml
[dependencies]
masterror = { version = "0.24.14", default-features = false }
masterror = { version = "0.24.15", default-features = false }
# or with features:
# masterror = { version = "0.24.14", features = [
# masterror = { version = "0.24.15", features = [
# "std", "axum", "actix", "openapi",
# "serde_json", "tracing", "metrics", "backtrace",
# "sqlx", "sqlx-migrate", "reqwest", "redis",
Expand Down
70 changes: 49 additions & 21 deletions src/app_error/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ pub struct ErrorInner {
pub backtrace: Option<Backtrace>,
#[cfg(feature = "backtrace")]
pub captured_backtrace: OnceLock<Option<Backtrace>>,
telemetry_dirty: AtomicBool
telemetry_dirty: AtomicBool,
#[cfg(feature = "tracing")]
tracing_dirty: AtomicBool
}

#[cfg(feature = "backtrace")]
Expand Down Expand Up @@ -270,19 +272,33 @@ impl Error {
backtrace: None,
#[cfg(feature = "backtrace")]
captured_backtrace: OnceLock::new(),
telemetry_dirty: AtomicBool::new(true)
telemetry_dirty: AtomicBool::new(true),
#[cfg(feature = "tracing")]
tracing_dirty: AtomicBool::new(true)
})
}
}

fn mark_dirty(&self) {
self.telemetry_dirty.store(true, Ordering::Release);
#[cfg(feature = "tracing")]
self.mark_tracing_dirty();
}

fn take_dirty(&self) -> bool {
self.telemetry_dirty.swap(false, Ordering::AcqRel)
}

#[cfg(feature = "tracing")]
fn mark_tracing_dirty(&self) {
self.tracing_dirty.store(true, Ordering::Release);
}

#[cfg(feature = "tracing")]
fn take_tracing_dirty(&self) -> bool {
self.tracing_dirty.swap(false, Ordering::AcqRel)
}

#[cfg(feature = "backtrace")]
fn capture_backtrace(&self) -> Option<&CapturedBacktrace> {
if let Some(backtrace) = self.backtrace.as_ref() {
Expand Down Expand Up @@ -324,27 +340,39 @@ impl Error {
)
.increment(1);
}
}

#[cfg(feature = "tracing")]
{
let message = self.message.as_deref();
let retry_seconds = self.retry.map(|value| value.after_seconds);
let trace_id = log_mdc::get("trace_id", |value| value.map(str::to_owned));
event!(
target: "masterror::error",
Level::ERROR,
code = self.code.as_str(),
category = kind_label(self.kind),
message = message,
retry_seconds,
redactable = matches!(self.edit_policy, MessageEditPolicy::Redact),
metadata_len = self.metadata.len() as u64,
www_authenticate = self.www_authenticate.as_deref(),
trace_id = trace_id.as_deref(),
"app error constructed"
);
}
#[cfg(feature = "tracing")]
self.flush_tracing();
}

#[cfg(feature = "tracing")]
fn flush_tracing(&self) {
if !self.take_tracing_dirty() {
return;
}

if !tracing::event_enabled!(target: "masterror::error", Level::ERROR) {
self.mark_tracing_dirty();
return;
}

let message = self.message.as_deref();
let retry_seconds = self.retry.map(|value| value.after_seconds);
let trace_id = log_mdc::get("trace_id", |value| value.map(str::to_owned));
event!(
target: "masterror::error",
Level::ERROR,
code = self.code.as_str(),
category = kind_label(self.kind),
message = message,
retry_seconds,
redactable = matches!(self.edit_policy, MessageEditPolicy::Redact),
metadata_len = self.metadata.len() as u64,
www_authenticate = self.www_authenticate.as_deref(),
trace_id = trace_id.as_deref(),
"app error constructed"
);
}

/// Create a new [`Error`] with a kind and message.
Expand Down
Loading