""" 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"))