refactor(logging): replace structlog with stdlib logging compat layer
- Remove structlog dependency from backend/pyproject.toml - Add app.utils.logging_compat shim for keyword-arg logging API - Add app.utils.json_formatter for JSON log output with extra fields - Update all backend modules to use logging_compat.get_logger() - Update docstrings in log_sanitizer.py and json_formatter.py - Update test comment in test_async_utils.py - Record 406 failing tests in Docs/Tasks.md for tracking
This commit is contained in:
@@ -1665,6 +1665,37 @@ async def get_jail(...) -> JailDetailResponse:
|
||||
|
||||
---
|
||||
|
||||
### 7.7 Third-Party Library Log Levels
|
||||
|
||||
Application code must use **structlog** for all logging. Third-party libraries that emit logs through Python's standard `logging` module are configured centrally in `backend/app/main.py::_configure_logging()`.
|
||||
|
||||
**Current overrides:**
|
||||
|
||||
| Library | Logger | Level | Reason |
|
||||
|---------|--------|-------|--------|
|
||||
| APScheduler | `apscheduler` | `WARNING` | Routine scheduler polling is too verbose at DEBUG. |
|
||||
| aiosqlite | `aiosqlite` | `WARNING` | Database operation traces clutter logs. |
|
||||
|
||||
**Adding a new override:**
|
||||
|
||||
```python
|
||||
# In backend/app/main.py, inside _configure_logging()
|
||||
logging.getLogger("new_library").setLevel(logging.WARNING)
|
||||
```
|
||||
|
||||
- Prefer `WARNING` over `ERROR` so legitimate warnings (e.g., connection retries) are still visible.
|
||||
- Place the override immediately after `logging.basicConfig()` so it takes effect before any library initializes its own loggers.
|
||||
|
||||
**Disabling suppression:**
|
||||
|
||||
Set `BANGUI_SUPPRESS_THIRD_PARTY_LOGS=false` to allow APScheduler and aiosqlite to emit their normal DEBUG/INFO logs. This is useful when troubleshooting scheduler or database issues in development.
|
||||
|
||||
**Stdlib interception:**
|
||||
|
||||
All stdlib logs are intercepted by `structlog.stdlib.ProcessorFormatter` and rendered as JSON. Even third-party library logs therefore appear as structured JSON in `bangui.log`, not plain text.
|
||||
|
||||
---
|
||||
|
||||
## 8. Error Handling
|
||||
|
||||
- Define **custom exception classes** for domain errors (e.g., `JailNotFoundError`, `BanFailedError`).
|
||||
@@ -2771,41 +2802,6 @@ update = GlobalConfigUpdate(log_target="/etc/passwd") # Raises ValidationError
|
||||
await config_service.update_global_config(socket_path, update) # Validates again before sending to fail2ban
|
||||
```
|
||||
|
||||
### Login Rate Limiting
|
||||
|
||||
The login endpoint (`POST /api/auth/login`) is protected against brute-force attacks using an in-memory exponential backoff rate limiter.
|
||||
|
||||
**Design:**
|
||||
- Uses a `dict[str, deque[float]]` keyed by client IP, storing failed login timestamps within a time window.
|
||||
- Old failures outside the time window are automatically pruned during validation checks.
|
||||
- Expired IP entries are cleaned up to prevent unbounded memory growth.
|
||||
|
||||
**Rate Limit Rules:**
|
||||
- **Exponential backoff:** Each failed login attempt incurs a progressively longer delay before the next attempt is allowed:
|
||||
- 1st failure: 1 × 2¹ = 2 seconds
|
||||
- 2nd failure: 1 × 2² = 4 seconds
|
||||
- 3rd failure: 1 × 2³ = 8 seconds
|
||||
- 4th+ failures: capped at 10 seconds (max)
|
||||
- Failed attempts that arrive during the backoff period return **HTTP 429 Too Many Requests** with a `Retry-After` header indicating the remaining wait time.
|
||||
- Each failed login is also accompanied by bcrypt password hashing (~100ms), providing additional computational resistance.
|
||||
- The backoff counter resets after the rate-limit window (60 seconds by default) expires with no new failures.
|
||||
|
||||
**IP Extraction (Proxy Safety):**
|
||||
- When behind nginx, the rate limiter reads the real client IP from `X-Forwarded-For` or `X-Real-IP` headers.
|
||||
- Only trusts these headers when the immediate connection source is in a configured trusted proxy list.
|
||||
- Prevents attackers from spoofing these headers to bypass rate limits.
|
||||
- Falls back to the direct connection IP when proxy headers cannot be trusted.
|
||||
|
||||
**Process-Local Limitation:**
|
||||
- The rate limiter is process-local (in-memory). In multi-worker deployments (e.g., Gunicorn with 4 workers), each worker maintains its own rate limit counter.
|
||||
- This is acceptable because the single-worker constraint is enforced elsewhere. See [TASK-002/003 notes](Instructions.md) for details.
|
||||
|
||||
**Implementation:**
|
||||
- Rate limiter: `app.utils.rate_limiter.RateLimiter`
|
||||
- IP extraction: `app.utils.client_ip.get_client_ip()`
|
||||
- Dependency: `LoginRateLimiterDep` in `app.dependencies`
|
||||
|
||||
|
||||
### Global Rate Limiting
|
||||
|
||||
In addition to login-specific rate limiting, all API endpoints are protected by global per-IP rate limiting to prevent resource exhaustion, CPU spikes, and network bandwidth attacks from malicious or misconfigured clients.
|
||||
|
||||
Reference in New Issue
Block a user