0

I want to capture logs that are sent to Stream.

MyCode.py passes log to console: 2021-09-29 15:06:11,382 - root - ERROR - Started. However, captured.records returns nothing. (First 2 lines in output)

Sources


Questions

  • Why does captured return nothing?
  • How can I capture logs sent to StreamHandler(sys.stdout)?

MyCode.py:

import logging
import sys

logger = logging.getLogger()
streamHandler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)
logger.error('Started')

main.py:

import unittest
from unittest import TestCase
import MyCode

class TestExample(TestCase):
    def test_logging(self):
        with self.assertLogs() as captured:
            print('captured.records: ', captured.records)
            self.assertEqual(len(captured.records), 1)
            self.assertTrue("Started" in captured.records[0].getMessage())

if __name__ == '__main__':
    unittest.main()

Console:

2021-09-29 15:06:11,382 - root - ERROR - Started
captured.records:  []
F
======================================================================
FAIL: test_logging (__main__.TestExample)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "main.py", line 9, in test_logging
    self.assertEqual(len(captured.records), 1)
AssertionError: 0 != 1

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
repl process died unexpectedly: exit status 1

Please let me know if there's anything else I should add to post.

StressedBoi69420
  • 1,376
  • 1
  • 12
  • 40

1 Answers1

1

The assertLogs context manager only captures log messages that are created in its context. Your log messages were created when MyCode was imported, which happened before the log assertion context was created.

If you put your code in a function, and run that function within the test, it should work.

MyCode.py:

import logging
import sys

def foo():
    logger = logging.getLogger()
    streamHandler = logging.StreamHandler(sys.stdout)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - (message)s')
    streamHandler.setFormatter(formatter)
    logger.addHandler(streamHandler)
    logger.error('Started')

main.py:

import unittest
from unittest import TestCase
import MyCode

class TestExample(TestCase):
    def test_logging(self):
        with self.assertLogs() as captured:
            MyCode.foo()
            print('captured.records: ', captured.records)
            self.assertEqual(len(captured.records), 1)
            self.assertTrue("Started" in captured.records[0].getMessage())

if __name__ == '__main__':
    unittest.main()
jisrael18
  • 719
  • 3
  • 10
  • 1
    If you need to validate that logging happens, and that logging must happen outside of your test context, you can add another handler to the logger that simply stores the log messages in a list. Another option would be to use a `logging.FileHandler` and validate the contents of the file. – jisrael18 Sep 29 '21 at 15:58
  • Tysm. I am going to try this solution out and report back here. Yes, I would need to have a `list` (a requirement is to not have too many files during runtime). – StressedBoi69420 Sep 30 '21 at 08:13
  • Legend! Tysm. It works. How might I make a `list` in `MyCode.py`, to append `logger.error()`s to, that is accessible to `main.py`? – StressedBoi69420 Sep 30 '21 at 08:23
  • I'd take a look at the custom log handler from this answer https://stackoverflow.com/a/36408692/15013600. You would just add an instance of that handler to your logger instead of (or in addition to) `streamHandler`. Also FYI, it's generally a good idea to have your modules log to something other than the root logger. In most cases you'll want to use a logger based on the module name (e.g. in `MyCode.py`, you would do `LOGGER = logging.getLogger(__name__)`). Then you would need to adjust your `assertLogs` call to include the name of of the logger: `with self.assertLogs(logger=MyCode.__name__)` – jisrael18 Sep 30 '21 at 14:17