Skip to content

Commit 46939e3

Browse files
committed
Replace _setup_root_logger with _setup_xcdat_logger
1 parent d28d0c9 commit 46939e3

File tree

3 files changed

+123
-19
lines changed

3 files changed

+123
-19
lines changed

tests/test_logger.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import logging
2+
3+
import pytest
4+
5+
from xcdat._logger import _setup_custom_logger, _setup_xcdat_logger
6+
7+
8+
@pytest.fixture(autouse=True)
9+
def reset_logging():
10+
# Reset logging before each test.
11+
logging.shutdown()
12+
13+
for handler in logging.root.handlers[:]:
14+
logging.root.removeHandler(handler)
15+
yield
16+
17+
# Cleanup after test.
18+
logging.shutdown()
19+
20+
21+
class TestSetupXCDATLogger:
22+
def test_logger_creation(self):
23+
logger = _setup_xcdat_logger()
24+
25+
assert isinstance(logger, logging.Logger)
26+
assert logger.name == "xcdat"
27+
28+
def test_logger_level(self):
29+
logger = _setup_xcdat_logger(level=logging.DEBUG)
30+
31+
assert logger.level == logging.DEBUG
32+
33+
def test_logger_force(self):
34+
logger = _setup_xcdat_logger()
35+
handler_ids_before = [id(h) for h in logger.handlers]
36+
37+
# Force reconfiguration.
38+
logger = _setup_xcdat_logger(force=True)
39+
40+
handler_ids_after = [id(h) for h in logger.handlers]
41+
42+
# Clean reset with one handler.
43+
assert len(logger.handlers) == 1
44+
# The handler should be a new instance, not the same object.
45+
assert handler_ids_before != handler_ids_after
46+
47+
def test_logger_format(self):
48+
logger = _setup_xcdat_logger()
49+
handler = logger.handlers[0]
50+
51+
assert isinstance(handler.formatter, logging.Formatter)
52+
assert handler.formatter._fmt == (
53+
"%(asctime)s [%(levelname)s]: %(filename)s(%(funcName)s:%(lineno)s) >> %(message)s"
54+
)
55+
56+
def test_logger_propagation(self):
57+
logger = _setup_xcdat_logger()
58+
59+
assert logger.propagate is False
60+
61+
def test_logger_no_duplicate_handlers(self):
62+
logger1 = _setup_xcdat_logger()
63+
num_handlers_before = len(logger1.handlers)
64+
65+
logger2 = _setup_xcdat_logger()
66+
num_handlers_after = len(logger2.handlers)
67+
68+
assert num_handlers_after == num_handlers_before
69+
70+
71+
class TestSetupCustomLogger:
72+
def test_custom_logger_creation(self):
73+
logger = _setup_custom_logger("test_logger")
74+
75+
assert isinstance(logger, logging.Logger)
76+
assert logger.name == "test_logger"
77+
78+
def test_custom_logger_propagation(self):
79+
logger = _setup_custom_logger("test_logger", propagate=False)
80+
assert logger.propagate is False
81+
82+
logger = _setup_custom_logger("test_logger", propagate=True)
83+
assert logger.propagate is True
84+
85+
def test_custom_logger_inherits_from_xcdat(self):
86+
_setup_xcdat_logger()
87+
logger = _setup_custom_logger("xcdat.submodule")
88+
89+
assert logger.parent is not None and logger.parent.name == "xcdat"

xcdat/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Top-level package for xcdat."""
22

33
from xcdat import tutorial # noqa: F401
4-
from xcdat._logger import _setup_root_logger
4+
from xcdat._logger import _setup_xcdat_logger
55
from xcdat.axis import ( # noqa: F401
66
center_times,
77
get_coords_by_name,
@@ -26,7 +26,7 @@
2626
from xcdat.temporal import TemporalAccessor # noqa: F401
2727
from xcdat.utils import compare_datasets # noqa: F401
2828

29-
# Initialize root logger once when the package is imported
30-
_setup_root_logger()
29+
# Initialize xCDAT logger once when the package is imported
30+
_setup_xcdat_logger()
3131

3232
__version__ = "0.10.1"

xcdat/_logger.py

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,46 @@
33
import logging
44
import logging.handlers
55

6-
# Logging module setup
76
LOG_FORMAT = (
87
"%(asctime)s [%(levelname)s]: %(filename)s(%(funcName)s:%(lineno)s) >> %(message)s"
98
)
109
LOG_LEVEL = logging.INFO
1110

1211

13-
def _setup_root_logger():
14-
"""Configures the root logger.
12+
def _setup_xcdat_logger(level: int = LOG_LEVEL, force: bool = False) -> logging.Logger:
13+
"""Configures and returns the xCDAT package logger.
1514
16-
This function sets up the root logger with a predefined format and log level.
17-
It also enables capturing of warnings issued by the `warnings` module and
18-
redirects them to the logging system.
15+
Parameters
16+
----------
17+
level : int
18+
Logging level for xCDAT (e.g., logging.DEBUG, logging.INFO).
19+
force : bool, optional
20+
If True, clears existing handlers before adding a new one.
1921
20-
Notes
21-
-----
22-
- The `force=True` parameter ensures that any existing logging configuration
23-
is overridden.
22+
Returns
23+
-------
24+
logging.Logger
25+
The xCDAT package logger.
2426
"""
25-
logging.basicConfig(
26-
format=LOG_FORMAT,
27-
level=LOG_LEVEL,
28-
force=True,
29-
)
30-
logging.captureWarnings(True)
27+
logger = logging.getLogger("xcdat")
28+
29+
if force:
30+
# Remove all existing handlers (e.g., during reconfiguration)
31+
for handler in logger.handlers[:]:
32+
logger.removeHandler(handler)
33+
34+
if not logger.handlers:
35+
handler = logging.StreamHandler()
36+
handler.setLevel(level)
37+
handler.setFormatter(logging.Formatter(LOG_FORMAT))
38+
logger.addHandler(handler)
39+
40+
logger.setLevel(level)
41+
42+
# Don’t double-log through the root logger
43+
logger.propagate = False
44+
45+
return logger
3146

3247

3348
def _setup_custom_logger(name, propagate=True) -> logging.Logger:

0 commit comments

Comments
 (0)