Refactor services and update documentation
- Refactor ban_service.py with improved error handling - Refactor blocklist_service.py for better code organization - Update geo_cache.py with performance improvements - Update backend development guide and task documentation - Update runner.csx script Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -611,6 +611,44 @@ async def _fail2ban_connection_handler(request: Request, exc: Fail2BanConnection
|
||||
)
|
||||
```
|
||||
|
||||
### Service Error Handling Policy
|
||||
|
||||
Service methods often call external systems (HTTP APIs, databases, fail2ban) that can fail in diverse ways. To maintain debuggability and predictability:
|
||||
|
||||
**Never catch `Exception` broadly** except where unavoidable. Instead, catch specific exception types that match the operation's failure modes:
|
||||
|
||||
- **Network I/O**: `TimeoutError`, `aiohttp.ClientError`, `asyncio.TimeoutError`
|
||||
- **File I/O**: `OSError` (includes `IOError`, `FileNotFoundError`, `PermissionError`)
|
||||
- **JSON parsing**: `json.JSONDecodeError`, `ValueError`
|
||||
- **Database errors**: `aiosqlite.Error` and derivatives (caught as `OSError`)
|
||||
- **Third-party libraries**: Specific exception classes (e.g., `geoip2.errors.GeoIP2Error`)
|
||||
|
||||
**When catching service-critical exceptions**:
|
||||
1. Catch the specific exception types for the operation.
|
||||
2. Log with the exception type and relevant context.
|
||||
3. Return a safe fallback (empty dict, None, etc.) or re-raise if the service cannot function.
|
||||
|
||||
**When truly unavoidable broad catches are needed** (e.g., retrying transient network failures):
|
||||
1. Place specific catches first.
|
||||
2. Add one final `except Exception` **after** specific cases, with `error_type="unexpected"` logged to flag surprises.
|
||||
3. Document why broad catching is necessary (e.g., "tests use mock objects that may raise arbitrary exceptions").
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
async def lookup_batch(ips: list[str], http_session: aiohttp.ClientSession) -> dict[str, GeoInfo]:
|
||||
"""Resolve multiple IPs, returning empty map on failure."""
|
||||
try:
|
||||
result = await http_session.post(url, json=payload, timeout=timeout)
|
||||
except (TimeoutError, aiohttp.ClientError) as exc:
|
||||
# Expected network failures — log and return empty result
|
||||
log.warning("geo_batch_http_failed", error=type(exc).__name__)
|
||||
return {}
|
||||
except Exception as exc:
|
||||
# Unexpected — log as error for investigation
|
||||
log.error("geo_batch_unexpected_error", error=type(exc).__name__)
|
||||
return {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Testing
|
||||
|
||||
Reference in New Issue
Block a user