Skip to content

Commit a78bc36

Browse files
authored
Fix logging (#96)
1 parent 3d21a67 commit a78bc36

5 files changed

Lines changed: 79 additions & 81 deletions

File tree

hapiclient/hapi.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,14 @@ def hapi(*args, **kwargs):
210210
# TODO: Consider warning.
211211
STOP = STOP + 'Z'
212212

213-
# Override defaults
214-
opts = setopts(hapiopts(), kwargs)
215213

216-
msg = "logging keyword must be True, False, or a file-like object"
217-
assert (isinstance(opts['logging'], bool) or hasattr(opts['logging'], 'write')), msg
214+
if 'logging' in kwargs and not isinstance(kwargs['logging'], bool):
215+
raise ValueError("logging keyword must be True or False")
216+
217+
configure_logging(kwargs.get('logging', None))
218218

219-
configure_logging(opts)
219+
# Override defaults
220+
opts = setopts(hapiopts(), kwargs)
220221

221222
assert (opts['cache'] in [True, False]), "cache keyword must be True of False"
222223
assert (opts['usecache'] in [True, False]), "usecache keyword must be True of False"

hapiclient/log.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,16 @@
55
_INTERNAL_HANDLER_ATTR = "_hapiclient_internal_handler"
66
_INTERNAL_LEVEL_ATTR = "_hapiclient_internal_level"
77

8-
# Disable propagation and add NullHandler by default so hapiclient
9-
# is silent unless explicitly configured by user. This is the standard
10-
# practice for library loggers.
8+
# Disable propagation by default so hapiclient logs don't bubble up
9+
# to root logger (e.g., pytest's logger). Can be re-enabled by user.
1110
_logger.propagate = False
12-
_logger.setLevel(_logging.NOTSET)
13-
if not _logger.handlers:
14-
_null_handler = _logging.NullHandler()
15-
setattr(_null_handler, _INTERNAL_HANDLER_ATTR, True)
16-
_logger.addHandler(_null_handler)
1711

1812

19-
def configure_logging(opts):
13+
def configure_logging(logging):
2014
"""Configure the hapiclient logger based on opts['logging'].
2115
2216
If the hapiclient logger has been configured externally (level != NOTSET
23-
or handlers present), the logging kwarg.
17+
or handlers present), the logging kwarg is ignored and a warning is logged.
2418
"""
2519
has_user_level = _logger.level != _logging.NOTSET and \
2620
_logger.level != getattr(_logger, _INTERNAL_LEVEL_ATTR, None)
@@ -29,7 +23,7 @@ def configure_logging(opts):
2923
for handler in _logger.handlers
3024
)
3125

32-
if opts['logging']:
26+
if logging is True:
3327
_logger.setLevel(_logging.INFO)
3428
setattr(_logger, _INTERNAL_LEVEL_ATTR, _logging.INFO)
3529
_logger.propagate = False
@@ -39,13 +33,16 @@ def configure_logging(opts):
3933
_handler.setFormatter(_logging.Formatter("%(message)s"))
4034
setattr(_handler, _INTERNAL_HANDLER_ATTR, True)
4135
_logger.addHandler(_handler)
42-
else:
36+
if logging is False:
4337
if has_user_level or has_user_handlers:
44-
# Don't log when logging is disabled - would cause output during tests
45-
pass
38+
from .util import warning
39+
if has_user_handlers:
40+
warning("Ignoring logging=False because standard Python logger for 'hapiclient' already configured with handlers.")
41+
else:
42+
warning("Ignoring logging=False because standard Python logger for 'hapiclient' already configured with log_level != NOTSET.")
4643
else:
47-
_logger.setLevel(_logging.NOTSET)
48-
setattr(_logger, _INTERNAL_LEVEL_ATTR, _logging.NOTSET)
44+
_logger.setLevel(_logging.WARNING)
45+
setattr(_logger, _INTERNAL_LEVEL_ATTR, _logging.WARNING)
4946

5047

5148
def log(msg, opts=None):

hapiclient/util.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from hapiclient.log import log, configure_logging
1+
from hapiclient.log import log
22

33

44
def setopts(defaults, given):
@@ -148,7 +148,7 @@ def prefix():
148148
import platform
149149
prefix = "\x1b[31mHAPIWarning:\x1b[0m "
150150
if platform.system() == 'Windows' and pythonshell() == 'shell':
151-
prefix = "HAPIWarning: "
151+
prefix = "HAPIWarning: "
152152

153153
return prefix
154154

misc/hapi_logging_demo.py

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import logging
2+
13
from hapiclient import hapi
24

35
server = 'http://hapi-server.org/servers/TestData2.0/hapi'
@@ -6,35 +8,57 @@
68
start = '1970-01-01T00:00:00'
79
stop = '1970-01-01T00:01:00'
810

9-
method = 3
10-
11-
# 1. Log to console using logging keyword (legacy)
12-
if method == 1:
13-
data, meta = hapi(server, dataset, parameters, start, stop, logging=True)
1411

