Skip to content

logger

Lightweight logging utility with colored console output and file handlers.

BaseLogger

Lightweight application logger with file and console handlers.

BaseLogger is a small wrapper around Python's standard logging that configures a file handler and a console handler (with colors). It is implemented as a simple singleton so multiple instantiations return the same configured logger instance.

Source code in src/mangetamain/utils/logger.py
 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
class BaseLogger:
    """Lightweight application logger with file and console handlers.

    BaseLogger is a small wrapper around Python's standard logging that
    configures a file handler and a console handler (with colors).
    It is implemented as a simple singleton so multiple instantiations
    return the same configured logger instance.
    """

    _instance = None  # Singleton

    def __new__(cls, input_name: Path | str | None = None) -> "BaseLogger":
        """Init or return the singleton logger instance."""
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._init_logger(input_name)
        return cls._instance

    def _init_logger(self, input_name: Path | str | None = None) -> None:
        """Initialize the internal logger, handlers and formatters.

        Args:
          input_name: Optional name used to create or locate the log
            file (defaults to 'app').
        """
        self._has_error = False
        self.base_folder = Path("logs")

        self.logger = logging.getLogger("SystemLogger")
        self.logger.setLevel(logging.DEBUG)
        self.logger.propagate = False

        file_formatter = logging.Formatter(
            fmt="[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s",
            datefmt="%H:%M:%S",
        )

        console_formatter = ColoredFormatter(
            fmt="[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s",
            datefmt="%H:%M:%S",
        )

        # File handler
        self.log_path, file_handler = self._setup_handler(input_name)
        file_handler.setLevel(logging.DEBUG)
        file_handler.setFormatter(file_formatter)

        # Console handler
        console_handler = logging.StreamHandler()
        console_handler.setLevel(logging.INFO)
        console_handler.setFormatter(console_formatter)

        if not self.logger.handlers:
            self.logger.addHandler(file_handler)
            self.logger.addHandler(console_handler)

    def _setup_handler(
        self,
        input_name: Path | str | None = None,
    ) -> tuple[Path, logging.FileHandler]:
        """Create and return a file handler for application logs.

        Args:
          input_name: Optional identifier used to name the log file.

        Returns:
          A tuple containing the Path to the log file and the
          configured file handler instance.
        """
        os.makedirs(self.base_folder, exist_ok=True)
        log_path = self.base_folder / f"{(input_name or 'app')}.log"
        file_handler = logging.FileHandler(log_path, encoding="utf-8")
        return log_path, file_handler

    def info(self, msg: str) -> None:
        """Log an informational message.

        Args:
          msg: Message to log at INFO level.
        """
        self.logger.info(msg, stacklevel=2)

    def debug(self, msg: str) -> None:
        """Log a debug-level message.

        Args:
          msg: Message to log at DEBUG level.
        """
        self.logger.debug(msg, stacklevel=2)

    def warning(self, msg: str) -> None:
        """Log a warning-level message.

        Args:
          msg: Message to log at WARNING level.
        """
        self.logger.warning(msg, stacklevel=2)

    def error(self, msg: str) -> None:
        """Log an error message and mark that an error occurred.

        Args:
          msg: Message to log at ERROR level.
        """
        self._has_error = True
        self.logger.error(msg, stacklevel=2)

    def critical(self, msg: str) -> None:
        """Log a critical-level message.

        Args:
          msg: Message to log at CRITICAL level.
        """
        self.logger.critical(msg, stacklevel=2)

    def get_log_path(self) -> Path:
        """Return the current log file path.

        Returns:
          Path: Path to the active log file.
        """
        return self.log_path

    def has_errors(self) -> bool:
        """Return whether an error has been logged during runtime.

        Returns:
          bool: True if any error was logged, otherwise False.
        """
        return self._has_error

__new__

__new__(input_name=None)

Init or return the singleton logger instance.

Source code in src/mangetamain/utils/logger.py
57
58
59
60
61
62
def __new__(cls, input_name: Path | str | None = None) -> "BaseLogger":
    """Init or return the singleton logger instance."""
    if cls._instance is None:
        cls._instance = super().__new__(cls)
        cls._instance._init_logger(input_name)
    return cls._instance

