Spaces:
Runtime error
Runtime error
| # src/utils/logger.py | |
| import inspect | |
| import logging | |
| import sys | |
| # A more informative default format, including the logger's name and a custom tag. | |
| _DEFAULT_FORMAT = "%(asctime)s — %(levelname)s \t[%(name)s%(tag)s] \t%(message)s" | |
| _DEFAULT_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" | |
| class TaggedFormatter(logging.Formatter): | |
| """ | |
| A custom formatter that adds a default value for 'tag' if it is not present | |
| in the LogRecord. This prevents errors if a log is generated without the | |
| TaggedAdapter, making the logging system more robust. | |
| """ | |
| def __init__( | |
| self, | |
| fmt=_DEFAULT_FORMAT, | |
| datefmt=_DEFAULT_DATE_FORMAT, | |
| **kwargs | |
| ): | |
| super().__init__(fmt, datefmt, **kwargs) | |
| def format(self, record: logging.LogRecord) -> str: | |
| """ | |
| Adds a default 'tag' to the record before formatting. | |
| """ | |
| if not hasattr(record, 'tag'): | |
| record.tag = "" | |
| return super().format(record) | |
| def setup_logging( | |
| level: int = logging.INFO, | |
| stream=sys.stdout | |
| ) -> None: | |
| """ | |
| Configures the root logger for the application. | |
| This function should be called once at application startup. It establishes a | |
| StreamHandler with the custom TaggedFormatter, ensuring all subsequent logs | |
| are handled and formatted correctly. | |
| Args: | |
| level: The minimum logging level to output (e.g., logging.INFO). | |
| stream: The stream where logs will be written (e.g., sys.stdout). | |
| """ | |
| root_logger = logging.getLogger() | |
| # Avoid adding duplicate handlers if this function is called multiple times. | |
| if root_logger.handlers: | |
| return | |
| handler = logging.StreamHandler(stream=stream) | |
| formatter = TaggedFormatter() | |
| handler.setFormatter(formatter) | |
| root_logger.addHandler(handler) | |
| root_logger.setLevel(level) | |
| root_logger.info("Logger set up") | |
| def logger( | |
| tag: str | None = None, | |
| *, | |
| name: str | None = None | |
| ) -> logging.LoggerAdapter: | |
| """ | |
| Returns a logger that injects a tag into the LogRecord's context. | |
| The custom TaggedFormatter must be active for the tag to appear in the output. | |
| If 'name' is not provided, it defaults to the name of the calling module. | |
| Example: | |
| ``` | |
| # In a file named 'my_app/database.py' | |
| setup_logging() | |
| logger = logger(tag="DATABASE") # Name will be 'my_app.database' | |
| logger.info("Connection established.") | |
| # Expected Output: | |
| # 2025-09-07 22:15:30 — INFO [my_app.database:DATABASE] Connection established. | |
| ``` | |
| Args: | |
| tag: The tag to inject into the 'extra' context of log records. | |
| name: The name for the logger. Defaults to the calling module's name. | |
| Returns: | |
| A LoggerAdapter that enriches log records with the specified tag. | |
| """ | |
| logger_name = name | |
| if logger_name is None: | |
| # inspect.stack()[1] gets the frame of the caller. | |
| # inspect.getmodule() gets the module from that frame. | |
| # .__name__ gets the module's name. | |
| frame = inspect.stack()[1] | |
| module = inspect.getmodule(frame[0]) | |
| if module: | |
| logger_name = module.__name__ | |
| else: | |
| # Fallback if the module can't be determined | |
| logger_name = "unknown_module" | |
| base_logger = logging.getLogger(logger_name) | |
| # A LoggerAdapter is the standard way to pass contextual information to loggers. | |
| # It wraps the logger and adds the dictionary provided to the 'extra' attribute of each LogRecord. | |
| return logging.LoggerAdapter( | |
| base_logger, | |
| {"tag": f":{tag}" if tag is not None else ""} | |
| ) | |