Skip to content

Commit 14c9627

Browse files
JP-4192: Work around default CRDS log configuration to allow stpipe capture (#312)
2 parents 6de859e + 6517a37 commit 14c9627

4 files changed

Lines changed: 81 additions & 6 deletions

File tree

changes/312.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add special handling for the CRDS logger to allow its messages to be captured by stpipe steps.

src/stpipe/crds_client.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22
This module defines functions that connect the core crds package
33
to stpipe, tailoring it to provide results in the forms required
44
by stpipe.
5-
6-
WARNING: stpipe and crds have circular dependencies. Do not use crds imports
7-
directly in modules other than this crds_client so that dependency order and
8-
general integration can be managed here.
95
"""
106

117
import re

src/stpipe/log.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from astropy.extern.configobj import validate
1414
from astropy.extern.configobj.configobj import ConfigObj
15+
from crds.core import log as crds_log
1516

1617
from . import config_parser
1718

@@ -102,6 +103,7 @@ def __init__(
102103
self.handlers.append(BreakHandler(self.break_level))
103104

104105
self._previous_level = {}
106+
self._previous_crds_console = False
105107

106108
def get_handler(self, handler_str):
107109
"""
@@ -131,6 +133,13 @@ def apply(self, log_names=None):
131133
If a logger has already been configured and not undone,
132134
it will be skipped.
133135
136+
Special handling is applied for the following log names:
137+
138+
- "py.warnings": If provided, Python warnings are captured and
139+
logged (``logging.captureWarnings(True)``).
140+
- "CRDS": If provided, the default stream handler for the CRDS
141+
logger is removed if needed.
142+
134143
Parameters
135144
----------
136145
log_names : tuple of str, list of str, or None, optional
@@ -141,6 +150,10 @@ def apply(self, log_names=None):
141150
log_names = [STPIPE_ROOT_LOGGER]
142151
if "py.warnings" in log_names:
143152
logging.captureWarnings(True)
153+
if "CRDS" in log_names:
154+
self._previous_crds_console = crds_log.THE_LOGGER.console is not None
155+
if self._previous_crds_console:
156+
crds_log.remove_console_handler()
144157
for log_name in log_names:
145158
# Don't reapply configuration, to avoid overwriting the
146159
# previously recorded level.
@@ -160,6 +173,14 @@ def undo(self, log_names=None, close_handlers=True):
160173
"""
161174
Removes the configuration from known loggers.
162175
176+
Special handling is applied for the following log names:
177+
178+
- "py.warnings": If provided, Python warnings are no longer captured
179+
and logged (``logging.captureWarnings(False)``).
180+
- "CRDS": If provided and if the default stream handler for the CRDS
181+
logger was present when configuration was applied, it will be
182+
restored.
183+
163184
Parameters
164185
----------
165186
log_names : list of str or None, optional
@@ -191,6 +212,9 @@ def undo(self, log_names=None, close_handlers=True):
191212
LogConfig.applied = None
192213
if "py.warnings" in log_names:
193214
logging.captureWarnings(False)
215+
if "CRDS" in log_names and self._previous_crds_console:
216+
self._previous_crds_console = False
217+
crds_log.add_console_handler()
194218

195219
@contextmanager
196220
def context(self, log_names=None):

tests/test_logger.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import warnings
44

55
import pytest
6+
from crds.core import log as crds_log
67

78
import stpipe.cmdline
89
from stpipe import Step
@@ -49,7 +50,7 @@ def _clean_up_logging():
4950

5051

5152
class LoggingStep(Step):
52-
"""A Step that utilizes a local logger to log a warning."""
53+
"""A Step that uses a local and external logger to log messages."""
5354

5455
spec = """
5556
str1 = string(default='default')
@@ -74,7 +75,7 @@ def get_stpipe_loggers():
7475

7576

7677
class LoggingPipeline(Pipeline):
77-
"""A Pipeline that utilizes a local logger to log a warning."""
78+
"""A Pipeline that uses a local and external logger to log messages."""
7879

7980
spec = """
8081
str1 = string(default='default')
@@ -589,3 +590,56 @@ def get_stpipe_loggers():
589590
assert len(recwarn) == 1 # Unchanged from above assert
590591
assert MSG in caplog.text
591592
assert logging._warnings_showwarning is None
593+
594+
595+
@pytest.mark.parametrize("restore_console", [True, False])
596+
def test_crds_log(capsys, restore_console):
597+
class CRDSLoggingStep(LoggingStep):
598+
def process(self):
599+
logger.info(STEP_INFO)
600+
logger.warning(STEP_WARNING)
601+
logger.debug(STEP_DEBUG)
602+
crds_log.info(EXTERNAL_INFO)
603+
crds_log.warning(EXTERNAL_WARNING)
604+
crds_log.debug(EXTERNAL_DEBUG)
605+
606+
@staticmethod
607+
def get_stpipe_loggers():
608+
return ("stpipe", "CRDS")
609+
610+
crds_logger = logging.getLogger("CRDS")
611+
if restore_console:
612+
# Before the step call, the crds logger has a stream handler
613+
assert len(crds_logger.handlers) == 1
614+
assert isinstance(crds_logger.handlers[0], logging.StreamHandler)
615+
else:
616+
# remove any current console handler
617+
crds_log.remove_console_handler()
618+
619+
# Before the step call, the crds logger has no handlers
620+
assert len(crds_logger.handlers) == 0
621+
622+
# During the step call, the CRDS handler is removed if necessary and
623+
# the stpipe handler is attached
624+
CRDSLoggingStep.call()
625+
626+
# With default configuration, exactly one info and warning message is expected
627+
# from each captured logger in the stderr stream
628+
capt = capsys.readouterr()
629+
for msg in ALL_MESSAGES:
630+
assert capt.out == ""
631+
if msg in [STEP_INFO, STEP_WARNING, EXTERNAL_INFO, EXTERNAL_WARNING]:
632+
assert msg in capt.err
633+
assert capt.err.count(msg) == 1
634+
else:
635+
assert capt.err.count(msg) == 0
636+
637+
# After the call is complete, the stpipe logger is removed and the CRDS
638+
# stream logger is restored if it was previously present
639+
if restore_console:
640+
assert len(crds_logger.handlers) == 1
641+
assert isinstance(crds_logger.handlers[0], logging.StreamHandler)
642+
else:
643+
assert len(crds_logger.handlers) == 0
644+
# Clean up: restore a console logger
645+
crds_log.add_console_handler()

0 commit comments

Comments
 (0)