diff --git a/zppy_interfaces/global_time_series/__main__.py b/zppy_interfaces/global_time_series/__main__.py index 2dc9e21..0cad995 100644 --- a/zppy_interfaces/global_time_series/__main__.py +++ b/zppy_interfaces/global_time_series/__main__.py @@ -6,9 +6,12 @@ from zppy_interfaces.global_time_series.coupled_global import coupled_global from zppy_interfaces.global_time_series.ocean_month import ocean_month from zppy_interfaces.global_time_series.utils import Parameters -from zppy_interfaces.multi_utils.logger import _setup_custom_logger +from zppy_interfaces.multi_utils.logger import _setup_child_logger, _setup_root_logger -logger = _setup_custom_logger(__name__) +# Set up the root logger and module level logger. The module level logger is +# a child of the root logger. +_setup_root_logger() +logger = _setup_child_logger(__name__) def main(parameters=None): diff --git a/zppy_interfaces/global_time_series/coupled_global.py b/zppy_interfaces/global_time_series/coupled_global.py index b07bee8..e7b6724 100644 --- a/zppy_interfaces/global_time_series/coupled_global.py +++ b/zppy_interfaces/global_time_series/coupled_global.py @@ -17,9 +17,9 @@ create_viewer_index, ) from zppy_interfaces.global_time_series.utils import Parameters -from zppy_interfaces.multi_utils.logger import _setup_custom_logger +from zppy_interfaces.multi_utils.logger import _setup_child_logger -logger = _setup_custom_logger(__name__) +logger = _setup_child_logger(__name__) # Useful helper functions and classes ######################################### diff --git a/zppy_interfaces/global_time_series/coupled_global_dataset_wrapper.py b/zppy_interfaces/global_time_series/coupled_global_dataset_wrapper.py index cd5e3dc..e9b3cce 100644 --- a/zppy_interfaces/global_time_series/coupled_global_dataset_wrapper.py +++ b/zppy_interfaces/global_time_series/coupled_global_dataset_wrapper.py @@ -4,9 +4,9 @@ import xcdat from zppy_interfaces.global_time_series.coupled_global_utils import Metric, Variable -from zppy_interfaces.multi_utils.logger import _setup_custom_logger +from zppy_interfaces.multi_utils.logger import _setup_child_logger -logger = _setup_custom_logger(__name__) +logger = _setup_child_logger(__name__) class DatasetWrapper(object): diff --git a/zppy_interfaces/global_time_series/coupled_global_plotting.py b/zppy_interfaces/global_time_series/coupled_global_plotting.py index 42f1f6b..76516d4 100644 --- a/zppy_interfaces/global_time_series/coupled_global_plotting.py +++ b/zppy_interfaces/global_time_series/coupled_global_plotting.py @@ -10,11 +10,11 @@ from netCDF4 import Dataset from zppy_interfaces.global_time_series.utils import Parameters -from zppy_interfaces.multi_utils.logger import _setup_custom_logger +from zppy_interfaces.multi_utils.logger import _setup_child_logger mpl.use("Agg") -logger = _setup_custom_logger(__name__) +logger = _setup_child_logger(__name__) # ---additional function to get moc time series diff --git a/zppy_interfaces/global_time_series/coupled_global_viewer.py b/zppy_interfaces/global_time_series/coupled_global_viewer.py index 3d650bf..5c7323b 100644 --- a/zppy_interfaces/global_time_series/coupled_global_viewer.py +++ b/zppy_interfaces/global_time_series/coupled_global_viewer.py @@ -6,10 +6,10 @@ from zppy_interfaces.global_time_series.coupled_global_utils import Metric, Variable from zppy_interfaces.global_time_series.utils import Parameters -from zppy_interfaces.multi_utils.logger import _setup_custom_logger +from zppy_interfaces.multi_utils.logger import _setup_child_logger from zppy_interfaces.multi_utils.viewer import OutputViewer -logger = _setup_custom_logger(__name__) +logger = _setup_child_logger(__name__) class VariableGroup(object): diff --git a/zppy_interfaces/global_time_series/ocean_month.py b/zppy_interfaces/global_time_series/ocean_month.py index 9331cb9..4295203 100644 --- a/zppy_interfaces/global_time_series/ocean_month.py +++ b/zppy_interfaces/global_time_series/ocean_month.py @@ -7,9 +7,9 @@ from mpas_tools.cime.constants import constants from netCDF4 import Dataset, chartostring, date2num -from zppy_interfaces.multi_utils.logger import _setup_custom_logger +from zppy_interfaces.multi_utils.logger import _setup_child_logger -logger = _setup_custom_logger(__name__) +logger = _setup_child_logger(__name__) def ocean_month( diff --git a/zppy_interfaces/multi_utils/logger.py b/zppy_interfaces/multi_utils/logger.py index 32a5f6e..5b834f7 100644 --- a/zppy_interfaces/multi_utils/logger.py +++ b/zppy_interfaces/multi_utils/logger.py @@ -1,34 +1,57 @@ -"""Logger module for setting up a logger. Based on xcdat/xcdat/_logger.py""" +"""Logger module for setting up a custom logger. Based on e3sm_diags/e3sm_diags/logger.py.""" import logging import logging.handlers -# Logging module setup -log_format = ( +LOG_FILENAME = "zppy_interfaces_run.log" +LOG_FORMAT = ( "%(asctime)s [%(levelname)s]: %(filename)s(%(funcName)s:%(lineno)s) >> %(message)s" ) -logging.basicConfig(format=log_format, filemode="w", level=logging.INFO) +LOG_FILEMODE = "w" +LOG_LEVEL = logging.INFO -# Console handler setup +# Add a console handler to display warnings in the console. This is useful +# for when other package loggers raise warnings (e.g, NumPy, Xarray). console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) -logFormatter = logging.Formatter(log_format) -console_handler.setFormatter(logFormatter) +console_handler.setFormatter(logging.Formatter(LOG_FORMAT)) logging.getLogger().addHandler(console_handler) -# Following the example of https://github.com/xCDAT/xcdat/blob/27396e510185fddfe04921427ce2d67618a93709/xcdat/_logger.py#L20 -def _setup_custom_logger(name, propagate=True) -> logging.Logger: - """Sets up a custom logger. +def _setup_root_logger(): + """Configures the root logger. - Documentation on logging: https://docs.python.org/3/library/logging.html + This function sets up the root logger with a predefined format and log level. + It also enables capturing of warnings issued by the `warnings` module and + redirects them to the logging system. + + Notes + ----- + - The `force=True` parameter ensures that any existing logging configuration + is overridden. + - The file handler is added dynamically to the root logger later in the + ``Run`` class once the log file path is known. + """ + logging.basicConfig( + format=LOG_FORMAT, + level=LOG_LEVEL, + force=True, + ) + + logging.captureWarnings(True) + + +def _setup_child_logger(name: str, propagate: bool = True) -> logging.Logger: + """Sets up a logger that is a child of the root logger. + + This child logger inherits the root logger's handlers. Parameters ---------- name : str Name of the file where this function is called. propagate : bool, optional - Whether to propagate logger messages or not, by default True + Whether to propagate logger messages or not, by default True. Returns ------- @@ -37,8 +60,6 @@ def _setup_custom_logger(name, propagate=True) -> logging.Logger: Examples --------- - Available levels: https://docs.python.org/3/library/logging.html#levels - Detailed information, typically of interest only when diagnosing problems: >>> logger.debug("") @@ -57,6 +78,10 @@ def _setup_custom_logger(name, propagate=True) -> logging.Logger: >>> logger.error("") + Similar to ``logger.error()``, but also outputs stack trace: + + >>> logger.exception("", exc_info=True) + A serious error, indicating that the program itself may be unable to continue running: @@ -64,6 +89,5 @@ def _setup_custom_logger(name, propagate=True) -> logging.Logger: """ logger = logging.getLogger(name) logger.propagate = propagate - # logger.setLevel("DEBUG") return logger diff --git a/zppy_interfaces/multi_utils/viewer.py b/zppy_interfaces/multi_utils/viewer.py index 958aaa7..f322799 100644 --- a/zppy_interfaces/multi_utils/viewer.py +++ b/zppy_interfaces/multi_utils/viewer.py @@ -11,9 +11,9 @@ ) from output_viewer.utils import rechmod -from zppy_interfaces.multi_utils.logger import _setup_custom_logger +from zppy_interfaces.multi_utils.logger import _setup_child_logger -logger = _setup_custom_logger(__name__) +logger = _setup_child_logger(__name__) # Copied from e3sm_diags