0

I am using pytest and I wish to log some log output that will be captured and displayed should a test fail.

To do this, I wish to add a new log level to the default pytest logger. My rationale for this is that my application outputs to the INFO level anyway, so I wish to avoid capturing those logs during the test. For that reason, I cannot simply change the log level to INFO.

Using the answers to this question I have derived the following solution, but it is not working. The error I see appears to be noted in a comment on accepted answer to the question, but there is no response.

AttributeError: module 'logging' has no attribute 'test_info'

How can I correctly add a custom logging level?

Using pytest fixture and tests

import logging
import pytest


@pytest.fixture(scope='session', autouse=True)
def logging_format(level_name: str='TEST_INFO', level_number: int=31):

    def log_test_info(self, message, *args, **kwargs):
        if self.isEnabledFor(level_number):
            self._log(level_number, message, args, **kwargs)

    logging.addLevelName(level_number, level_name)
    setattr(logging, level_name, level_number)
    setattr(logging.getLoggerClass(), level_name.lower(), log_test_info)


def test():
    logging.test_info('Some log data.')
    assert True

Not using pytest fixture and tests

To be sure this is not a pytest issue, I it's also possible to replicate the issue without using it.

import logging

level_name = 'TEST_INFO'
level_number = 31

def log_test_info(self, message, *args, **kwargs):
    if self.isEnabledFor(level_number):
        self._log(level_number, message, args, **kwargs)

logging.addLevelName(level_number, level_name)
setattr(logging, level_name, level_number)
setattr(logging.getLoggerClass(), level_name.lower(), log_test_info)

logging.test_info('Some log data.')
assert True
David Gard
  • 11,225
  • 36
  • 115
  • 227
  • In your example, you are adding the constant TEST_INFO to the logging module but adding the log_test_info function to the logger class. You need to use `logging.getLogger()` to get an instance of the root logger that you can call use to call `test_info`, alternatively, use `logging.log(level_number, "Some log data.")` – Tim Jun 06 '22 at 13:19

1 Answers1

2

In this code there is a misunderstanding between a logger instance and the logger module.

To be able to call the test_log method in the original code you need to obtain a logger instance eg:

# After the existing code

logger = logging.getLogger()
logger.test_info("Some log data.")

As you have added the test_info method to the loggerClass it will be available to the logger instance.

To have the method available on the logging module the function needs to be created for that purpose and added to the module:

# After the existing code

def module_test_info(message, *args, **kwargs):
    logging.log(level_number, message, *args, **kwargs)

setattr(logging, level_name.lower(), module_test_info)

This will add the module_test_info function to the logging module with the name test_info you will be able to call it as such logging.test_level("Some log data.")

Tim
  • 2,510
  • 1
  • 22
  • 26
  • Thank you for your explanation here and your comment on my question. You are correct, I was misunderstanding the intricacies of the module vs the instance. I have added the new level as in my question and then implemented `logging.log(LEVEL, 'msg')` as that is more than sufficient for my use case. – David Gard Jun 06 '22 at 13:42