feat: funciones Python infra y tipos Python (core, datascience, infra)
Infra: cache_to_file, cache_to_sqlite, http_download_file, http_get_json, http_post_json, read_file_with_encoding, safe_extract_zip, scan_directory, setup_logger, normalize_zip_filenames. Tipos: 30+ tipos core (agent_action, context, task, message, parse_result...), 6 tipos datascience (entity_candidate, extraction_result...), 2 tipos infra. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""Configuracion de logger con rotacion de archivo y salida a consola."""
|
||||
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def setup_logger(
|
||||
name: str = "app",
|
||||
log_dir: str = "logs",
|
||||
level: int = logging.DEBUG,
|
||||
) -> logging.Logger:
|
||||
"""Configura un logger con dual output: archivo rotante y consola.
|
||||
|
||||
Crea el directorio de logs si no existe. El archivo usa nivel DEBUG con
|
||||
formato detallado y rotacion diaria (maxBytes=10MB, backupCount=5).
|
||||
La consola usa nivel INFO con formato simplificado. Es idempotente: si el
|
||||
logger ya tiene handlers no se duplican.
|
||||
|
||||
Args:
|
||||
name: Nombre del logger (identifica la instancia en el sistema de logging).
|
||||
log_dir: Directorio donde se guardan los archivos de log.
|
||||
level: Nivel minimo del logger principal (por defecto DEBUG).
|
||||
|
||||
Returns:
|
||||
Logger configurado con handler de archivo y handler de consola.
|
||||
"""
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(level)
|
||||
logger.propagate = False
|
||||
|
||||
# Idempotente: si ya tiene handlers no agregar mas
|
||||
if logger.handlers:
|
||||
return logger
|
||||
|
||||
fmt_detailed = logging.Formatter(
|
||||
"[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s"
|
||||
)
|
||||
fmt_simple = logging.Formatter(
|
||||
"[%(asctime)s] %(levelname)s: %(message)s"
|
||||
)
|
||||
|
||||
# File handler con rotacion por tamano
|
||||
log_filename = os.path.join(log_dir, f"{datetime.now():%Y-%m-%d}.log")
|
||||
file_handler = logging.handlers.RotatingFileHandler(
|
||||
log_filename,
|
||||
maxBytes=10 * 1024 * 1024, # 10 MB
|
||||
backupCount=5,
|
||||
encoding="utf-8",
|
||||
)
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
file_handler.setFormatter(fmt_detailed)
|
||||
|
||||
# Console handler
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[attr-defined]
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(logging.INFO)
|
||||
console_handler.setFormatter(fmt_simple)
|
||||
|
||||
logger.addHandler(file_handler)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
def get_logger(name: str = "app") -> logging.Logger:
|
||||
"""Devuelve un logger existente o lo crea con setup_logger.
|
||||
|
||||
Args:
|
||||
name: Nombre del logger.
|
||||
|
||||
Returns:
|
||||
Logger configurado.
|
||||
"""
|
||||
logger = logging.getLogger(name)
|
||||
return logger if logger.handlers else setup_logger(name)
|
||||
Reference in New Issue
Block a user