Monitor
Error Handling
Overview
The OpenZeppelin Monitor uses a structured error handling system that provides rich context and tracing capabilities across service boundaries. Let’s start with a real-world example of how errors flow through our system.
Error Flow Example
Let’s follow how an error propagates through our blockchain monitoring system:
Low-level Transport (endpoint_manager.rs
)
// Creates basic errors with specific context
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));
}
}
Client Layer (evm/client.rs
)
// Adds business context to low-level errors
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"));
}
Filter Layer (evm/filter.rs
)
// Converts to domain-specific errors
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,
));
}
};
}
When this error occurs, it produces the following log:
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
Error Structure
Error Context
Every error in our system includes detailed context information:
pub struct ErrorContext
/// The error message
pub message: String,
/// The source error (if any)
pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
/// Unique trace ID for error tracking
pub trace_id: String,
/// Timestamp when the error occurred
pub timestamp: DateTime<Utc>,
/// Optional key-value metadata
pub metadata: HashMap<String, String>,
Domain-Specific Error Types
Module | Error Type | Description |
---|---|---|
**Configuration** | ConfigError | * ValidationError - Configuration validation failures * ParseError - Configuration parsing issues * FileError - File system related errors * Other - Unclassified errors |
**Security** | SecurityError | * ValidationError - Security validation failures * ParseError - Security data parsing issues * NetworkError - Security service connectivity issues * Other - Unclassified security-related errors |
**Blockchain Service** | BlockChainError | * ConnectionError - Network connectivity issues * RequestError - Malformed requests or invalid responses * BlockNotFound - Requested block not found * TransactionError - Transaction processing failures * InternalError - Internal client errors * ClientPoolError - Client pool related issues * Other - Unclassified errors |
**Block Watcher Service** | BlockWatcherError | * SchedulerError - Block watching scheduling issues * NetworkError - Network connectivity problems * ProcessingError - Block processing failures * StorageError - Storage operation failures * BlockTrackerError - Block tracking issues * Other - Unclassified errors |
**Filter Service** | FilterError | * BlockTypeMismatch - Block type validation failures * NetworkError - Network connectivity issues * InternalError - Internal processing errors * Other - Unclassified errors |
**Notification Service** | NotificationError | * NetworkError - Network connectivity issues * ConfigError - Configuration problems * InternalError - Internal processing errors * ExecutionError - Script execution failures * Other - Unclassified errors |
**Repository** | RepositoryError | * ValidationError - Data validation failures * LoadError - Data loading issues * InternalError - Internal processing errors * Other - Unclassified errors |
**Script Utils** | ScriptError | * NotFound - Resource not found errors * ExecutionError - Script execution failures * ParseError - Script parsing issues * SystemError - System-level errors * Other - Unclassified errors |
**Trigger Service** | TriggerError | * NotFound - Resource not found errors * ExecutionError - Trigger execution failures * ConfigurationError - Trigger configuration issues * Other - Unclassified errors |
**Monitor Executor** | MonitorExecutionError | * NotFound - Resource not found errors * ExecutionError - Monitor execution failures * Other - Unclassified errors |
Error Handling Guidelines
When to Use Each Pattern
Scenario | Approach |
---|---|
Crossing Domain Boundaries | Convert to domain-specific error type using custom error constructors |
Within Same Domain | Use .with_context() to add information while maintaining error type |
External API Boundaries | Always convert to your domain’s error type to avoid leaking implementation details |
Error Creation Examples
Creating a Configuration Error without a source
let error = ConfigError::validation_error(
"Invalid network configuration",
None,
Some(HashMap::from([
("network", "ethereum"),
("field", "rpc_url")
]))
);
Creating a Configuration Error with a source
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
);
Tracing with #[instrument]
#[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);
// ...
}
Key aspects:
skip_all
- Skips automatic instrumentation of function parameters for performancefields(...)
- Adds specific fields we want to track (like network slug)tracing::debug!
- Adds debug-level spans for important operations