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