sridhar249
sridhar249

Reputation: 5061

pytest implementing a logfile per test method

I would like to create a separate log file for each test method. And i would like to do this in the conftest.py file and pass the logfile instance to the test method. This way, whenever i log something in a test method it would log to a separate log file and will be very easy to analyse.

I tried the following. Inside conftest.py file i added this:

logs_dir = pkg_resources.resource_filename("test_results", "logs")
def pytest_runtest_setup(item):
    test_method_name = item.name
    testpath = item.parent.name.strip('.py')
    path = '%s/%s' % (logs_dir, testpath)
    if not os.path.exists(path):
        os.makedirs(path)
    log = logger.make_logger(test_method_name, path) # Make logger takes care of creating the logfile and returns the python logging object.

The problem here is that pytest_runtest_setup does not have the ability to return anything to the test method. Atleast, i am not aware of it.

So, i thought of creating a fixture method inside the conftest.py file with scope="function" and call this fixture from the test methods. But, the fixture method does not know about the the Pytest.Item object. In case of pytest_runtest_setup method, it receives the item parameter and using that we are able to find out the test method name and test method path.

Please help!

Upvotes: 4

Views: 4892

Answers (3)

Andreu Gimenez
Andreu Gimenez

Reputation: 541

I found this solution by researching further upon webh's answer. I tried to use pytest-logger but their file structure is very rigid and it was not really useful for me. I found this code working without any plugin. It is based on set_log_path, which is an experimental feature.

Pytest 6.1.1 and Python 3.8.4

# conftest.py

# Required modules
import pytest
from pathlib import Path

# Configure logging
@pytest.hookimpl(hookwrapper=True,tryfirst=True)
def pytest_runtest_setup(item):
    config=item.config
    logging_plugin=config.pluginmanager.get_plugin("logging-plugin")
    filename=Path('pytest-logs', item._request.node.name+".log")
    logging_plugin.set_log_path(str(filename))
    yield

Notice that the use of Path can be substituted by os.path.join. Moreover, different tests can be set up in different folders and keep a record of all tests done historically by using a timestamp on the filename. One could use the following filename for example:

# conftest.py

# Required modules
import pytest
import datetime
from pathlib import Path

# Configure logging
@pytest.hookimpl(hookwrapper=True,tryfirst=True)
def pytest_runtest_setup(item):
   ...
   filename=Path(
      'pytest-logs',
       item._request.node.name,
       f"{datetime.datetime.now().strftime('%Y%m%dT%H%M%S')}.log"
       )
   ...

Additionally, if one would like to modify the log format, one can change it in pytest configuration file as described in the documentation.

# pytest.ini
[pytest]
log_file_level = INFO
log_file_format = %(name)s [%(levelname)s]: %(message)

My first stackoverflow answer!

Upvotes: 8

webh
webh

Reputation: 379

In newer pytest version this can be achieved with set_log_path.

@pytest.fixture
def manage_logs(request, autouse=True):
    """Set log file name same as test name"""

    request.config.pluginmanager.get_plugin("logging-plugin")\
        .set_log_path(os.path.join('log', request.node.name + '.log'))

Upvotes: 1

sridhar249
sridhar249

Reputation: 5061

I found the answer i was looking for. I was able to achieve it using the function scoped fixture like this:

@pytest.fixture(scope="function")
def log(request):
    test_path = request.node.parent.name.strip(".py")
    test_name = request.node.name
    node_id = request.node.nodeid
    log_file_path = '%s/%s' % (logs_dir, test_path)
    if not os.path.exists(log_file_path):
        os.makedirs(log_file_path)
    logger_obj = logger.make_logger(test_name, log_file_path, node_id)
    yield logger_obj
    handlers = logger_obj.handlers
    for handler in handlers:
        handler.close()
        logger_obj.removeHandler(handler)

Upvotes: 2

Related Questions