# This first piece can go into some global utils.py file. # python foo.py # LOGGER_FOO_PY=DEBUG python foo.py # python -m pytest foo.py import logging import os import inspect import pytest from unittest.mock import patch def setup_local_logger(handler=None) -> logging.Logger: """ Sets up a logger for the calling module. If an environment variable named `LOGGER_` is set, it determines the logging level. Otherwise, the default logging level is INFO. The logger will use a StreamHandler by default unless a custom handler is provided. Args: handler (logging.Handler, optional): A custom logging handler. If not provided, a StreamHandler is used. Returns: logging.Logger: Configured logger instance. """ caller_frame = inspect.stack()[1] caller_filename = os.path.basename(caller_frame.filename) logger_key = ("LOGGER_" + os.path.basename(caller_filename)).upper().replace(".", "_") logger = logging.getLogger(logger_key) level = os.environ.get(logger_key, "INFO").upper() numlevel = getattr(logging, level, logging.INFO) if not logger.hasHandlers(): if handler is None: handler = logging.StreamHandler() formatter = logging.Formatter( '%(asctime)-15s %(levelname)-6s %(filename)s:%(lineno)d %(message)s' ) handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(numlevel) for handler in logger.handlers: handler.setLevel(numlevel) return logger @pytest.mark.parametrize("log_level, log_method, expected_level, message", [ ('INFO', 'info', logging.INFO, "This is an info message"), ('DEBUG', 'debug', logging.DEBUG, "This is a debug message"), ('ERROR', 'error', logging.ERROR, "This is an error message"), ]) @patch.object(logging.Logger, '_log') def test_logger_levels(mock_log, log_level, log_method, expected_level, message): """ Tests that the logger logs messages at the correct level based on environment variable settings. Args: mock_log (unittest.mock.MagicMock): Mock object for the logger's _log method. log_level (str): The log level to set in the environment variable. log_method (str): The logger method to call (e.g., 'info', 'debug', 'error'). expected_level (int): The expected numeric log level. message (str): The message to log. """ logger_key = ("LOGGER_" + os.path.basename(__file__)).upper().replace(".", "_") os.environ[logger_key] = log_level try: logger = setup_local_logger() getattr(logger, log_method)(message) mock_log.assert_called_with(expected_level, message, ()) finally: del os.environ[logger_key] ################################################################# # This would go into one of many source files using this stuff. mylogger = setup_local_logger() def example_usage(): """ Log a few messages at different logging levels. """ mylogger.error("This is an error message talking about catastrophic stuff") mylogger.info("This is an info message talking about important stuff") mylogger.debug("This is a debug message talking about nit-picky stuff") if __name__ == "__main__": example_usage()