critical

critical(msg)

Log a critical-level message.

Parameters:

Name Type Description Default
msg str

Message to log at CRITICAL level.

required
Source code in src/mangetamain/utils/logger.py
153
154
155
156
157
158
159
def critical(self, msg: str) -> None:
    """Log a critical-level message.

    Args:
      msg: Message to log at CRITICAL level.
    """
    self.logger.critical(msg, stacklevel=2)

debug

debug(msg)

Log a debug-level message.

Parameters:

Name Type Description Default
msg str

Message to log at DEBUG level.

required
Source code in src/mangetamain/utils/logger.py
128
129
130
131
132
133
134
def debug(self, msg: str) -> None:
    """Log a debug-level message.

    Args:
      msg: Message to log at DEBUG level.
    """
    self.logger.debug(msg, stacklevel=2)

error

error(msg)

Log an error message and mark that an error occurred.

Parameters:

Name Type Description Default
msg str

Message to log at ERROR level.

required
Source code in src/mangetamain/utils/logger.py
144
145
146
147
148
149
150
151
def error(self, msg: str) -> None:
    """Log an error message and mark that an error occurred.

    Args:
      msg: Message to log at ERROR level.
    """
    self._has_error = True
    self.logger.error(msg, stacklevel=2)

get_log_path

get_log_path()

Return the current log file path.

Returns:

Name Type Description
Path Path

Path to the active log file.

Source code in src/mangetamain/utils/logger.py
161
162
163
164
165
166
167
def get_log_path(self) -> Path:
    """Return the current log file path.

    Returns:
      Path: Path to the active log file.
    """
    return self.log_path

has_errors

has_errors()

Return whether an error has been logged during runtime.

Returns:

Name Type Description
bool bool

True if any error was logged, otherwise False.

Source code in src/mangetamain/utils/logger.py
169
170
171
172
173
174
175
def has_errors(self) -> bool:
    """Return whether an error has been logged during runtime.

    Returns:
      bool: True if any error was logged, otherwise False.
    """
    return self._has_error

info

info(msg)

Log an informational message.

Parameters:

Name Type Description Default
msg str

Message to log at INFO level.

required
Source code in src/mangetamain/utils/logger.py
120
121
122
123
124
125
126
def info(self, msg: str) -> None:
    """Log an informational message.

    Args:
      msg: Message to log at INFO level.
    """
    self.logger.info(msg, stacklevel=2)

warning

warning(msg)

Log a warning-level message.

Parameters:

Name Type Description Default
msg str

Message to log at WARNING level.

required
Source code in src/mangetamain/utils/logger.py
136
137
138
139
140
141
142
def warning(self, msg: str) -> None:
    """Log a warning-level message.

    Args:
      msg: Message to log at WARNING level.
    """
    self.logger.warning(msg, stacklevel=2)

ColoredFormatter

Bases: Formatter

A logging formatter that adds ANSI color codes to console output.

This formatter applies simple ANSI color codes to the formatted message according to the record level name (DEBUG, INFO, WARNING, ERROR, CRITICAL). It improves readability when logs are viewed in a terminal that supports ANSI escapes.

Source code in src/mangetamain/utils/logger.py
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
class ColoredFormatter(logging.Formatter):
    """A logging formatter that adds ANSI color codes to console output.

    This formatter applies simple ANSI color codes to the formatted
    message according to the record level name (DEBUG, INFO, WARNING,
    ERROR, CRITICAL). It improves readability when logs are viewed in
    a terminal that supports ANSI escapes.
    """

    COLORS: ClassVar[dict[str, str]] = {
        "DEBUG": "\033[0;36m",  # Cyan
        "INFO": "\033[0;32m",  # Green
        "WARNING": "\033[0;33m",  # Yellow
        "ERROR": "\033[0;31m",  # Red
        "CRITICAL": "\033[0;37m\033[41m",  # White on Red BG
        "RESET": "\033[0m",  # Reset
    }

    def format(self, record: logging.LogRecord) -> str:
        """Format the record and wrap it in color escape sequences.

        Args:
          record: The logging.Record to format.

        Returns:
          The formatted log string with ANSI color codes applied.
        """
        msg = (
            self.COLORS.get(record.levelname, self.COLORS["RESET"])
            + super().format(record)
            + self.COLORS["RESET"]
        )
        return msg

