V2 Pro: Logging rotativo, Redis cache, Health Check de LibreTranslate y Rate Limiting en botones (#7)
This commit is contained in:
0
utils/__init__.py
Normal file
0
utils/__init__.py
Normal file
BIN
utils/__pycache__/cache.cpython-312.pyc
Normal file
BIN
utils/__pycache__/cache.cpython-312.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/logger.cpython-312.pyc
Normal file
BIN
utils/__pycache__/logger.cpython-312.pyc
Normal file
Binary file not shown.
83
utils/cache.py
Normal file
83
utils/cache.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
Módulo de caché Redis - Bots de Traducción
|
||||
Proporciona una interfaz unificada de caché con Redis como backend
|
||||
y fallback a memoria RAM si Redis no está disponible.
|
||||
"""
|
||||
import os
|
||||
import redis
|
||||
from utils.logger import discord_logger as log
|
||||
|
||||
_redis_client = None
|
||||
|
||||
def get_redis() -> redis.Redis | None:
|
||||
"""Retorna cliente Redis, intentando conectar si no existe. Devuelve None si no disponible."""
|
||||
global _redis_client
|
||||
if _redis_client is not None:
|
||||
return _redis_client
|
||||
|
||||
host = os.getenv("REDIS_HOST", "localhost")
|
||||
port = int(os.getenv("REDIS_PORT", "6379"))
|
||||
password = os.getenv("REDIS_PASSWORD", "translation_redis_secret")
|
||||
db = int(os.getenv("REDIS_DB", "0"))
|
||||
|
||||
try:
|
||||
client = redis.Redis(
|
||||
host=host,
|
||||
port=port,
|
||||
password=password,
|
||||
db=db,
|
||||
decode_responses=True,
|
||||
socket_connect_timeout=2,
|
||||
socket_timeout=2
|
||||
)
|
||||
client.ping()
|
||||
_redis_client = client
|
||||
log.info(f"✅ Redis conectado en {host}:{port}")
|
||||
return _redis_client
|
||||
except Exception as e:
|
||||
log.warning(f"⚠️ Redis no disponible, usando caché en RAM: {e}")
|
||||
return None
|
||||
|
||||
# ─── API de caché genérica (con TTL en segundos) ───────────────────────────
|
||||
|
||||
def cache_get(key: str) -> str | None:
|
||||
"""Obtiene un valor del caché. Devuelve None si no existe o si Redis no está disponible."""
|
||||
r = get_redis()
|
||||
if r:
|
||||
try:
|
||||
return r.get(key)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def cache_set(key: str, value: str, ttl: int = 86400) -> None:
|
||||
"""Guarda un valor en el caché con TTL en segundos (default 24h)."""
|
||||
r = get_redis()
|
||||
if r:
|
||||
try:
|
||||
r.setex(key, ttl, value)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def cache_delete(key: str) -> None:
|
||||
"""Elimina una clave del caché."""
|
||||
r = get_redis()
|
||||
if r:
|
||||
try:
|
||||
r.delete(key)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def cache_increment(key: str, ttl: int = 60) -> int:
|
||||
"""Incrementa un contador atómico en Redis. Ideal para Rate Limiting."""
|
||||
r = get_redis()
|
||||
if r:
|
||||
try:
|
||||
pipe = r.pipeline()
|
||||
pipe.incr(key)
|
||||
pipe.expire(key, ttl)
|
||||
results = pipe.execute()
|
||||
return results[0]
|
||||
except Exception:
|
||||
pass
|
||||
return 0
|
||||
50
utils/logger.py
Normal file
50
utils/logger.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Módulo de Logging Centralizado - Bots de Traducción
|
||||
Proporciona loggers rotativos por día para Discord, Telegram y Panel Web.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
|
||||
# Directorio base para almacenar los logs
|
||||
LOG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "logs")
|
||||
os.makedirs(LOG_DIR, exist_ok=True)
|
||||
|
||||
def _create_logger(name: str, filename: str, level=logging.INFO) -> logging.Logger:
|
||||
"""Crea y configura un logger con salida a archivo rotativo y a consola."""
|
||||
logger = logging.getLogger(name)
|
||||
|
||||
if logger.handlers:
|
||||
return logger # Ya está configurado, no duplicar handlers
|
||||
|
||||
logger.setLevel(level)
|
||||
|
||||
formatter = logging.Formatter(
|
||||
"[%(asctime)s] %(levelname)s [%(name)s] %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
|
||||
# Handler de archivo: rota a medianoche, guarda los últimos 14 días
|
||||
file_handler = TimedRotatingFileHandler(
|
||||
os.path.join(LOG_DIR, filename),
|
||||
when="midnight",
|
||||
interval=1,
|
||||
backupCount=14,
|
||||
encoding="utf-8"
|
||||
)
|
||||
file_handler.setFormatter(formatter)
|
||||
file_handler.suffix = "%Y-%m-%d.log"
|
||||
|
||||
# Handler de consola para visualización en Docker
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(formatter)
|
||||
|
||||
logger.addHandler(file_handler)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
return logger
|
||||
|
||||
# Loggers disponibles para cada componente del ecosistema
|
||||
discord_logger = _create_logger("discord_bot", "discord.log")
|
||||
telegram_logger = _create_logger("telegram_bot", "telegram.log")
|
||||
panel_logger = _create_logger("panel_web", "panel.log")
|
||||
Reference in New Issue
Block a user