RUST 基于map_err的卫语句错误处理方式
- 使用扩展方法
use tracing::error;
use eyre::Result;
pub trait LogErrExt<T> {
/// 记录错误日志后返回原 Result
///
/// error!("[{}] 错误详情: {}", context, e);
fn log_err(self, context: &str) -> Result<T>;
}
impl<T> LogErrExt<T> for Result<T> {
fn log_err(self, context: &str) -> Result<T> {
self.map_err(|e| {
error!("[{}] 错误详情: {}", context, e);
e
})
}
}
// 2025-07-11:
// 上面的写法在实际应用中还存在一点问题,项目中可能存在各种Result,
// 例如自定义封装的 AppResult<T>、RspResult<T>等等,
// 因此不应该只为 eyre::Result 实现 LogErrExt trait,
// 推荐以下改写
pub trait LogErrExt<T, E> {
/// 记录错误日志后返回原 Result
///
/// error!("[{}] 错误详情: {}", context, e);
fn log_err(self, context: &str) -> Self;
}
impl<T, E> LogErrExt<T, E> for Result<T, E>
where
E: std::fmt::Display
{
fn log_err(self, context: &str) -> Self {
self.map_err(|e| {
tracing::error!("[{}] 错误详情: {}", context, e);
e
})
}
}
// Option 同理也可以使用拓展方法,但是一般Option类型没有错误信息,所以一般直接else打印None就行
let Some(ticker) = ticker_data.get(0) else { // 直接处理else
error!("thread A: get_ticker_price: ticker is none");
continue;
};
// 还是记录一下Option的用法,毕竟也算符合DRY(Don't Repeat Yourself) 原则的用法
pub trait LogOptionExt<T> {
fn log_none(self, msg: &str) -> Option<T>;
}
impl<T> LogOptionExt<T> for Option<T> {
fn log_none(self, msg: &str) -> Option<T> {
if self.is_none() {
error!("[{}] 错误详情: Option is None", msg);
}
self
}
}
// 使用方法
loop {
let Some(bn) = log.block_number.log_none("log.transaction_hash") else {
continue;
};
}
- 使用卫语句, 避免了大量match写法 或者if else嵌套
tokio::spawn(async move {
let mut interval = interval(Duration::from_secs(60));
let lbank_client = LBankClient::new();
loop {
interval.tick().await;
info!(">>>>>===== start thread A working: get fibo_usdt price =====>>>>>");
let Ok(ticker_data) = <LBankClient as LBank>::get_ticker_price(&lbank_client).await.log_err("thread A: get_ticker_price") else {
continue;
};
let Some(ticker) = ticker_data.get(0) else {
error!("thread A: get_ticker_price: ticker is none");
continue;
};
if ticker.symbol != "fibo_usdt" {
error!("thread A: get_ticker_price: symbol != fibo_usdt");
continue;
};
let Ok(price) = ticker.price.parse::<f64>() else {
error!("thread A: get_ticker_price: price parse f64 error");
continue;
};
if price == 0.0 {
error!("thread A: get_ticker_price: price == 0");
continue;
}
if let Err(_) = FiboPrice::create(ticker.price.clone()).await.log_err("thread A: FiboPrice::create") {
continue;
}
info!("thread A: successful record fibo_usdt price={}", ticker.price);
}
});