345 lines
11 KiB
Markdown
345 lines
11 KiB
Markdown
# Structured Error Logging Guide
|
|
|
|
## Overview
|
|
|
|
Every error in the MEV bot must now include:
|
|
1. **Reason** - Why the error occurred (root cause)
|
|
2. **Origin** - Where it happened (file, function, line - automatically tracked)
|
|
3. **Context** - What we were trying to do
|
|
4. **Category** - Type of error (Network, Parsing, Validation, etc.)
|
|
5. **Severity** - How critical is this error
|
|
6. **Details** - Additional structured data
|
|
|
|
## Quick Start
|
|
|
|
### Before (Old Way - BAD)
|
|
```go
|
|
// ❌ NO CONTEXT - Don't do this anymore
|
|
logger.Error("Failed to get latest block")
|
|
|
|
// ❌ MINIMAL CONTEXT - Still not enough
|
|
logger.Error("Failed to get latest block:", err)
|
|
```
|
|
|
|
### After (New Way - GOOD)
|
|
```go
|
|
import pkgerrors "github.com/fraktal/mev-beta/pkg/errors"
|
|
|
|
// ✅ FULL CONTEXT - Do this instead
|
|
logger.ErrorStructured(
|
|
pkgerrors.NetworkError("Failed to fetch latest block").
|
|
WithReason("RPC endpoint returned 429 rate limit").
|
|
WithAction("Polling for new blocks to detect MEV opportunities").
|
|
WithImpact("Block processing delayed, may miss time-sensitive arbitrage opportunities").
|
|
WithSuggestion("Reduce polling frequency or use backup RPC endpoint").
|
|
WithDetail("endpoint", rpcURL).
|
|
WithDetail("blockNumber", lastBlock).
|
|
Wrap(err),
|
|
)
|
|
```
|
|
|
|
## Error Categories
|
|
|
|
### Network Errors
|
|
```go
|
|
err := pkgerrors.NetworkError("DNS resolution failed").
|
|
WithReason("Nameserver timeout for arb1.arbitrum.io").
|
|
WithAction("Connecting to Arbitrum RPC endpoint").
|
|
WithImpact("Cannot fetch blockchain data, all MEV operations suspended").
|
|
WithSuggestion("Check DNS configuration in /etc/resolv.conf or use IP address").
|
|
WithDetail("hostname", "arb1.arbitrum.io").
|
|
WithDetail("nameserver", "8.8.8.8").
|
|
Wrap(originalErr)
|
|
|
|
logger.ErrorStructured(err)
|
|
```
|
|
|
|
**Output**:
|
|
```
|
|
Main log (compact):
|
|
2025/11/02 20:19:03 [ERROR] [NETWORK/ERROR] DNS resolution failed | Reason: Nameserver timeout for arb1.arbitrum.io | Action: Connecting to Arbitrum RPC endpoint | Origin: pkg/arbitrum/connection.go:142 | Underlying: lookup arb1.arbitrum.io: i/o timeout
|
|
|
|
Error log (detailed):
|
|
2025/11/02 20:19:03 [ERROR] [ERR-1730584743-NETWORK] NETWORK/ERROR: DNS resolution failed
|
|
Origin: pkg/arbitrum/connection.go:142 (ConnectToRPC)
|
|
ErrorID: ERR-1730584743-NETWORK
|
|
Timestamp: 2025-11-02T20:19:03Z
|
|
Reason: Nameserver timeout for arb1.arbitrum.io
|
|
Action: Connecting to Arbitrum RPC endpoint
|
|
Impact: Cannot fetch blockchain data, all MEV operations suspended
|
|
Suggestion: Check DNS configuration in /etc/resolv.conf or use IP address
|
|
Details:
|
|
- hostname: arb1.arbitrum.io
|
|
- nameserver: 8.8.8.8
|
|
Underlying: lookup arb1.arbitrum.io: i/o timeout
|
|
```
|
|
|
|
### Parsing Errors
|
|
```go
|
|
err := pkgerrors.ParsingError("Failed to decode swap event").
|
|
WithReason("ABI signature mismatch - expected Swap(address,address,int256,int256) but got different signature").
|
|
WithAction("Parsing Uniswap V3 swap transaction for arbitrage detection").
|
|
WithImpact("This swap will not be considered for arbitrage opportunities").
|
|
WithSuggestion("Update ABI definition or add support for this swap variant").
|
|
WithDetail("txHash", "0x1234...").
|
|
WithDetail("poolAddress", "0xabcd...").
|
|
WithDetail("expectedSig", "0x1c411e9a").
|
|
WithDetail("actualSig", "0x9f2c64").
|
|
Wrap(abiErr)
|
|
|
|
logger.ErrorStructured(err)
|
|
```
|
|
|
|
### Validation Errors
|
|
```go
|
|
err := pkgerrors.ValidationError("Invalid token pair detected").
|
|
WithReason("Token0 address is zero address (0x0000...)").
|
|
WithAction("Validating swap event before profit calculation").
|
|
WithImpact("Skipping this opportunity to avoid calculation errors").
|
|
WithSuggestion("Fix pool detection logic to exclude invalid pools").
|
|
WithDetail("token0", zeroAddress.Hex()).
|
|
WithDetail("token1", token1.Hex()).
|
|
WithDetail("poolAddress", pool.Hex())
|
|
|
|
logger.WarnStructured(err)
|
|
```
|
|
|
|
### Execution Errors
|
|
```go
|
|
err := pkgerrors.ExecutionError("Transaction reverted on-chain").
|
|
WithReason("Insufficient liquidity in target pool at execution time").
|
|
WithAction("Executing flash loan arbitrage transaction").
|
|
WithImpact("Lost gas fees (~0.00008 ETH), no profit captured").
|
|
WithSuggestion("Increase slippage tolerance or implement pre-execution simulation").
|
|
WithDetail("txHash", tx.Hash().Hex()).
|
|
WithDetail("gasUsed", receipt.GasUsed).
|
|
WithDetail("revertReason", revertMsg).
|
|
WithDetail("estimatedProfit", "0.015 ETH").
|
|
WithDetail("actualLoss", "0.00008 ETH")
|
|
|
|
logger.ErrorStructured(err)
|
|
```
|
|
|
|
### Math/Calculation Errors
|
|
```go
|
|
err := pkgerrors.MathError("Profit margin calculation overflow").
|
|
WithReason("AmountOut too small (0.000001 ETH), division by near-zero causes overflow").
|
|
WithAction("Calculating profit margin for arbitrage opportunity").
|
|
WithImpact("Opportunity rejected to prevent extreme values in logs").
|
|
WithSuggestion("Add minimum amount threshold of 0.0001 ETH before calculations").
|
|
WithDetail("amountIn", "0.5 ETH").
|
|
WithDetail("amountOut", "0.000001 ETH").
|
|
WithDetail("netProfit", "-0.00008 ETH")
|
|
|
|
logger.WarnStructured(err)
|
|
```
|
|
|
|
### Configuration Errors
|
|
```go
|
|
err := pkgerrors.ConfigurationError("Invalid RPC configuration").
|
|
WithReason("providers_runtime.yaml missing required 'url' field for primary provider").
|
|
WithAction("Loading RPC provider configuration at startup").
|
|
WithImpact("Cannot connect to blockchain, bot will not start").
|
|
WithSuggestion("Add 'url' field to primary provider configuration").
|
|
WithDetail("configFile", "config/providers_runtime.yaml").
|
|
WithDetail("provider", "primary").
|
|
Wrap(configErr)
|
|
|
|
logger.ErrorStructured(err)
|
|
```
|
|
|
|
## Helper Functions
|
|
|
|
### Quick Error Creation
|
|
```go
|
|
// For common patterns, use helper functions
|
|
err := pkgerrors.NetworkError("Connection timeout")
|
|
err := pkgerrors.ParsingError("ABI decode failed")
|
|
err := pkgerrors.ValidationError("Invalid input")
|
|
err := pkgerrors.ExecutionError("Transaction reverted")
|
|
err := pkgerrors.ConfigurationError("Missing config file")
|
|
err := pkgerrors.MathError("Division by zero")
|
|
err := pkgerrors.SecurityError("Unauthorized access")
|
|
```
|
|
|
|
### Custom Categories and Severities
|
|
```go
|
|
err := pkgerrors.NewStructuredError(
|
|
pkgerrors.CategoryDatabase,
|
|
pkgerrors.SeverityCritical,
|
|
"Failed to save opportunity to database",
|
|
).
|
|
WithReason("Connection pool exhausted, all 10 connections in use").
|
|
WithAction("Persisting arbitrage opportunity for analysis").
|
|
WithImpact("Opportunity data will be lost, cannot track historical performance").
|
|
WithSuggestion("Increase database connection pool size or reduce write frequency")
|
|
```
|
|
|
|
## Migration from Old to New
|
|
|
|
### Pattern 1: Simple Error
|
|
```go
|
|
// OLD
|
|
logger.Error("Failed to parse transaction", "error", err)
|
|
|
|
// NEW
|
|
logger.ErrorStructured(
|
|
pkgerrors.ParsingError("Failed to parse transaction").
|
|
WithReason("Transaction data is incomplete or corrupted").
|
|
WithAction("Parsing pending transaction from mempool").
|
|
WithImpact("Transaction skipped, may miss MEV opportunity").
|
|
Wrap(err),
|
|
)
|
|
```
|
|
|
|
### Pattern 2: Error with Context
|
|
```go
|
|
// OLD
|
|
logger.Error(fmt.Sprintf("Pool %s validation failed: %v", poolAddr, err))
|
|
|
|
// NEW
|
|
logger.ErrorStructured(
|
|
pkgerrors.ValidationError("Pool validation failed").
|
|
WithReason("Pool reserves returned zero values").
|
|
WithAction("Validating pool before adding to arbitrage scan").
|
|
WithImpact("Pool excluded from opportunity detection").
|
|
WithSuggestion("Check if pool is active and has liquidity").
|
|
WithDetail("poolAddress", poolAddr.Hex()).
|
|
Wrap(err),
|
|
)
|
|
```
|
|
|
|
### Pattern 3: Warning
|
|
```go
|
|
// OLD
|
|
logger.Warn("Rate limit exceeded")
|
|
|
|
// NEW
|
|
logger.WarnStructured(
|
|
pkgerrors.NetworkError("RPC rate limit exceeded").
|
|
WithReason("Exceeded 100 requests per second quota").
|
|
WithAction("Fetching pool data for arbitrage detection").
|
|
WithImpact("Reduced scanning speed, may miss fast opportunities").
|
|
WithSuggestion("Implement request batching or use backup endpoint").
|
|
WithDetail("endpoint", rpcURL).
|
|
WithDetail("requestCount", reqCount).
|
|
WithDetail("timeWindow", "1s"),
|
|
)
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Always Provide Reason
|
|
```go
|
|
// ❌ BAD
|
|
WithReason("error occurred")
|
|
|
|
// ✅ GOOD
|
|
WithReason("TCP connection refused - RPC endpoint is down or firewalled")
|
|
```
|
|
|
|
### 2. Be Specific in Actions
|
|
```go
|
|
// ❌ BAD
|
|
WithAction("processing data")
|
|
|
|
// ✅ GOOD
|
|
WithAction("Fetching Uniswap V3 pool reserves for profit calculation")
|
|
```
|
|
|
|
### 3. Describe Real Impact
|
|
```go
|
|
// ❌ BAD
|
|
WithImpact("something might break")
|
|
|
|
// ✅ GOOD
|
|
WithImpact("Arbitrage detection stopped, estimated revenue loss: $50-100/hour")
|
|
```
|
|
|
|
### 4. Give Actionable Suggestions
|
|
```go
|
|
// ❌ BAD
|
|
WithSuggestion("fix the problem")
|
|
|
|
// ✅ GOOD
|
|
WithSuggestion("Restart with PROVIDER_CONFIG_PATH pointing to valid providers_runtime.yaml")
|
|
```
|
|
|
|
### 5. Add Relevant Details
|
|
```go
|
|
// ✅ GOOD
|
|
WithDetail("txHash", tx.Hash().Hex()).
|
|
WithDetail("blockNumber", blockNum).
|
|
WithDetail("gasPrice", gasPrice.String()).
|
|
WithDetail("poolAddress", pool.Hex()).
|
|
WithDetail("attemptNumber", retryCount)
|
|
```
|
|
|
|
## Output Format
|
|
|
|
### Compact (Main Log)
|
|
```
|
|
[NETWORK/ERROR] DNS resolution failed | Reason: Nameserver timeout | Action: Connecting to RPC | Origin: connection.go:142 | Underlying: i/o timeout
|
|
```
|
|
|
|
### Detailed (Error Log)
|
|
```
|
|
[ERR-1730584743-NETWORK] NETWORK/ERROR: DNS resolution failed
|
|
Origin: pkg/arbitrum/connection.go:142 (ConnectToRPC)
|
|
ErrorID: ERR-1730584743-NETWORK
|
|
Timestamp: 2025-11-02T20:19:03Z
|
|
Reason: Nameserver timeout for arb1.arbitrum.io
|
|
Action: Connecting to Arbitrum RPC endpoint
|
|
Impact: Cannot fetch blockchain data, all MEV operations suspended
|
|
Suggestion: Check DNS configuration or use IP address
|
|
Details:
|
|
- hostname: arb1.arbitrum.io
|
|
Underlying: lookup arb1.arbitrum.io: i/o timeout
|
|
```
|
|
|
|
## Error Categories Reference
|
|
|
|
| Category | Severity | Use For |
|
|
|----------|----------|---------|
|
|
| `CategoryNetwork` | ERROR | RPC, DNS, connection issues |
|
|
| `CategoryParsing` | ERROR | ABI decoding, transaction parsing |
|
|
| `CategoryValidation` | WARNING | Input validation, data validation |
|
|
| `CategoryExecution` | CRITICAL | Transaction execution, contract calls |
|
|
| `CategoryConfiguration` | CRITICAL | Config loading, invalid settings |
|
|
| `CategoryDatabase` | ERROR | Database operations |
|
|
| `CategorySecurity` | CRITICAL | Security violations, unauthorized access |
|
|
| `CategoryMath` | ERROR | Arithmetic errors, overflow/underflow |
|
|
| `CategoryInternal` | ERROR | Internal logic errors, unexpected state |
|
|
| `CategoryExternal` | ERROR | External service failures |
|
|
|
|
## Testing
|
|
|
|
```bash
|
|
# Build with new error system
|
|
go build -o mev-bot ./cmd/mev-bot
|
|
|
|
# Check error log for structured format
|
|
tail -f logs/mev_bot_errors.log
|
|
|
|
# Verify all errors have:
|
|
# - Category/Severity
|
|
# - Reason
|
|
# - Action
|
|
# - Origin (file:line)
|
|
```
|
|
|
|
## Benefits
|
|
|
|
1. **Debuggability**: Know exactly where and why each error occurred
|
|
2. **Monitoring**: Can alert on specific error categories
|
|
3. **Analytics**: Track error patterns over time
|
|
4. **Troubleshooting**: Users can quickly understand and fix issues
|
|
5. **Professionalism**: Production-grade error reporting
|
|
|
|
## Next Steps
|
|
|
|
1. Gradually migrate existing `logger.Error()` calls to `logger.ErrorStructured()`
|
|
2. Add error categorization to all new code
|
|
3. Update error handling in critical paths first (RPC, parsing, execution)
|
|
4. Monitor error logs for patterns and improve error messages
|