Skip to content

Commit 18b2f47

Browse files
authored
Merge pull request #727 from oliver-sanders/722
2 parents af66cc0 + 83ac6f9 commit 18b2f47

File tree

3 files changed

+60
-3
lines changed

3 files changed

+60
-3
lines changed

docs/source/changelog.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ Changes in Traitlets
44
Traitlets 5.2
55
-------------
66

7+
5.2.1
8+
*****
9+
10+
- logging: Don't attempt to close handlers unless they have been opened.
11+
Fixes ``ValueError: Unable to configure formatter 'console'`` traceback.
12+
713
5.2.0
814
*****
915

traitlets/config/application.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ def _configure_logging(self):
276276
config = self.get_default_logging_config()
277277
nested_update(config, self.logging_config or {})
278278
dictConfig(config)
279+
# make a note that we have configured logging
280+
self._logging_configured = True
279281

280282
@default("log")
281283
def _log_default(self):
@@ -942,9 +944,14 @@ def generate_config_file(self, classes=None):
942944
return "\n".join(lines)
943945

944946
def close_handlers(self):
945-
for handler in self.log.handlers:
946-
with suppress(Exception):
947-
handler.close()
947+
if getattr(self, "_logging_configured", False):
948+
# don't attempt to close handlers unless they have been opened
949+
# (note accessing self.log.handlers will create handlers if they
950+
# have not yet been initialised)
951+
for handler in self.log.handlers:
952+
with suppress(Exception):
953+
handler.close()
954+
self._logging_configured = False
948955

949956
def exit(self, exit_status=0):
950957
self.log.debug("Exiting application: %s" % self.name)

traitlets/config/tests/test_application.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,50 @@ def test_logging_config(tmp_path, capsys):
804804
assert capsys.readouterr().err == "[Application] WARNING | warn\n"
805805

806806

807+
@pytest.fixture
808+
def caplogconfig(monkeypatch):
809+
"""Capture logging config events for DictConfigurator objects.
810+
811+
This suppresses the event (so the configuration doesn't happen).
812+
813+
Returns a list of (args, kwargs).
814+
"""
815+
calls = []
816+
817+
def _configure(*args, **kwargs):
818+
nonlocal calls
819+
calls.append((args, kwargs))
820+
821+
monkeypatch.setattr(
822+
"logging.config.DictConfigurator.configure",
823+
_configure,
824+
)
825+
826+
return calls
827+
828+
829+
def test_logging_teardown_on_error(capsys, caplogconfig):
830+
"""Ensure we don't try to open logs in order to close them (See #722).
831+
832+
If you try to configure logging handlers whilst Python is shutting down
833+
you may get traceback.
834+
"""
835+
# create and destroy an app (without configuring logging)
836+
# (ensure that the logs were not configured)
837+
app = Application()
838+
del app
839+
assert len(caplogconfig) == 0 # logging was not configured
840+
out, err = capsys.readouterr()
841+
assert "Traceback" not in err
842+
843+
# ensure that the logs would have been configured otherwise
844+
# (to prevent this test from yielding false-negatives)
845+
app = Application()
846+
app._logging_configured = True # make it look like logging was configured
847+
del app
848+
assert len(caplogconfig) == 1 # logging was configured
849+
850+
807851
if __name__ == "__main__":
808852
# for test_help_output:
809853
MyApp.launch_instance()

0 commit comments

Comments
 (0)