本文档介绍了OpenZeppelin Monitor的结构化错误处理系统,该系统提供了丰富的上下文信息和跨服务边界的跟踪能力。文档通过实例展示了错误如何在系统中传播,并详细描述了错误结构,包括错误上下文和特定于域的错误类型。此外,还提供了错误处理指南,说明了何时使用哪种模式来创建和跟踪错误,例如跨域边界时转换为特定于域的错误类型,以及使用with_context()
添加信息。
OpenZeppelin Monitor 使用结构化的错误处理系统,该系统在服务边界之间提供丰富的上下文和跟踪功能。 让我们从一个现实世界的例子开始,了解错误如何在我们的系统中流动。
让我们跟踪错误如何在我们的区块链监控系统中传播:
底层传输 ( endpoint_manager.rs
)
// 使用特定上下文创建基本错误
async fn send_raw_request(...) -> Result<Value, anyhow::Error> {
let response = client.post(...)
.await
.map_err(|e| anyhow::anyhow!("Failed to send request: {}", e))?;
if !status.is_success() {
return Err(anyhow::anyhow!("HTTP error {}: {}", status, error_body));
}
}
客户端层 ( evm/client.rs
)
// 向底层错误添加业务上下文
async fn get_transaction_receipt(...) -> Result<EVMTransactionReceipt, anyhow::Error> {
let response = self.alloy_client
.send_raw_request(...)
.await
.with_context(|| format!("Failed to get transaction receipt: {}", tx_hash))?;
if receipt_data.is_null() {
return Err(anyhow::anyhow!("Transaction receipt not found"));
}
}
过滤器层 ( evm/filter.rs
)
// 转换为特定于域的错误
async fn filter_block(...) -> Result<Vec<MonitorMatch>, FilterError> {
let receipts = match futures::future::join_all(receipt_futures).await {
Ok(receipts) => receipts,
Err(e) => {
return Err(FilterError::network_error(
format!("Failed to get transaction receipts for block {}", block_num),
Some(e.into()),
None,
));
}
};
}
发生此错误时,它会生成以下日志:
ERROR filter_block: openzeppelin_monitor::utils::error: Error occurred,
error.message: Failed to get transaction receipts for block 15092829,
error.trace_id: a464d73c-5992-4cb5-a002-c8d705bfef8d,
error.timestamp: 2025-03-14T09:42:03.412341+00:00,
error.chain: Failed to get receipt for transaction 0x7722194b65953085fe1e9ec01003f1d7bdd6258a0ea5c91a59da80419513d95d
Caused by: HTTP error 429 Too Many Requests: {"code":-32007,"message":"[Exceeded request limit per second]"}
network: ethereum_mainnet
我们系统中的每个错误都包含详细的上下文信息:
pub struct ErrorContext {
/// 错误消息
pub message: String,
/// 源错误(如果有)
pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
/// 用于错误跟踪的唯一跟踪 ID
pub trace_id: String,
/// 发生错误时的时间戳
pub timestamp: DateTime<Utc>,
/// 可选的键值元数据
pub metadata: HashMap<String, String>,
}
模块 | 错误类型 | 描述 |
---|---|---|
Configuration |
ConfigError |
- ValidationError - 配置验证失败<br> <br>- ParseError - 配置解析问题<br> <br>- FileError - 文件系统相关错误<br> <br>- Other - 未分类的错误 |
Security |
SecurityError |
- ValidationError - 安全验证失败<br> <br>- ParseError - 安全数据解析问题<br> <br>- NetworkError - 安全服务连接问题<br> <br>- Other - 未分类的安全相关错误 |
Blockchain Service |
BlockChainError |
- ConnectionError - 网络连接问题<br> <br>- RequestError - 格式错误的请求或无效的响应<br> <br>- BlockNotFound - 找不到请求的区块<br> <br>- TransactionError - 交易处理失败<br> <br>- InternalError - 内部客户端错误<br> <br>- ClientPoolError - 客户端池相关问题<br> <br>- Other - 未分类的错误 |
Block Watcher Service |
BlockWatcherError |
- SchedulerError - 区块监视调度问题<br> <br>- NetworkError - 网络连接问题<br> <br>- ProcessingError - 区块处理失败<br> <br>- StorageError - 存储操作失败<br> <br>- BlockTrackerError - 区块跟踪问题<br> <br>- Other - 未分类的错误 |
Filter Service |
FilterError |
- BlockTypeMismatch - 区块类型验证失败<br> <br>- NetworkError - 网络连接问题<br> <br>- InternalError - 内部处理错误<br> <br>- Other - 未分类的错误 |
Notification Service |
NotificationError |
- NetworkError - 网络连接问题<br> <br>- ConfigError - 配置问题<br> <br>- InternalError - 内部处理错误<br> <br>- ExecutionError - 脚本执行失败<br> <br>- Other - 未分类的错误 |
Repository |
RepositoryError |
- ValidationError - 数据验证失败<br> <br>- LoadError - 数据加载问题<br> <br>- InternalError - 内部处理错误<br> <br>- Other - 未分类的错误 |
Script Utils |
ScriptError |
- NotFound - 找不到资源错误<br> <br>- ExecutionError - 脚本执行失败<br> <br>- ParseError - 脚本解析问题<br> <br>- SystemError - 系统级错误<br> <br>- Other - 未分类的错误 |
Trigger Service |
TriggerError |
- NotFound - 找不到资源错误<br> <br>- ExecutionError - 触发器执行失败<br> <br>- ConfigurationError - 触发器配置问题<br> <br>- Other - 未分类的错误 |
Monitor Executor |
MonitorExecutionError |
- NotFound - 找不到资源错误<br> <br>- ExecutionError - 监控器执行失败<br> <br>- Other - 未分类的错误 |
场景 | 方法 |
---|---|
跨越域边界 | 使用自定义错误构造器转换为特定于域的错误类型 |
同一域内 | 使用 .with_context() 添加信息,同时保持错误类型 |
外部 API 边界 | 始终转换为你的域的错误类型,以避免泄漏实现细节 |
创建没有源的配置错误
let error = ConfigError::validation_error(
"Invalid network configuration",
None,
Some(HashMap::from([\
("network", "ethereum"),\
("field", "rpc_url")\
]))
);
创建带有源的配置错误
let io_error = std::io::Error::new(std::io::ErrorKind::Other, "Failed to read file");
let error = ConfigError::validation_error(
"Invalid network configuration",
Some(io_error.into()),
None
);
##[instrument(skip_all, fields(network = %_network.slug))]
async fn filter_block(
&self,
client: &T,
_network: &Network,
block: &BlockType,
monitors: &[Monitor],
) -> Result<Vec<MonitorMatch>, FilterError> {
tracing::debug!("Processing block {}", block_number);
// ...
}
关键方面:
skip_all
- 跳过函数参数的自动检测以提高性能
fields(…)
- 添加我们要跟踪的特定字段(如网络 slug)
tracing::debug!
- 为重要操作添加调试级别的 span
- 原文链接: docs.openzeppelin.com/mo...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!