Spaces:
Running
Running
Guilherme Favaron
Major update: Add hybrid search, reranking, multiple LLMs, and UI improvements
1b447de
| """ | |
| Configuração de logging estruturado | |
| """ | |
| import logging | |
| import sys | |
| import json | |
| from datetime import datetime | |
| from typing import Dict, Any, Optional | |
| from pathlib import Path | |
| class StructuredFormatter(logging.Formatter): | |
| """Formatter para logs estruturados em JSON""" | |
| def format(self, record: logging.LogRecord) -> str: | |
| """ | |
| Formata log record como JSON estruturado | |
| Args: | |
| record: Registro de log | |
| Returns: | |
| String JSON formatada | |
| """ | |
| log_data = { | |
| "timestamp": datetime.utcnow().isoformat() + "Z", | |
| "level": record.levelname, | |
| "logger": record.name, | |
| "message": record.getMessage(), | |
| "module": record.module, | |
| "function": record.funcName, | |
| "line": record.lineno | |
| } | |
| # Adiciona informações extras se existirem | |
| if hasattr(record, "extra_data"): | |
| log_data["extra"] = record.extra_data | |
| # Adiciona informação de exceção se houver | |
| if record.exc_info: | |
| log_data["exception"] = self.formatException(record.exc_info) | |
| return json.dumps(log_data, ensure_ascii=False) | |
| class HumanReadableFormatter(logging.Formatter): | |
| """Formatter para logs legíveis por humanos""" | |
| def __init__(self): | |
| super().__init__( | |
| fmt="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s", | |
| datefmt="%Y-%m-%d %H:%M:%S" | |
| ) | |
| def setup_logger( | |
| name: str, | |
| level: str = "INFO", | |
| log_file: Optional[str] = None, | |
| structured: bool = False | |
| ) -> logging.Logger: | |
| """ | |
| Configura logger com formatação customizada | |
| Args: | |
| name: Nome do logger | |
| level: Nível de log (DEBUG, INFO, WARNING, ERROR, CRITICAL) | |
| log_file: Caminho do arquivo de log (opcional) | |
| structured: Se True, usa formato JSON estruturado | |
| Returns: | |
| Logger configurado | |
| """ | |
| logger = logging.getLogger(name) | |
| logger.setLevel(getattr(logging, level.upper())) | |
| # Remove handlers existentes para evitar duplicação | |
| logger.handlers.clear() | |
| # Handler para console | |
| console_handler = logging.StreamHandler(sys.stdout) | |
| console_handler.setLevel(getattr(logging, level.upper())) | |
| if structured: | |
| console_handler.setFormatter(StructuredFormatter()) | |
| else: | |
| console_handler.setFormatter(HumanReadableFormatter()) | |
| logger.addHandler(console_handler) | |
| # Handler para arquivo se especificado | |
| if log_file: | |
| log_path = Path(log_file) | |
| log_path.parent.mkdir(parents=True, exist_ok=True) | |
| file_handler = logging.FileHandler(log_file, encoding="utf-8") | |
| file_handler.setLevel(getattr(logging, level.upper())) | |
| if structured: | |
| file_handler.setFormatter(StructuredFormatter()) | |
| else: | |
| file_handler.setFormatter(HumanReadableFormatter()) | |
| logger.addHandler(file_handler) | |
| return logger | |
| def log_with_context( | |
| logger: logging.Logger, | |
| level: str, | |
| message: str, | |
| **kwargs | |
| ) -> None: | |
| """ | |
| Loga mensagem com contexto adicional | |
| Args: | |
| logger: Logger a usar | |
| level: Nível do log | |
| message: Mensagem principal | |
| **kwargs: Contexto adicional (session_id, user_id, etc) | |
| """ | |
| extra_record = type('obj', (object,), {'extra_data': kwargs})() | |
| log_func = getattr(logger, level.lower()) | |
| log_func(message, extra={"extra_data": kwargs}) | |
| class PerformanceLogger: | |
| """Logger especializado para métricas de performance""" | |
| def __init__(self, logger: logging.Logger): | |
| self.logger = logger | |
| self.metrics: Dict[str, list] = {} | |
| def log_metric( | |
| self, | |
| operation: str, | |
| duration_ms: float, | |
| metadata: Optional[Dict[str, Any]] = None | |
| ) -> None: | |
| """ | |
| Registra métrica de performance | |
| Args: | |
| operation: Nome da operação | |
| duration_ms: Duração em milissegundos | |
| metadata: Informações adicionais | |
| """ | |
| metric_data = { | |
| "operation": operation, | |
| "duration_ms": duration_ms, | |
| "timestamp": datetime.utcnow().isoformat() + "Z" | |
| } | |
| if metadata: | |
| metric_data.update(metadata) | |
| self.logger.info( | |
| f"Performance: {operation} completed in {duration_ms:.2f}ms", | |
| extra={"extra_data": metric_data} | |
| ) | |
| # Armazena em memória para análise | |
| if operation not in self.metrics: | |
| self.metrics[operation] = [] | |
| self.metrics[operation].append(duration_ms) | |
| def get_stats(self, operation: Optional[str] = None) -> Dict[str, Any]: | |
| """ | |
| Retorna estatísticas de performance | |
| Args: | |
| operation: Operação específica (None = todas) | |
| Returns: | |
| Dicionário com estatísticas | |
| """ | |
| if operation: | |
| if operation not in self.metrics: | |
| return {} | |
| durations = self.metrics[operation] | |
| return { | |
| "operation": operation, | |
| "count": len(durations), | |
| "avg_ms": sum(durations) / len(durations), | |
| "min_ms": min(durations), | |
| "max_ms": max(durations), | |
| "total_ms": sum(durations) | |
| } | |
| # Retorna stats de todas operações | |
| stats = {} | |
| for op, durations in self.metrics.items(): | |
| stats[op] = { | |
| "count": len(durations), | |
| "avg_ms": sum(durations) / len(durations), | |
| "min_ms": min(durations), | |
| "max_ms": max(durations), | |
| "total_ms": sum(durations) | |
| } | |
| return stats | |
| def clear_metrics(self) -> None: | |
| """Limpa todas as métricas armazenadas""" | |
| self.metrics.clear() | |
| # Instâncias globais de logger | |
| app_logger = setup_logger("rag_template", level="INFO") | |
| db_logger = setup_logger("rag_template.database", level="INFO") | |
| llm_logger = setup_logger("rag_template.llm", level="INFO") | |
| embedding_logger = setup_logger("rag_template.embeddings", level="INFO") | |
| # Logger de performance | |
| perf_logger = PerformanceLogger(setup_logger("rag_template.performance", level="INFO")) | |