File size: 6,194 Bytes
780413d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
"""
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