Source code for movement.utils.logging
"""Logging utilities for the movement package."""
import logging
from datetime import datetime
from functools import wraps
from logging.handlers import RotatingFileHandler
from pathlib import Path
FORMAT = (
"%(asctime)s - %(levelname)s - "
"%(processName)s %(filename)s:%(lineno)s - %(message)s"
)
DEFAULT_LOG_DIRECTORY = Path.home() / ".movement"
[docs]
def configure_logging(
log_level: int = logging.DEBUG,
logger_name: str = "movement",
log_directory: Path = DEFAULT_LOG_DIRECTORY,
):
"""Configure the logging module.
This function sets up a circular log file with a rotating file handler.
Parameters
----------
log_level : int, optional
The logging level to use. Defaults to logging.INFO.
logger_name : str, optional
The name of the logger to configure.
Defaults to "movement".
log_directory : pathlib.Path, optional
The directory to store the log file in. Defaults to
~/.movement. A different directory can be specified,
for example for testing purposes.
"""
# Set the log directory and file path
log_directory.mkdir(parents=True, exist_ok=True)
log_file = (log_directory / f"{logger_name}.log").as_posix()
# Check if logger with the given name is already configured
logger_configured = logger_name in logging.root.manager.loggerDict
logger = logging.getLogger(logger_name)
# Logger needs to be (re)configured if unconfigured or
# if configured but the log file path has changed
configure_logger = (
not logger_configured
or not logger.handlers
or log_file != logger.handlers[0].baseFilename # type: ignore
)
if configure_logger:
if logger_configured:
# remove the handlers to allow for reconfiguration
logger.handlers.clear()
logger.setLevel(log_level)
# Create a rotating file handler
max_log_size = 5 * 1024 * 1024 # 5 MB
handler = RotatingFileHandler(log_file, maxBytes=max_log_size)
# Create a formatter and set it to the handler
formatter = logging.Formatter(FORMAT)
handler.setFormatter(formatter)
# Add the handler to the logger
logger.addHandler(handler)
[docs]
def log_error(error, message: str, logger_name: str = "movement"):
"""Log an error message and return the Exception.
Parameters
----------
error : Exception
The error to log and return.
message : str
The error message.
logger_name : str, optional
The name of the logger to use. Defaults to "movement".
Returns
-------
Exception
The error that was passed in.
"""
logger = logging.getLogger(logger_name)
logger.error(message)
return error(message)
[docs]
def log_warning(message: str, logger_name: str = "movement"):
"""Log a warning message.
Parameters
----------
message : str
The warning message.
logger_name : str, optional
The name of the logger to use. Defaults to "movement".
"""
logger = logging.getLogger(logger_name)
logger.warning(message)
[docs]
def log_to_attrs(func):
"""Log the operation performed by the wrapped function.
This decorator appends log entries to the data's ``log``
attribute. The wrapped function must accept an :class:`xarray.Dataset`
or :class:`xarray.DataArray` as its first argument and return an
object of the same type.
"""
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
log_entry = {
"operation": func.__name__,
"datetime": str(datetime.now()),
**{f"arg_{i}": arg for i, arg in enumerate(args[1:], start=1)},
**kwargs,
}
# Append the log entry to the result's attributes
if result is not None and hasattr(result, "attrs"):
if "log" not in result.attrs:
result.attrs["log"] = []
result.attrs["log"].append(log_entry)
return result
return wrapper