format

format(record)

Format the record and wrap it in color escape sequences.

Parameters:

Name Type Description Default
record LogRecord

The logging.Record to format.

required

Returns:

Type Description
str

The formatted log string with ANSI color codes applied.

Source code in src/mangetamain/utils/logger.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def format(self, record: logging.LogRecord) -> str:
    """Format the record and wrap it in color escape sequences.

    Args:
      record: The logging.Record to format.

    Returns:
      The formatted log string with ANSI color codes applied.
    """
    msg = (
        self.COLORS.get(record.levelname, self.COLORS["RESET"])
        + super().format(record)
        + self.COLORS["RESET"]
    )
    return msg

RotLogger

Bases: BaseLogger

Logger using a rotating file handler to bound log file size.

RotLogger uses :class:logging.handlers.RotatingFileHandler to keep logs from growing indefinitely by rotating them when they exceed a configured size.

Source code in src/mangetamain/utils/logger.py
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
class RotLogger(BaseLogger):
    """Logger using a rotating file handler to bound log file size.

    RotLogger uses :class:`logging.handlers.RotatingFileHandler` to
    keep logs from growing indefinitely by rotating them when they
    exceed a configured size.
    """

    def _setup_handler(
        self,
        input_name: Path | str | None = None,
    ) -> tuple[Path, logging.FileHandler]:
        """Create a rotating file handler for application logs.

        Args:
          input_name: Optional identifier for the log folder.

        Returns:
          A tuple (log_path, file_handler) of the configured rotating handler.
        """
        log_dir = self.base_folder / (input_name or "app")
        os.makedirs(log_dir, exist_ok=True)
        log_path = log_dir / f"{(input_name or 'app')}.log"

        file_handler = RotatingFileHandler(
            log_path,
            maxBytes=5 * 1024 * 1024,
            backupCount=5,
            encoding="utf-8",
        )

        return log_path, file_handler

TimeLogger

Bases: BaseLogger

Logger that writes timestamped log files into a per-run folder.

This variant of :class:BaseLogger creates a new timestamped file for each run inside logs/<input_name>/ which is useful when keeping separate logs per execution is desired.

Source code in src/mangetamain/utils/logger.py
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
class TimeLogger(BaseLogger):
    """Logger that writes timestamped log files into a per-run folder.

    This variant of :class:`BaseLogger` creates a new timestamped file
    for each run inside ``logs/<input_name>/`` which is useful when
    keeping separate logs per execution is desired.
    """

    def _setup_handler(
        self,
        input_name: Path | str | None = None,
    ) -> tuple[Path, logging.FileHandler]:
        """Create a timestamped file handler for a single run.

        Args:
          input_name: Optional identifier used to name the folder.

        Returns:
          A tuple (log_path, file_handler) where log_path is the Path to the
          created log file and file_handler is the configured handler.
        """
        timestamp = datetime.now().strftime("%d-%m-%Y_%H-%M-%S")
        log_dir = self.base_folder / (input_name or "app")
        os.makedirs(log_dir, exist_ok=True)
        log_path = log_dir / f"{timestamp}.log"

        file_handler = logging.FileHandler(log_path, encoding="utf-8")
        return log_path, file_handler

get_logger

get_logger()

Return the module-level configured logger instance.

Returns:

Name Type Description
RotLogger RotLogger

The shared logger instance used by the application.

Source code in src/mangetamain/utils/logger.py
245
246
247
248
249
250
251
def get_logger() -> RotLogger:
    """Return the module-level configured logger instance.

    Returns:
      RotLogger: The shared logger instance used by the application.
    """
    return logger