Spaces:
Runtime error
Runtime error
| """ | |
| Sistema de logging customizado para para.AI API v3.0 | |
| Configura loggers com rotação de arquivos e formatação padronizada | |
| """ | |
| import logging | |
| import sys | |
| from pathlib import Path | |
| from logging.handlers import RotatingFileHandler | |
| from datetime import datetime | |
| from typing import Optional | |
| # ============================================================================ | |
| # FORMATTERS | |
| # ============================================================================ | |
| class ColoredFormatter(logging.Formatter): | |
| """ | |
| Formatter com cores para output no console. | |
| """ | |
| # Códigos de cores ANSI | |
| COLORS = { | |
| 'DEBUG': '\033[36m', # Cyan | |
| 'INFO': '\033[32m', # Green | |
| 'WARNING': '\033[33m', # Yellow | |
| 'ERROR': '\033[31m', # Red | |
| 'CRITICAL': '\033[35m', # Magenta | |
| } | |
| RESET = '\033[0m' | |
| def format(self, record): | |
| """Formata log com cores.""" | |
| # Adicionar cor ao level name | |
| levelname = record.levelname | |
| if levelname in self.COLORS: | |
| record.levelname = f"{self.COLORS[levelname]}{levelname}{self.RESET}" | |
| # Formatar | |
| result = super().format(record) | |
| # Resetar levelname original (para não afetar outros handlers) | |
| record.levelname = levelname | |
| return result | |
| class JSONFormatter(logging.Formatter): | |
| """ | |
| Formatter que gera logs em formato JSON. | |
| Útil para parsing automatizado e ferramentas de log aggregation. | |
| """ | |
| def format(self, record): | |
| """Formata log como JSON.""" | |
| import json | |
| log_data = { | |
| "timestamp": datetime.fromtimestamp(record.created).isoformat(), | |
| "level": record.levelname, | |
| "logger": record.name, | |
| "message": record.getMessage(), | |
| "module": record.module, | |
| "function": record.funcName, | |
| "line": record.lineno, | |
| } | |
| # Adicionar exception info se houver | |
| if record.exc_info: | |
| log_data["exception"] = self.formatException(record.exc_info) | |
| # Adicionar campos extras | |
| if hasattr(record, "extra"): | |
| log_data.update(record.extra) | |
| return json.dumps(log_data) | |
| # ============================================================================ | |
| # CONFIGURAÇÃO DE LOGGER | |
| # ============================================================================ | |
| def setup_logger( | |
| name: str, | |
| level: Optional[str] = None, | |
| log_file: Optional[str] = None, | |
| enable_console: bool = True, | |
| enable_file: bool = True, | |
| use_json: bool = False, | |
| max_bytes: int = 10485760, # 10MB | |
| backup_count: int = 5 | |
| ) -> logging.Logger: | |
| """ | |
| Configura e retorna um logger customizado. | |
| Args: | |
| name: Nome do logger (geralmente __name__ do módulo) | |
| level: Nível de log (DEBUG, INFO, WARNING, ERROR, CRITICAL) | |
| log_file: Caminho do arquivo de log (se None, usa padrão) | |
| enable_console: Habilitar output no console | |
| enable_file: Habilitar output em arquivo | |
| use_json: Usar formato JSON nos logs | |
| max_bytes: Tamanho máximo do arquivo de log | |
| backup_count: Número de arquivos de backup a manter | |
| Returns: | |
| Logger configurado | |
| """ | |
| # Criar logger | |
| logger = logging.getLogger(name) | |
| # Evitar duplicação de handlers | |
| if logger.handlers: | |
| return logger | |
| # Determinar nível de log | |
| if level is None: | |
| try: | |
| from api.config import get_settings | |
| settings = get_settings() | |
| level = settings.LOG_LEVEL | |
| except: | |
| level = "DEBUG" | |
| logger.setLevel(getattr(logging, level.upper())) | |
| # ======================================================================== | |
| # CONSOLE HANDLER | |
| # ======================================================================== | |
| if enable_console: | |
| console_handler = logging.StreamHandler(sys.stdout) | |
| console_handler.setLevel(logging.DEBUG) | |
| # Formato para console | |
| if use_json: | |
| console_formatter = JSONFormatter() | |
| else: | |
| console_format = "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s" | |
| console_formatter = ColoredFormatter( | |
| console_format, | |
| datefmt="%Y-%m-%d %H:%M:%S" | |
| ) | |
| console_handler.setFormatter(console_formatter) | |
| logger.addHandler(console_handler) | |
| # ======================================================================== | |
| # FILE HANDLER | |
| # ======================================================================== | |
| if enable_file: | |
| # Determinar caminho do arquivo | |
| if log_file is None: | |
| try: | |
| from api.config import get_settings | |
| settings = get_settings() | |
| log_dir = Path(settings.LOG_FILE_PATH) | |
| except: | |
| log_dir = Path("./logs") | |
| log_dir.mkdir(parents=True, exist_ok=True) | |
| # Nome do arquivo com data | |
| today = datetime.now().strftime("%Y%m%d") | |
| log_file = log_dir / f"para_ai_{today}.log" | |
| else: | |
| log_file = Path(log_file) | |
| log_file.parent.mkdir(parents=True, exist_ok=True) | |
| # Rotating file handler (rotaciona quando atinge max_bytes) | |
| file_handler = RotatingFileHandler( | |
| log_file, | |
| maxBytes=max_bytes, | |
| backupCount=backup_count, | |
| encoding="utf-8" | |
| ) | |
| file_handler.setLevel(logging.DEBUG) | |
| # Formato para arquivo | |
| if use_json: | |
| file_formatter = JSONFormatter() | |
| else: | |
| file_format = "%(asctime)s | %(levelname)-8s | %(name)s | %(funcName)s:%(lineno)d | %(message)s" | |
| file_formatter = logging.Formatter( | |
| file_format, | |
| datefmt="%Y-%m-%d %H:%M:%S" | |
| ) | |
| file_handler.setFormatter(file_formatter) | |
| logger.addHandler(file_handler) | |
| # Prevenir propagação para logger pai (evita duplicação) | |
| logger.propagate = False | |
| return logger | |
| # ============================================================================ | |
| # LOGGER GLOBAL DA API | |
| # ============================================================================ | |
| # Logger principal da API (usado em api/main.py) | |
| api_logger = setup_logger("api", enable_console=True, enable_file=True) | |
| # ============================================================================ | |
| # FUNÇÕES AUXILIARES | |
| # ============================================================================ | |
| def log_request(method: str, path: str, status_code: int, duration_ms: float): | |
| """ | |
| Loga uma requisição HTTP de forma padronizada. | |
| Args: | |
| method: Método HTTP (GET, POST, etc) | |
| path: Caminho da URL | |
| status_code: Código de status HTTP | |
| duration_ms: Duração da requisição em milissegundos | |
| """ | |
| emoji = "✅" if status_code < 400 else "❌" | |
| api_logger.info( | |
| f"{emoji} {method} {path} - Status: {status_code} - Duration: {duration_ms:.2f}ms" | |
| ) | |
| def log_llm_call( | |
| provider: str, | |
| model: str, | |
| tokens: int, | |
| duration_ms: float, | |
| success: bool = True | |
| ): | |
| """ | |
| Loga uma chamada ao LLM de forma padronizada. | |
| Args: | |
| provider: Nome do provedor (groq, openai, etc) | |
| model: Nome do modelo usado | |
| tokens: Número de tokens consumidos | |
| duration_ms: Duração da chamada em ms | |
| success: Se a chamada foi bem sucedida | |
| """ | |
| emoji = "🤖" if success else "⚠️" | |
| status = "OK" if success else "ERRO" | |
| api_logger.info( | |
| f"{emoji} LLM Call [{provider}/{model}] - Tokens: {tokens} - " | |
| f"Duration: {duration_ms:.2f}ms - Status: {status}" | |
| ) | |
| def log_processing( | |
| processor_name: str, | |
| acordao_id: str, | |
| duration_ms: float, | |
| success: bool = True, | |
| error: Optional[str] = None | |
| ): | |
| """ | |
| Loga processamento de acórdão. | |
| Args: | |
| processor_name: Nome do processador | |
| acordao_id: ID do acórdão processado | |
| duration_ms: Duração em ms | |
| success: Se processamento foi bem sucedido | |
| error: Mensagem de erro se houver | |
| """ | |
| emoji = "✅" if success else "❌" | |
| status = "OK" if success else f"ERRO: {error}" | |
| api_logger.info( | |
| f"{emoji} Processor [{processor_name}] - Acórdão: {acordao_id} - " | |
| f"Duration: {duration_ms:.2f}ms - {status}" | |
| ) | |
| def log_database_operation( | |
| operation: str, | |
| table: str, | |
| records: int, | |
| duration_ms: float, | |
| success: bool = True | |
| ): | |
| """ | |
| Loga operação de banco de dados. | |
| Args: | |
| operation: Tipo de operação (INSERT, UPDATE, SELECT, etc) | |
| table: Nome da tabela | |
| records: Número de registros afetados | |
| duration_ms: Duração em ms | |
| success: Se operação foi bem sucedida | |
| """ | |
| emoji = "💾" if success else "⚠️" | |
| status = "OK" if success else "ERRO" | |
| api_logger.info( | |
| f"{emoji} DB [{operation}] {table} - Records: {records} - " | |
| f"Duration: {duration_ms:.2f}ms - {status}" | |
| ) | |
| # ============================================================================ | |
| # CONTEXT MANAGER PARA LOGGING DE PERFORMANCE | |
| # ============================================================================ | |
| class LogPerformance: | |
| """ | |
| Context manager para logar performance de operações. | |
| Exemplo: | |
| with LogPerformance("processar_acordao", logger): | |
| # código aqui | |
| pass | |
| """ | |
| def __init__(self, operation_name: str, logger: Optional[logging.Logger] = None): | |
| """ | |
| Args: | |
| operation_name: Nome da operação | |
| logger: Logger a usar (se None, usa api_logger) | |
| """ | |
| self.operation_name = operation_name | |
| self.logger = logger or api_logger | |
| self.start_time = None | |
| def __enter__(self): | |
| """Inicia timer.""" | |
| self.start_time = datetime.now() | |
| self.logger.debug(f"🏁 Iniciando: {self.operation_name}") | |
| return self | |
| def __exit__(self, exc_type, exc_val, exc_tb): | |
| """Finaliza timer e loga resultado.""" | |
| duration = (datetime.now() - self.start_time).total_seconds() * 1000 | |
| if exc_type is None: | |
| self.logger.info(f"✅ Concluído: {self.operation_name} - {duration:.2f}ms") | |
| else: | |
| self.logger.error( | |
| f"❌ Erro em: {self.operation_name} - {duration:.2f}ms - {exc_val}" | |
| ) | |
| return False # Não suprime exceção | |
| # ============================================================================ | |
| # EXEMPLOS DE USO | |
| # ============================================================================ | |
| if __name__ == "__main__": | |
| # Criar logger | |
| logger = setup_logger("test_logger") | |
| # Testar diferentes níveis | |
| logger.debug("Mensagem de DEBUG") | |
| logger.info("Mensagem de INFO") | |
| logger.warning("Mensagem de WARNING") | |
| logger.error("Mensagem de ERROR") | |
| logger.critical("Mensagem de CRITICAL") | |
| # Testar funções auxiliares | |
| log_request("GET", "/api/v1/health", 200, 15.5) | |
| log_llm_call("groq", "llama-3.1-70b", 1500, 850.3) | |
| log_processing("ProcessorMetadados", "acordao_001", 1200.5, True) | |
| log_database_operation("INSERT", "acordaos", 10, 45.2) | |
| # Testar context manager | |
| with LogPerformance("operacao_teste", logger): | |
| import time | |
| time.sleep(0.1) # Simular trabalho | |
| print("\n✅ Logs escritos em ./logs/") | |