Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions tests/test_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import logging

import pytest

from xcdat._logger import _setup_custom_logger, _setup_xcdat_logger


@pytest.fixture(autouse=True)
def reset_logging():
# Reset logging before each test.
logging.shutdown()

for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
yield

# Cleanup after test.
logging.shutdown()


class TestSetupXCDATLogger:
def test_logger_creation(self):
logger = _setup_xcdat_logger()

assert isinstance(logger, logging.Logger)
assert logger.name == "xcdat"

def test_logger_level(self):
logger = _setup_xcdat_logger(level=logging.DEBUG)

assert logger.level == logging.DEBUG

def test_logger_force(self):
logger = _setup_xcdat_logger()
handler_ids_before = [id(h) for h in logger.handlers]

# Force reconfiguration.
logger = _setup_xcdat_logger(force=True)

handler_ids_after = [id(h) for h in logger.handlers]

# Clean reset with one handler.
assert len(logger.handlers) == 1
# The handler should be a new instance, not the same object.
assert handler_ids_before != handler_ids_after

def test_logger_format(self):
logger = _setup_xcdat_logger()
handler = logger.handlers[0]

assert isinstance(handler.formatter, logging.Formatter)
assert handler.formatter._fmt == (
"%(asctime)s [%(levelname)s]: %(filename)s(%(funcName)s:%(lineno)s) >> %(message)s"
)

def test_logger_propagation(self):
logger = _setup_xcdat_logger()

assert logger.propagate is False

def test_logger_no_duplicate_handlers(self):
logger1 = _setup_xcdat_logger()
num_handlers_before = len(logger1.handlers)

logger2 = _setup_xcdat_logger()
num_handlers_after = len(logger2.handlers)

assert num_handlers_after == num_handlers_before


class TestSetupCustomLogger:
def test_custom_logger_creation(self):
logger = _setup_custom_logger("test_logger")

assert isinstance(logger, logging.Logger)
assert logger.name == "test_logger"

def test_custom_logger_propagation(self):
logger = _setup_custom_logger("test_logger", propagate=False)
assert logger.propagate is False

logger = _setup_custom_logger("test_logger", propagate=True)
assert logger.propagate is True

def test_custom_logger_inherits_from_xcdat(self):
_setup_xcdat_logger()
logger = _setup_custom_logger("xcdat.submodule")

assert logger.parent is not None and logger.parent.name == "xcdat"
4 changes: 4 additions & 0 deletions xcdat/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Top-level package for xcdat."""

from xcdat import tutorial # noqa: F401
from xcdat._logger import _setup_xcdat_logger
from xcdat.axis import ( # noqa: F401
center_times,
get_coords_by_name,
Expand All @@ -25,4 +26,7 @@
from xcdat.temporal import TemporalAccessor # noqa: F401
from xcdat.utils import compare_datasets # noqa: F401

# Initialize xCDAT logger once when the package is imported
_setup_xcdat_logger()

__version__ = "0.10.1"
48 changes: 38 additions & 10 deletions xcdat/_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,46 @@
import logging
import logging.handlers

# Logging module setup
log_format = (
LOG_FORMAT = (
"%(asctime)s [%(levelname)s]: %(filename)s(%(funcName)s:%(lineno)s) >> %(message)s"
)
logging.basicConfig(format=log_format, filemode="w", level=logging.INFO)

# Console handler setup
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
logFormatter = logging.Formatter(log_format)
console_handler.setFormatter(logFormatter)
logging.getLogger().addHandler(console_handler)
LOG_LEVEL = logging.INFO


def _setup_xcdat_logger(level: int = LOG_LEVEL, force: bool = False) -> logging.Logger:
"""Configures and returns the xCDAT package logger.
Parameters
----------
level : int
Logging level for xCDAT (e.g., logging.DEBUG, logging.INFO).
force : bool, optional
If True, clears existing handlers before adding a new one.
Returns
-------
logging.Logger
The xCDAT package logger.
"""
logger = logging.getLogger("xcdat")

if force:
# Remove all existing handlers (e.g., during reconfiguration)
for handler in logger.handlers[:]:
logger.removeHandler(handler)

if not logger.handlers:
handler = logging.StreamHandler()
handler.setLevel(level)
handler.setFormatter(logging.Formatter(LOG_FORMAT))
logger.addHandler(handler)

logger.setLevel(level)

# Don’t double-log through the root logger
logger.propagate = False

return logger


def _setup_custom_logger(name, propagate=True) -> logging.Logger:
Expand Down
Loading