Skip to content

Commit 0767329

Browse files
authored
Merge pull request #3547 from jsiirola/logstream-loggeradapter
Support passing LoggerAdapter objects to LogStream
2 parents b524a24 + d593c68 commit 0767329

File tree

4 files changed

+89
-1
lines changed

4 files changed

+89
-1
lines changed

pyomo/common/log.py

+2
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,8 @@ def redirect_streams(self, redirects):
415415
"""
416416
found = 0
417417
logger = self._logger
418+
while isinstance(logger, logging.LoggerAdapter):
419+
logger = logger.logger
418420
while logger:
419421
for handler in logger.handlers:
420422
found += 1

pyomo/common/tests/test_log.py

+12
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,18 @@ def test_log_stream(self):
550550
# Exiting the context manager flushes the LogStream
551551
self.assertEqual(OUT.getvalue(), "INFO: line 1\nINFO: line 2\n")
552552

553+
def test_loggerAdapter(self):
554+
class Adapter(logging.LoggerAdapter):
555+
def process(self, msg, kwargs):
556+
return '[%s] %s' % (self.extra['foo'], msg), kwargs
557+
558+
adapter = Adapter(logging.getLogger('pyomo'), {"foo": 42})
559+
ls = LogStream(logging.INFO, adapter)
560+
LI = LoggingIntercept(level=logging.INFO, formatter=pyomo_formatter)
561+
with LI as OUT:
562+
ls.write("hello, world\n")
563+
self.assertEqual(OUT.getvalue(), "INFO: [42] hello, world\n")
564+
553565

554566
class TestPreformatted(unittest.TestCase):
555567
def test_preformatted_api(self):

pyomo/common/tests/test_tee.py

+74
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,80 @@ def test_capture_fd_to_logger(self):
422422
finally:
423423
logger.propagate, logger.handlers = orig
424424

425+
def test_capture_to_logger_adapter(self):
426+
class Adapter(logging.LoggerAdapter):
427+
def process(self, msg, kwargs):
428+
return '[%s] %s' % (self.extra['foo'], msg), kwargs
429+
430+
logger = logging.getLogger('_pyomo_no_logger')
431+
adapter = Adapter(logger, {"foo": 42})
432+
lstream = LogStream(logging.WARNING, adapter)
433+
orig = logger.propagate, logger.handlers
434+
try:
435+
logger.propagate = False
436+
logger.handlers = []
437+
with LoggingIntercept(module='_pyomo_no_logger') as LOG:
438+
with tee.capture_output(lstream, capture_fd=False):
439+
sys.stderr.write("hi!\n")
440+
sys.stderr.flush()
441+
self.assertEqual(LOG.getvalue(), "[42] hi!\n")
442+
443+
# test that we handle the lastResort logger correctly
444+
_lastResort = logging.lastResort
445+
with tee.capture_output() as OUT:
446+
with tee.capture_output(lstream, capture_fd=False):
447+
self.assertIsNot(_lastResort, logging.lastResort)
448+
sys.stderr.write("hi?\n")
449+
self.assertEqual(OUT.getvalue(), "[42] hi?\n")
450+
451+
# test that we allow redirect-to-logger out
452+
with tee.capture_output() as OUT:
453+
logger.addHandler(logging.NullHandler())
454+
logger.addHandler(logging.StreamHandler(sys.stderr))
455+
with tee.capture_output(lstream, capture_fd=False):
456+
sys.stderr.write("hi.\n")
457+
self.assertEqual(OUT.getvalue(), "[42] hi.\n")
458+
logger.handlers.clear()
459+
finally:
460+
logger.propagate, logger.handlers = orig
461+
462+
def test_capture_fd_to_logger_adapter(self):
463+
class Adapter(logging.LoggerAdapter):
464+
def process(self, msg, kwargs):
465+
return '[%s] %s' % (self.extra['foo'], msg), kwargs
466+
467+
logger = logging.getLogger('_pyomo_no_logger')
468+
adapter = Adapter(logger, {"foo": 42})
469+
lstream = LogStream(logging.WARNING, adapter)
470+
orig = logger.propagate, logger.handlers
471+
try:
472+
logger.propagate = False
473+
logger.handlers = []
474+
with LoggingIntercept(module='_pyomo_no_logger') as LOG:
475+
with tee.capture_output(lstream, capture_fd=True):
476+
sys.stderr.write("hi!\n")
477+
sys.stderr.flush()
478+
self.assertEqual(LOG.getvalue(), "[42] hi!\n")
479+
480+
# test that we handle the lastResort logger correctly
481+
_lastResort = logging.lastResort
482+
with tee.capture_output() as OUT:
483+
with tee.capture_output(lstream, capture_fd=True):
484+
self.assertIsNot(_lastResort, logging.lastResort)
485+
sys.stderr.write("hi?\n")
486+
self.assertEqual(OUT.getvalue(), "[42] hi?\n")
487+
488+
# test that we allow redirect-to-logger out
489+
with tee.capture_output() as OUT:
490+
logger.addHandler(logging.NullHandler())
491+
logger.addHandler(logging.StreamHandler(sys.stderr))
492+
with tee.capture_output(lstream, capture_fd=True):
493+
sys.stderr.write("hi.\n")
494+
self.assertEqual(OUT.getvalue(), "[42] hi.\n")
495+
logger.handlers.clear()
496+
finally:
497+
logger.propagate, logger.handlers = orig
498+
425499
def test_no_fileno_stdout(self):
426500
T = tee.capture_output()
427501
with T:

pyomo/opt/results/solution.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def pprint(self, ostream, option, from_list=False, prefix="", repn=None):
8989
#
9090
# the following is specialized logic for handling variable and
9191
# constraint maps - which are dictionaries of dictionaries, with
92-
# at a minimum an "id" element per sub-directionary.
92+
# at a minimum an "id" element per sub-dictionary.
9393
#
9494
first = True
9595
for key in self._order:

0 commit comments

Comments
 (0)