pub mod domain {
pub const STATE: &str = "degenbot::state";
pub const PATH: &str = "degenbot::path";
pub const SOLVER: &str = "degenbot::solver";
pub const SIM: &str = "degenbot::sim";
pub const PUMP: &str = "degenbot::pump";
pub const EXEC: &str = "degenbot::exec";
pub const VERIFY: &str = "degenbot::verify";
pub const INGEST: &str = "degenbot::ingest";
pub const RPC: &str = "degenbot::rpc";
pub const AAVE: &str = "degenbot::aave";
pub const ALL: [&str; 10] = [
STATE, PATH, SOLVER, SIM, PUMP, EXEC, VERIFY, INGEST, RPC, AAVE,
];
#[must_use]
pub fn is_valid(target: &str) -> bool {
ALL.contains(&target)
}
}
pub const ENGINE_SPAN_PREFIX: &str = "degenbot.";
#[must_use]
pub fn in_engine_span() -> bool {
tracing::Span::current()
.metadata()
.is_some_and(|meta| meta.name().starts_with(ENGINE_SPAN_PREFIX))
}
static DIAG_GUARD_WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub fn guard_diag(context: &str) {
if in_engine_span() {
return;
}
#[cfg(feature = "strict-engine-spans")]
debug_assert!(
false,
"diagnostic emitted outside an engine span from {context} (ADR-043 §3)"
);
if !DIAG_GUARD_WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) {
tracing::warn!(
target: DIAGNOSTIC_TARGET,
context,
"diagnostic emitted outside an engine span (this warning fires once; ADR-043 §3)"
);
}
}
pub const DIAGNOSTIC_TARGET: &str = "degenbot::diag";
pub fn install_panic_hook() {
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let thread = std::thread::current();
let thread = thread.name().unwrap_or("<unnamed>").to_owned();
let payload = info
.payload()
.downcast_ref::<&str>()
.map(|s| (*s).to_owned())
.or_else(|| info.payload().downcast_ref::<String>().cloned())
.unwrap_or_else(|| info.to_string());
tracing::Span::current().record("otel.status_code", "ERROR");
tracing::error!(
target: DIAGNOSTIC_TARGET,
exception_type = "panic",
thread,
payload,
"panic captured"
);
previous(info);
}));
}
#[macro_export]
macro_rules! telemetry_target {
(state) => {
$crate::telemetry::domain::STATE
};
(path) => {
$crate::telemetry::domain::PATH
};
(solver) => {
$crate::telemetry::domain::SOLVER
};
(sim) => {
$crate::telemetry::domain::SIM
};
(pump) => {
$crate::telemetry::domain::PUMP
};
(exec) => {
$crate::telemetry::domain::EXEC
};
(verify) => {
$crate::telemetry::domain::VERIFY
};
(ingest) => {
$crate::telemetry::domain::INGEST
};
(rpc) => {
$crate::telemetry::domain::RPC
};
(aave) => {
$crate::telemetry::domain::AAVE
};
($other:ident) => {
::core::compile_error!(::core::concat!(
"unknown telemetry domain `",
::core::stringify!($other),
"`; valid domains: state,path,solver,sim,pump,exec,verify,ingest,rpc,aave"
))
};
}
#[macro_export]
macro_rules! diag {
(domain = $domain:ident, $($rest:tt)*) => {{
$crate::telemetry::guard_diag(::core::module_path!());
::tracing::debug!(target: $crate::telemetry_target!($domain), $($rest)*)
}};
}
#[macro_export]
macro_rules! diag_trace {
(domain = $domain:ident, $($rest:tt)*) => {{
$crate::telemetry::guard_diag(::core::module_path!());
::tracing::trace!(target: $crate::telemetry_target!($domain), $($rest)*)
}};
}
#[macro_export]
macro_rules! op_debug {
(domain = $domain:ident, $($rest:tt)*) => {
::tracing::debug!(target: $crate::telemetry_target!($domain), $($rest)*)
};
}
#[macro_export]
macro_rules! op_info {
(domain = $domain:ident, $($rest:tt)*) => {
::tracing::info!(target: $crate::telemetry_target!($domain), $($rest)*)
};
}
#[macro_export]
macro_rules! op_warn {
(domain = $domain:ident, $($rest:tt)*) => {
::tracing::warn!(target: $crate::telemetry_target!($domain), $($rest)*)
};
}
#[macro_export]
macro_rules! op_error {
(domain = $domain:ident, $($rest:tt)*) => {
::tracing::error!(target: $crate::telemetry_target!($domain), $($rest)*)
};
}
#[macro_export]
macro_rules! op_span {
(domain = $domain:ident, $name:literal $(, $($field:tt)*)?) => {
::tracing::info_span!(
target: $crate::telemetry_target!($domain),
$name $(, $($field)*)?
)
};
}
pub const RETIRED_ENV_NAMES: &[(&str, &str)] = &[
("DEGENBOT_VERIFY_DBG", "degenbot::verify=debug"),
("DEGENBOT_V2_CALC_TRACE", "degenbot::sim=debug"),
("DEGENBOT_SIM_LOG_REVERTED_SWAPS", "degenbot::sim=debug"),
("DEGENBOT_SIM_DIVERGENCE_LOG", "degenbot::sim=debug"),
("DEGENBOT_DUMP_CALL_TRACE", "degenbot::sim=trace"),
("DEGENBOT_DUMP_TICK_MAPS", "degenbot::state=trace"),
("DEGENBOT_WS_TRACE", "degenbot::ingest=debug"),
("DEGENBOT_DRAIN_DBG", "degenbot::pump=debug"),
("DEGENBOT_TRACE_DISPATCH", "degenbot::pump=debug"),
("DEGENBOT_TRACE_REGISTER_SEED", "degenbot::path=debug"),
("DEGENBOT_TRACE_LIQUIDITY", "degenbot::state=debug"),
("DEGENBOT_TRACE_TICK", "degenbot::state=debug"),
("DEGENBOT_GATE_TRACE", "degenbot::solver=debug"),
("DEGENBOT_AAVE_EVTRACE", "degenbot::aave=debug"),
("DEGENBOT_AAVE_TX_TRACE", "degenbot::aave=debug"),
(
"DEGENBOT_LOG_FMT",
"the console writer is derived (one writer per process)",
),
];
pub fn warn_retired_env_names() {
for (name, directive) in RETIRED_ENV_NAMES {
if let Ok(value) = std::env::var(name) {
crate::op_warn!(
domain = pump,
retired_env = %name,
value = %value,
equivalent = %directive,
"retired telemetry flag set; use the domain directive instead (ADR-043 section 5)"
);
}
}
}
#[cfg(test)]
mod tests {
use super::domain;
use std::fmt::Write as _;
use std::sync::{Arc, Mutex};
use tracing_subscriber::layer::SubscriberExt;
static PANIC_HOOK_LOCK: Mutex<()> = Mutex::new(());
#[derive(Clone, Default)]
struct Capture {
events: Arc<Mutex<Vec<String>>>,
records: Arc<Mutex<Vec<String>>>,
}
impl<S: tracing::Subscriber> tracing_subscriber::Layer<S> for Capture {
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
if *event.metadata().level() != tracing::Level::ERROR {
return;
}
let mut text = String::new();
event.record(&mut Fmt(&mut text));
self.events
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(text);
}
fn on_record(
&self,
_span: &tracing::span::Id,
values: &tracing::span::Record<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
let mut text = String::new();
values.record(&mut Fmt(&mut text));
self.records
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(text);
}
}
struct Fmt<'a>(&'a mut String);
impl tracing::field::Visit for Fmt<'_> {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
let _ = write!(self.0, "{}={value:?} ", field.name());
}
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
let _ = write!(self.0, "{}={value} ", field.name());
}
}
#[test]
fn domain_set_is_closed_and_complete() {
assert!(domain::is_valid(domain::SIM));
assert!(domain::is_valid(domain::AAVE));
assert!(!domain::is_valid("degenbot::diag"));
assert!(!domain::is_valid("sim"));
assert_eq!(domain::ALL.len(), 10);
}
#[test]
fn engine_span_detection_uses_the_degenbot_prefix() {
let subscriber = tracing_subscriber::registry();
tracing::subscriber::with_default(subscriber, || {
assert!(!super::in_engine_span());
let span = tracing::info_span!("degenbot.test.engine");
let _guard = span.enter();
assert!(super::in_engine_span());
});
}
#[test]
#[expect(clippy::panic)] fn panic_hook_emits_one_error_and_marks_the_active_span_error() {
let _guard = PANIC_HOOK_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let capture = Capture::default();
let subscriber = tracing_subscriber::registry().with(capture.clone());
tracing::subscriber::with_default(subscriber, || {
let span = tracing::info_span!(
"degenbot.test.engine",
otel.status_code = tracing::field::Empty,
);
let _entered = span.enter();
super::install_panic_hook();
let _ = std::panic::catch_unwind(|| panic!("boom-facade-123"));
let _ = std::panic::take_hook();
});
let events = capture
.events
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
assert_eq!(events.len(), 1, "exactly one ERROR per panic: {events:?}");
assert!(
events[0].contains("boom-facade-123"),
"payload carried: {events:?}"
);
assert!(
events[0].contains("thread="),
"thread name carried: {events:?}"
);
drop(events);
let records = capture
.records
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
assert!(
records
.iter()
.any(|r| r.contains("otel.status_code") && r.contains("ERROR")),
"active span marked Status::Error: {records:?}"
);
}
#[test]
fn facade_macros_expand_on_the_domain_target() {
let subscriber = tracing_subscriber::registry();
tracing::subscriber::with_default(subscriber, || {
crate::op_info!(domain = sim, block = 1u64, "sim probe");
let span = crate::op_span!(domain = sim, "degenbot.sim.probe", block = 1u64);
let _guard = span.enter();
});
}
}