Saltar al contenido
# 🧵 Log Buffer Truncation + SQLite Export ## 🤔 ¿Qué hago? ¿Cómo lo hago? ¿Y para qué lo hago? **¿Qué hago:** Klaus-proxy mantiene un buffer de logs en memoria con tamaño limitado (máx 10,000 eventos). Cuando se llena, automáticamente remueve los eventos más antiguos (FIFO). Los logs pueden exportarse a SQLite para persistencia y auditoría. **¿Cómo lo hago:** - Buffer circular (`deque(maxlen=10000)`) en `proxy/log_buffer.py` - Evento nuevo → auto-remove del más antiguo si está lleno - Endpoint POST `/logs/export` persiste logs a SQLite - Tabla `logs` en `/data/analytics.db` con schema simple **¿Para qué lo hago:** - Proteger memoria en producción (bounded footprint ~2MB) - Evitar OOM en operaciones prolongadas - Mantener historial exportable para compliance/auditoría - Permite debugging retrospectivo sin reiniciar el proxy --- ## 🏗️ Arquitectura ```mermaid graph TD A["🔄 LogRecord<br/>Handler.emit"] --> B["deque.append"] B --> C{"Buffer<br/>Full?"} C -->|Yes| D["Auto-remove oldest<br/>FIFO"] C -->|No| E["Store in memory"] F["GET /logs<br/>Last N entries"] --> E G["GET /logs/stream<br/>SSE real-time"] --> E H["POST /logs/export<br/>Persist"] --> I["SQLite<br/>analytics.db"] E -.-> J["~2MB memory<br/>max"] E -.-> K["deque<br/>maxlen=10k"] ``` --- ## 📊 Límites y recursos | Métrica | Valor | Notas | | --- | --- | --- | | **Max eventos** | 10,000 | Configurable en `MAX_EVENTS` | | **Memory footprint** | ~2 MB | ~200 bytes/evento | | **Rotación** | FIFO automática | Evento nuevo remueve oldest | | **Persistencia** | SQLite | Optional via POST /logs/export | | **TTL en memoria** | ~10 días | Con 1k req/día | --- ## 🔌 API Endpoints ### GET /logs Últimos N eventos del buffer. ```bash curl http://localhost:8000/logs?n=50 ``` ### GET /logs/stream Streaming SSE de eventos en tiempo real. ```bash curl http://localhost:8000/logs/stream ``` ### GET /logs/stats Estadísticas del buffer. ```bash curl http://localhost:8000/logs/stats ``` **Response:** ```json { "current_size": 7234, "max_size": 10000, "utilization_pct": 72.34, "oldest": {"ts": "...", "level": "INFO", "name": "...", "msg": "..."}, "newest": {"ts": "...", "level": "INFO", "name": "...", "msg": "..."} } ``` ### POST /logs/export Exporta buffer completo a SQLite. ```bash curl -X POST http://localhost:8000/logs/export ``` **Response:** ```json { "success": true, "exported_count": 2347, "db_path": "/data/analytics.db", "exported_at": "2026-09-17T13:05:30.123456+00:00" } ``` **Schema SQLite:** ```sql CREATE TABLE logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts TEXT NOT NULL, level TEXT NOT NULL, name TEXT NOT NULL, msg TEXT NOT NULL, exported_at TEXT NOT NULL ); ``` --- ## 💻 Implementación ### proxy/log_buffer.py - `MAX_EVENTS = 10_000` — límite circular - `_entries: deque[...]` — buffer FIFO - `get_recent(n)` — últimos n eventos - `get_stats()` — metadata - `stream_sse(request)` — SSE streaming - `export_to_db(db_path)` — persiste a SQLite ### proxy/main.py endpoints - `GET /logs` — últimos 200 eventos - `GET /logs/stream` — SSE - `GET /logs/stats` — estadísticas - `POST /logs/export` — exporta a BD --- ## 🧪 Tests (8 tests) | Caso | Descripción | Status | | --- | --- | --- | | TC1 | Buffer starts empty | ✅ | | TC2 | get_recent returns correct slice | ✅ | | TC3 | Circular rotation (LRU) | ✅ | | TC4 | get_stats() metadata | ✅ | | TC5 | SSE streaming | ✅ | | TC6 | Export to SQLite | ✅ | | TC7 | Concurrent writes (thread safety) | ✅ | | TC8 | Edge cases (empty, overflow) | ✅ | ```bash PYTHONPATH=. pytest tests/test_log_buffer.py -v ``` --- ## ⚠️ Notas de producción 1. **SQLite single-writer:** Múltiples procesos → consider mutex 2. **Table grows indefinitely:** Add cleanup/partitioning para large scale 3. **Memory verified:** ~2MB en operación normal (bounded) 4. **Configurable:** Cambiar `MAX_EVENTS` según necesidad --- ## ✅ Criterios de Aceptación P3.1 - ✅ Buffer nunca excede 10k eventos - ✅ Memory <50MB con carga sostenida - ✅ LRU removal automático - ✅ Endpoint /logs/export funcional - ✅ Tests: 8/8 pass - ✅ Documentación completa