15-
# 2. Log to console using Python's standard logging module
16-
if method == 2:
17-
import logging
18-
logger = logging.getLogger("hapiclient")
19-
logger.setLevel(logging.INFO)
20-
handler = logging.StreamHandler()
21-
formatter = logging.Formatter("%(asctime)s [%(name)s] %(message)s", datefmt="%Y-%m-%dT%H:%M:%S")
22-
formatter.default_msec_format = '%s.%03d'
23-
handler.setFormatter(formatter)
24-
logger.addHandler(handler)
25-
data, meta = hapi(server, dataset, parameters, start, stop)
26-
27-
# 3. Log to a file using Python's standard logging module
28-
if method == 3:
29-
import logging
12+
def reset_hapiclient_logging():
3013
logger = logging.getLogger("hapiclient")
31-
logger.setLevel(logging.INFO)
32-
handler = logging.FileHandler("hapi_logging_demo.log")
33-
formatter = logging.Formatter("%(asctime)s %(message)s", datefmt="%Y-%m-%dT%H:%M:%S")
34-
formatter.default_msec_format = '%s.%03d'
35-
handler.setFormatter(formatter)
36-
logger.addHandler(handler)
37-
data, meta = hapi(server, dataset, parameters, start, stop)
38-
39-
# Note that logging keyword is ignored when using Python's standard logging module.
40-
data, meta = hapi(server, dataset, parameters, start, stop, logging=False)
14+
for handler in list(logger.handlers):
15+
logger.removeHandler(handler)
16+
handler.close()
17+
logger.setLevel(logging.NOTSET)
18+
logger.propagate = False
19+
if hasattr(logger, "_hapiclient_internal_level"):
20+
delattr(logger, "_hapiclient_internal_level")
21+
22+
23+
for method in range(1, 7):
24+
25+
reset_hapiclient_logging()
26+
27+
print(80*"-")
28+
print("Method {}: ".format(method))
29+
print(80*"-")
30+
31+
# 1. Log to console using logging keyword (legacy)
32+
if 1 <= method <= 3:
33+
if method == 1:
34+
# No logging kwarg, so no logging
35+
data, meta = hapi(server, dataset, parameters, start, stop)
36+
if method == 2:
37+
# Use internal logging because logging=True and no standard logging handlers defined
38+
data, meta = hapi(server, dataset, parameters, start, stop, logging=True)
39+
if method == 3:
40+
# No logging because logging=False and no standard logging handlers defined
41+
data, meta = hapi(server, dataset, parameters, start, stop, logging=False)
42+
43+
# 2. Log to console using Python's standard logging module
44+
if 4 <= method <= 6:
45+
import logging
46+
logger = logging.getLogger("hapiclient")
47+
logger.setLevel(logging.INFO)
48+
handler = logging.StreamHandler()
49+
formatter = logging.Formatter("%(asctime)s [%(name)s] %(message)s", datefmt="%Y-%m-%dT%H:%M:%S")
50+
formatter.default_msec_format = '%s.%03d'
51+
handler.setFormatter(formatter)
52+
logger.addHandler(handler)
53+
54+
if method == 4:
55+
# Use standard logging because defined
56+
data, meta = hapi(server, dataset, parameters, start, stop)
57+
if method == 5:
58+
# Use standard logging because defined
59+
data, meta = hapi(server, dataset, parameters, start, stop, logging=True)
60+
if method == 6:
61+
# Warning because standard logging defined but logging=False
62+
data, meta = hapi(server, dataset, parameters, start, stop, logging=False)
63+
64+
print(80*"-" + "\n")

test/test_logging.py

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
def _reset_logger():
1313
"""Remove all handlers and reset level on the hapiclient logger."""
1414
logger = logging.getLogger("hapiclient")
15-
logger.handlers.clear()
15+
for handler in list(logger.handlers):
16+
logger.removeHandler(handler)
17+
handler.close()
1618
logger.setLevel(logging.NOTSET)
19+
logger.propagate = False
1720
if hasattr(logger, "_hapiclient_internal_level"):
1821
delattr(logger, "_hapiclient_internal_level")
1922

@@ -87,30 +90,6 @@ def test_logging_false_ignored_when_externally_configured(self):
8790
assert 'Running hapi.py version' in output
8891

8992

90-
class TestStandardLoggingFile:
91-
"""Method 4: Standard logging with FileHandler."""
92-
93-
def setup_method(self):
94-
_reset_logger()
95-
96-
def teardown_method(self):
97-
_reset_logger()
98-
99-
def test_logs_to_file(self, tmp_path):
100-
logfile = tmp_path / "hapiclient.log"
101-
logger = logging.getLogger("hapiclient")
102-
logger.setLevel(logging.INFO)
103-
handler = logging.FileHandler(str(logfile))
104-
handler.setFormatter(logging.Formatter("%(asctime)s %(message)s"))
105-
logger.addHandler(handler)
106-
107-
data, meta = hapi(server, dataset, parameters, start, stop)
108-
handler.close()
109-
110-
content = logfile.read_text()
111-
assert 'Running hapi.py version' in content
112-
113-
11493
class TestLoggingKeywordFalse:
11594
"""Default: logging=False suppresses INFO messages."""
11695

@@ -129,9 +108,6 @@ def test_no_info_output(self, capsys):
129108

130109
if __name__ == "__main__":
131110
import pytest
132-
#pytest.main([__file__])
133111
pytest.main([__file__ + "::TestLoggingKeywordFalse", "-v"])
134112
pytest.main([__file__ + "::TestLoggingKeywordTrue", "-v"])
135-
pytest.main([__file__ + "::TestLoggingKeywordFileObject", "-v"])
136113
pytest.main([__file__ + "::TestStandardLoggingConsole", "-v"])
137-
pytest.main([__file__ + "::TestStandardLoggingFile", "-v"])

0 commit comments

Comments
 (0)