Skip to content

Commit 60b114d

Browse files
authored
Merge pull request #560 from OpenCOMPES/logging_tests_and_fixes
Logging tests and fixes
2 parents 6b927a2 + 1a41837 commit 60b114d

File tree

2 files changed

+141
-18
lines changed

2 files changed

+141
-18
lines changed

src/sed/core/logging.py

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import sys
1212
from datetime import datetime
1313
from functools import wraps
14+
from inspect import signature
1415
from typing import Callable
1516

1617
# Default log directory
@@ -43,28 +44,33 @@ def setup_logging(
4344
# Create base logger
4445
base_logger = logging.getLogger("sed")
4546
base_logger.setLevel(logging.DEBUG) # Set the minimum log level for the logger
46-
if set_base_handler or not base_logger.hasHandlers():
47-
if base_logger.hasHandlers():
47+
if set_base_handler or len(base_logger.handlers) == 0:
48+
if len(base_logger.handlers):
4849
base_logger.handlers.clear()
4950

5051
# Determine log file path
5152
if user_log_path is None:
5253
user_log_path = DEFAULT_LOG_DIR
53-
os.makedirs(user_log_path, exist_ok=True)
54-
log_file = os.path.join(user_log_path, f"sed_{datetime.now().strftime('%Y-%m-%d')}.log")
55-
56-
# Create file handler and set level to debug
57-
file_handler = logging.FileHandler(log_file)
58-
file_handler.setLevel(FILE_VERBOSITY)
59-
60-
# Create formatter for file
61-
file_formatter = logging.Formatter(
62-
"%(asctime)s - %(name)s - %(levelname)s - %(message)s in %(filename)s:%(lineno)d",
63-
)
64-
file_handler.setFormatter(file_formatter)
65-
66-
# Add file handler to logger
67-
base_logger.addHandler(file_handler)
54+
try:
55+
os.makedirs(user_log_path, exist_ok=True)
56+
log_file = os.path.join(user_log_path, f"sed_{datetime.now().strftime('%Y-%m-%d')}.log")
57+
58+
# Create file handler and set level to debug
59+
file_handler = logging.FileHandler(log_file)
60+
file_handler.setLevel(FILE_VERBOSITY)
61+
62+
# Create formatter for file
63+
file_formatter = logging.Formatter(
64+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s in %(filename)s:%(lineno)d",
65+
)
66+
file_handler.setFormatter(file_formatter)
67+
68+
# Add file handler to logger
69+
base_logger.addHandler(file_handler)
70+
except PermissionError:
71+
logging.warning(f"Cannot create logfile in Folder {user_log_path}, disabling logfile.")
72+
base_logger.addHandler(logging.NullHandler())
73+
base_logger.propagate = False
6874

6975
# create named logger
7076
logger = base_logger.getChild(name)
@@ -109,7 +115,11 @@ def log_call(func: Callable):
109115
def new_func(*args, **kwargs):
110116
saved_args = locals()
111117
args_str = ""
112-
for arg in saved_args["args"][1:]:
118+
for arg in (
119+
saved_args["args"][1:]
120+
if "self" in signature(func).parameters
121+
else saved_args["args"]
122+
):
113123
args_str += f"{arg}, "
114124
for name, arg in saved_args["kwargs"].items():
115125
args_str += f"{name}={arg}, "

tests/test_logging.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import io
2+
import logging
3+
import os
4+
from datetime import datetime
5+
6+
import pytest
7+
8+
from sed.core.logging import call_logger
9+
from sed.core.logging import set_verbosity
10+
from sed.core.logging import setup_logging
11+
12+
13+
@pytest.fixture
14+
def logger_():
15+
logger = setup_logging("test_logger")
16+
log_capture_string = io.StringIO()
17+
ch = logging.StreamHandler(log_capture_string)
18+
ch.setLevel(logging.DEBUG)
19+
logger.addHandler(ch)
20+
yield logger, log_capture_string
21+
22+
23+
def test_debug_logging(logger_):
24+
logger, log_capture_string = logger_
25+
logger.debug("This is a debug message")
26+
assert "This is a debug message" in log_capture_string.getvalue()
27+
28+
29+
def test_info_logging(logger_):
30+
logger, log_capture_string = logger_
31+
logger.info("This is an info message")
32+
assert "This is an info message" in log_capture_string.getvalue()
33+
34+
35+
def test_warning_logging(logger_):
36+
logger, log_capture_string = logger_
37+
logger.warning("This is a warning message")
38+
assert "This is a warning message" in log_capture_string.getvalue()
39+
40+
41+
def test_error_logging(logger_):
42+
logger, log_capture_string = logger_
43+
logger.error("This is an error message")
44+
assert "This is an error message" in log_capture_string.getvalue()
45+
46+
47+
def test_critical_logging(logger_):
48+
logger, log_capture_string = logger_
49+
logger.critical("This is a critical message")
50+
assert "This is a critical message" in log_capture_string.getvalue()
51+
52+
53+
def test_set_verbosity(logger_):
54+
logger, log_capture_string = logger_
55+
set_verbosity(logger, verbose=True)
56+
assert logger.handlers[0].level == logging.INFO
57+
set_verbosity(logger, verbose=False)
58+
assert logger.handlers[0].level == logging.WARNING
59+
60+
61+
def test_logger_has_base_logger(logger_):
62+
logger, log_capture_string = logger_
63+
assert logger.name == "sed.test_logger"
64+
assert logger.parent.name == "sed"
65+
assert logger.parent.parent.name == "root"
66+
assert logger.parent.level == logging.DEBUG
67+
assert isinstance(logger.parent.handlers[0], logging.FileHandler)
68+
69+
70+
def test_logger_creates_logfile(tmp_path):
71+
logger = setup_logging("test_logger", set_base_handler=True, user_log_path=tmp_path)
72+
log_file = os.path.join(tmp_path, f"sed_{datetime.now().strftime('%Y-%m-%d')}.log")
73+
assert os.path.exists(log_file)
74+
with open(log_file) as f:
75+
assert f.read() == ""
76+
logger.debug("This is a debug message")
77+
with open(log_file) as f:
78+
assert "This is a debug message" in f.read()
79+
80+
81+
def test_readonly_path(tmp_path, caplog):
82+
os.chmod(tmp_path, 0o444)
83+
with caplog.at_level(logging.WARNING):
84+
setup_logging("test_logger", set_base_handler=True, user_log_path=tmp_path)
85+
assert f"Cannot create logfile in Folder {tmp_path}, disabling logfile." in caplog.messages[0]
86+
log_file = os.path.join(tmp_path, f"sed_{datetime.now().strftime('%Y-%m-%d')}.log")
87+
assert not os.path.exists(log_file)
88+
89+
90+
def test_call_logger(logger_):
91+
logger, log_capture_string = logger_
92+
93+
@call_logger(logger)
94+
def test_function(test_param=None): # noqa: ARG001
95+
return
96+
97+
test_function(test_param=[1, 3, 5])
98+
assert "test_function(test_param=[1, 3, 5])" in log_capture_string.getvalue()
99+
100+
test_function([1, 3, 5])
101+
assert "test_function([1, 3, 5])" in log_capture_string.getvalue()
102+
103+
test_function()
104+
assert "test_function()" in log_capture_string.getvalue()
105+
106+
class TestClass:
107+
@call_logger(logger)
108+
def test_method(self, test_param=None): # noqa: ARG002
109+
return
110+
111+
test_instance = TestClass()
112+
test_instance.test_method(test_param=[1, 3, 5])
113+
assert "test_method(test_param=[1, 3, 5])" in log_capture_string.getvalue()

0 commit comments

Comments
 (0)