""" Sistema de logging para o Docling Document Processor. Este módulo configura e gerencia o sistema de logging da aplicação, incluindo rotação de arquivos e formatação consistente. """ import logging import sys from logging.handlers import RotatingFileHandler from pathlib import Path from typing import Optional import config # Flag para evitar configuração duplicada _logging_configured = False # Cache de loggers _loggers: dict[str, logging.Logger] = {} def setup_logger( name: str = "docling_space", level: int = logging.INFO, log_to_file: bool = True, log_to_console: bool = True ) -> logging.Logger: """ Configura e retorna um logger. Args: name: Nome do logger. level: Nível de logging (default: INFO). log_to_file: Se deve logar em arquivo. log_to_console: Se deve logar no console. Returns: Logger configurado. """ global _logging_configured # Se já existe no cache, retorna if name in _loggers: return _loggers[name] # Cria o logger logger = logging.getLogger(name) logger.setLevel(level) # Evita handlers duplicados if logger.handlers: return logger # Formatter formatter = logging.Formatter( config.LOG_FORMAT, datefmt=config.LOG_DATE_FORMAT ) # Handler de console if log_to_console: console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(level) console_handler.setFormatter(formatter) logger.addHandler(console_handler) # Handler de arquivo com rotação if log_to_file: try: # Garante que o diretório existe config.LOGS_DIR.mkdir(parents=True, exist_ok=True) log_file = config.LOGS_DIR / config.LOG_FILE file_handler = RotatingFileHandler( log_file, maxBytes=config.LOG_MAX_BYTES, backupCount=config.LOG_BACKUP_COUNT, encoding="utf-8" ) file_handler.setLevel(level) file_handler.setFormatter(formatter) logger.addHandler(file_handler) except Exception as e: # Se não conseguir criar o arquivo de log, continua só com console if log_to_console: logger.warning(f"Não foi possível criar arquivo de log: {e}") # Não propaga para o root logger logger.propagate = False # Adiciona ao cache _loggers[name] = logger _logging_configured = True return logger def get_logger(name: Optional[str] = None) -> logging.Logger: """ Obtém um logger pelo nome. Se o logger não existir, cria um novo com as configurações padrão. Args: name: Nome do logger. Se None, usa "docling_space". Returns: Logger configurado. """ if name is None: name = "docling_space" # Se for um nome de módulo completo, usa apenas a última parte if "." in name: short_name = name.split(".")[-1] else: short_name = name logger_name = f"docling_space.{short_name}" if logger_name not in _loggers: return setup_logger(logger_name) return _loggers[logger_name] def log_exception( logger: logging.Logger, message: str, exc: Exception, include_traceback: bool = True ) -> None: """ Loga uma exceção com detalhes. Args: logger: Logger a usar. message: Mensagem descritiva. exc: Exceção a logar. include_traceback: Se deve incluir traceback completo. """ if include_traceback: logger.exception(f"{message}: {exc}") else: logger.error(f"{message}: {type(exc).__name__}: {exc}") def log_processing_start( logger: logging.Logger, filename: str, file_size: int ) -> None: """ Loga o início do processamento de um arquivo. Args: logger: Logger a usar. filename: Nome do arquivo. file_size: Tamanho em bytes. """ size_mb = file_size / (1024 * 1024) logger.info(f"Iniciando processamento: {filename} ({size_mb:.2f} MB)") def log_processing_complete( logger: logging.Logger, filename: str, duration_seconds: float, output_format: str ) -> None: """ Loga a conclusão do processamento de um arquivo. Args: logger: Logger a usar. filename: Nome do arquivo. duration_seconds: Tempo de processamento em segundos. output_format: Formato de saída usado. """ logger.info( f"Processamento concluído: {filename} " f"({duration_seconds:.2f}s, formato: {output_format})" ) def log_validation_error( logger: logging.Logger, filename: str, error_code: str, message: str ) -> None: """ Loga um erro de validação. Args: logger: Logger a usar. filename: Nome do arquivo. error_code: Código do erro. message: Mensagem de erro. """ logger.warning(f"Validação falhou [{error_code}] {filename}: {message}") class ProcessingLogger: """ Context manager para logging de processamento. Automaticamente loga início e fim do processamento com timing. """ def __init__( self, logger: logging.Logger, operation: str, filename: str ): self.logger = logger self.operation = operation self.filename = filename self.start_time: float = 0 def __enter__(self): import time self.start_time = time.time() self.logger.info(f"[INÍCIO] {self.operation}: {self.filename}") return self def __exit__(self, exc_type, exc_val, exc_tb): import time duration = time.time() - self.start_time if exc_type is None: self.logger.info( f"[FIM] {self.operation}: {self.filename} ({duration:.2f}s)" ) else: self.logger.error( f"[ERRO] {self.operation}: {self.filename} " f"({duration:.2f}s) - {exc_type.__name__}: {exc_val}" ) # Não suprime exceções return False