Source code for mesa.mesa_logging
"""This provides logging functionality for MESA.
It is modeled on the default `logging approach that comes with Python <https://docs.python.org/library/logging.html>`_.
"""
import inspect
import logging
from functools import wraps
from logging import DEBUG, INFO
__all__ = [
"DEBUG",
"DEFAULT_LEVEL",
"INFO",
"LOGGER_NAME",
"function_logger",
"get_module_logger",
"get_rootlogger",
"log_to_stderr",
"method_logger",
]
LOGGER_NAME = "MESA"
DEFAULT_LEVEL = DEBUG
def create_module_logger(name: str | None = None):
"""Helper function for creating a module logger.
Args:
name (str): The name to be given to the logger. If the name is None, the name defaults to the name of the module.
"""
if name is None:
frm = inspect.stack()[1]
mod = inspect.getmodule(frm[0])
name = mod.__name__
logger = logging.getLogger(f"{LOGGER_NAME}.{name}")
_module_loggers[name] = logger
return logger
[docs]
def get_module_logger(name: str):
"""Helper function for getting the module logger.
Args:
name (str): The name of the module in which the method being decorated is located
"""
try:
logger = _module_loggers[name]
except KeyError:
logger = create_module_logger(name)
return logger
_rootlogger = None
_module_loggers = {}
_logger = get_module_logger(__name__)
class MESAColorFormatter(logging.Formatter):
"""Custom formatter for color based formatting."""
grey = "\x1b[38;20m"
green = "\x1b[32m"
yellow = "\x1b[33;20m"
red = "\x1b[31;20m"
bold_red = "\x1b[31;1m"
reset = "\x1b[0m"
format = (
"[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s [%(filename)s:%(lineno)d]"
)
FORMATS = {
logging.DEBUG: grey + format + reset,
logging.INFO: green + format + reset,
logging.WARNING: yellow + format + reset,
logging.ERROR: red + format + reset,
logging.CRITICAL: bold_red + format + reset,
}
def format(self, record):
"""Format record."""
log_fmt = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt)
return formatter.format(record)
[docs]
def method_logger(name: str):
"""Decorator for adding logging to a method.
Args:
name (str): The name of the module in which the method being decorated is located
"""
logger = get_module_logger(name)
classname = inspect.getouterframes(inspect.currentframe())[1][3]
def real_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# hack, because log is applied to methods, we can get
# object instance as first arguments in args
logger.debug(
f"calling {classname}.{func.__name__} with {args[1::]} and {kwargs}"
)
res = func(*args, **kwargs)
return res
return wrapper
return real_decorator
[docs]
def function_logger(name):
"""Decorator for adding logging to a Function.
Args:
name (str): The name of the module in which the function being decorated is located
"""
logger = get_module_logger(name)
def real_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
logger.debug(f"calling {func.__name__} with {args} and {kwargs}")
res = func(*args, **kwargs)
return res
return wrapper
return real_decorator
[docs]
def get_rootlogger():
"""Returns root logger used by MESA.
Returns:
the root logger of MESA
"""
global _rootlogger # noqa: PLW0603
if not _rootlogger:
_rootlogger = logging.getLogger(LOGGER_NAME)
_rootlogger.handlers = []
_rootlogger.addHandler(logging.NullHandler())
_rootlogger.setLevel(DEBUG)
return _rootlogger
[docs]
def log_to_stderr(level: int | None = None, pass_root_logger_level: bool = False):
"""Turn on logging and add a handler which prints to stderr.
Args:
level: minimum level of the messages that will be logged
pass_root_logger_level: bool, optional. Default False
if True, all module loggers will be set to the same logging level as the root logger.
"""
if not level:
level = DEFAULT_LEVEL
logger = get_rootlogger()
# avoid creation of multiple stream handlers for logging to console
for entry in logger.handlers:
if (isinstance(entry, logging.StreamHandler)) and (
isinstance(entry.formatter, MESAColorFormatter)
):
return logger
formatter = MESAColorFormatter()
handler = logging.StreamHandler()
handler.setLevel(level)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.propagate = False
if pass_root_logger_level:
for _, mod_logger in _module_loggers.items():
mod_logger.setLevel(level)
return logger