Agregar logging estructurado a utils.py
- JSONFormatter, StructuredLogger, setup_logging(), get_logger() - Soporte para logs JSON con contexto - Usar logger en config.py para warnings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,13 @@ from pathlib import Path
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Any
|
||||
|
||||
try:
|
||||
from .utils import get_logger
|
||||
except ImportError:
|
||||
from utils import get_logger
|
||||
|
||||
logger = get_logger("orchestrator.config")
|
||||
|
||||
|
||||
def load_env():
|
||||
"""Carga variables desde .env si existe."""
|
||||
@@ -202,7 +209,7 @@ class Config:
|
||||
with open(self.config_path) as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
except ImportError:
|
||||
print("AVISO: PyYAML no instalado. pip install pyyaml")
|
||||
logger.warning("PyYAML no instalado", suggestion="pip install pyyaml")
|
||||
return {}
|
||||
|
||||
def _parse_settings(self) -> Settings:
|
||||
|
||||
@@ -7,8 +7,178 @@ por múltiples componentes del sistema.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional, Any
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# LOGGING ESTRUCTURADO
|
||||
# =============================================================================
|
||||
|
||||
class JSONFormatter(logging.Formatter):
|
||||
"""Formateador que produce logs en formato JSON estructurado."""
|
||||
|
||||
def __init__(self, service: str = "orchestrator"):
|
||||
super().__init__()
|
||||
self.service = service
|
||||
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
log_data = {
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||||
"level": record.levelname,
|
||||
"service": self.service,
|
||||
"logger": record.name,
|
||||
"message": record.getMessage(),
|
||||
}
|
||||
|
||||
# Agregar información de ubicación en DEBUG
|
||||
if record.levelno <= logging.DEBUG:
|
||||
log_data["location"] = {
|
||||
"file": record.filename,
|
||||
"line": record.lineno,
|
||||
"function": record.funcName,
|
||||
}
|
||||
|
||||
# Agregar excepción si existe
|
||||
if record.exc_info:
|
||||
log_data["exception"] = {
|
||||
"type": record.exc_info[0].__name__ if record.exc_info[0] else None,
|
||||
"message": str(record.exc_info[1]) if record.exc_info[1] else None,
|
||||
}
|
||||
|
||||
# Agregar campos extra
|
||||
if hasattr(record, "extra_fields"):
|
||||
log_data["context"] = record.extra_fields
|
||||
|
||||
return json.dumps(log_data, default=str)
|
||||
|
||||
|
||||
class StructuredLogger:
|
||||
"""
|
||||
Logger estructurado con soporte para contexto adicional.
|
||||
|
||||
Ejemplo:
|
||||
logger = get_logger("architect-app")
|
||||
logger.info("Request recibido", agent="architect", action="chat")
|
||||
logger.error("Error de conexión", error=str(e), retry=3)
|
||||
"""
|
||||
|
||||
def __init__(self, logger: logging.Logger):
|
||||
self._logger = logger
|
||||
|
||||
def _log(self, level: int, message: str, **kwargs):
|
||||
"""Log con campos extra."""
|
||||
record = self._logger.makeRecord(
|
||||
self._logger.name,
|
||||
level,
|
||||
"(unknown)",
|
||||
0,
|
||||
message,
|
||||
(),
|
||||
None,
|
||||
)
|
||||
if kwargs:
|
||||
record.extra_fields = kwargs
|
||||
self._logger.handle(record)
|
||||
|
||||
def debug(self, message: str, **kwargs):
|
||||
self._log(logging.DEBUG, message, **kwargs)
|
||||
|
||||
def info(self, message: str, **kwargs):
|
||||
self._log(logging.INFO, message, **kwargs)
|
||||
|
||||
def warning(self, message: str, **kwargs):
|
||||
self._log(logging.WARNING, message, **kwargs)
|
||||
|
||||
def error(self, message: str, exc_info: bool = False, **kwargs):
|
||||
if exc_info:
|
||||
self._logger.error(message, exc_info=True, extra={"extra_fields": kwargs} if kwargs else {})
|
||||
else:
|
||||
self._log(logging.ERROR, message, **kwargs)
|
||||
|
||||
def critical(self, message: str, **kwargs):
|
||||
self._log(logging.CRITICAL, message, **kwargs)
|
||||
|
||||
|
||||
def setup_logging(
|
||||
service: str = "orchestrator",
|
||||
level: str = "INFO",
|
||||
log_file: Optional[Path] = None,
|
||||
json_format: bool = True
|
||||
) -> StructuredLogger:
|
||||
"""
|
||||
Configura el sistema de logging.
|
||||
|
||||
Args:
|
||||
service: Nombre del servicio (aparece en los logs)
|
||||
level: Nivel de logging (DEBUG, INFO, WARNING, ERROR)
|
||||
log_file: Archivo opcional para escribir logs
|
||||
json_format: Si True, usa formato JSON; si False, formato legible
|
||||
|
||||
Returns:
|
||||
StructuredLogger configurado
|
||||
"""
|
||||
logger = logging.getLogger(service)
|
||||
logger.setLevel(getattr(logging, level.upper(), logging.INFO))
|
||||
logger.handlers.clear()
|
||||
|
||||
if json_format:
|
||||
formatter = JSONFormatter(service=service)
|
||||
else:
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
|
||||
# Handler para consola (stderr)
|
||||
console_handler = logging.StreamHandler(sys.stderr)
|
||||
console_handler.setFormatter(formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# Handler para archivo (opcional)
|
||||
if log_file:
|
||||
log_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
file_handler = logging.FileHandler(log_file)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
return StructuredLogger(logger)
|
||||
|
||||
|
||||
# Cache de loggers
|
||||
_loggers: dict[str, StructuredLogger] = {}
|
||||
|
||||
|
||||
def get_logger(
|
||||
name: str = "orchestrator",
|
||||
level: str = "INFO",
|
||||
log_file: Optional[Path] = None
|
||||
) -> StructuredLogger:
|
||||
"""
|
||||
Obtiene un logger estructurado (cached).
|
||||
|
||||
Args:
|
||||
name: Nombre del logger/servicio
|
||||
level: Nivel de logging
|
||||
log_file: Archivo opcional para logs
|
||||
|
||||
Returns:
|
||||
StructuredLogger
|
||||
"""
|
||||
if name not in _loggers:
|
||||
_loggers[name] = setup_logging(
|
||||
service=name,
|
||||
level=level,
|
||||
log_file=log_file,
|
||||
json_format=True
|
||||
)
|
||||
return _loggers[name]
|
||||
|
||||
|
||||
class RateLimiter:
|
||||
|
||||
Reference in New Issue
Block a user