"""Logging utilities for the movement package."""
import sys
import warnings
from datetime import datetime
from functools import wraps
from pathlib import Path
from loguru import logger as loguru_logger
DEFAULT_LOG_DIRECTORY = Path.home() / ".movement"
[docs]
class MovementLogger:
"""A custom logger extending the :mod:`loguru logger <loguru._logger>`."""
def __init__(self):
"""Initialize the logger with the :mod:`loguru._logger`."""
self.logger = loguru_logger
def _log_and_return_exception(self, log_method, message, *args, **kwargs):
"""Log the message and return an Exception if specified."""
log_method(message, *args, **kwargs)
if isinstance(message, Exception):
return message
[docs]
def error(self, message, *args, **kwargs):
"""Log error message and optionally return an Exception.
This method overrides loguru's
:meth:`logger.error() <loguru._logger.Logger.error>` to optionally
return an Exception if the message is an Exception.
"""
return self._log_and_return_exception(
self.logger.error, message, *args, **kwargs
)
[docs]
def exception(self, message, *args, **kwargs):
"""Log error message with traceback and optionally return an Exception.
This method overrides loguru's
:meth:`logger.exception() <loguru._logger.Logger.exception>` to
optionally return an Exception if the message is an Exception.
"""
return self._log_and_return_exception(
self.logger.exception, message, *args, **kwargs
)
def __getattr__(self, name):
"""Redirect attribute access to the loguru logger."""
return getattr(self.logger, name)
def __repr__(self):
"""Return the loguru logger's representation."""
return repr(self.logger)
logger = MovementLogger()
[docs]
def showwarning(message, category, filename, lineno, file=None, line=None):
"""Redirect alerts from the :mod:`warnings` module to the logger.
This function replaces :func:`logging.captureWarnings` which redirects
warnings issued by the :mod:`warnings` module to the logging system.
"""
formatted_message = warnings.formatwarning(
message, category, filename, lineno, line
)
logger.opt(depth=2).warning(formatted_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