What Is Pytest Caplog? (Everything You Need To Know)

While developing software, keeping track of events is crucial.

Logging serves as a means of tracking events to catch software bugs as they happen and learn more about the use of your code.

However, as your test suites grow in complexity, so does the need for effective logging to diagnose issues and maintain a clear understanding of test execution.

So, how can you optimize Pytest Logging?

In our quest for robust logging, Pytest Caplog emerges as a valuable tool. Caplog is a Pytest feature that facilitates the capture and analysis of log messages during test execution.

This guide aims to empower you with the knowledge and skills needed to leverage Pytest Caplog effectively.

We will explore Pytest Caplog, understand its features, and learn how it can be integrated into test suites to enhance visibility.

From capturing and analyzing log messages to customizing logging configurations, this guide will equip you with practical strategies to streamline the debugging process in your Python projects.

Let us dive right in!

Link To Example Code

What You’ll Learn

By the end of this tutorial, you will

  • gain a deep understanding of Pytest Caplog, and how it helps capture and analyze log messages during test execution.
  • learn strategies to improve the visibility of your test results, making it easier to streamline the debugging process.
  • understand how to make assertions on captured log messages during test execution.
  • embrace best practices for effective test logging using Pytest caplog
  • explore the differences between Pytest caplog and Pytest capsys, and when to use each.

What is Pytest Logging?

Pytest Logging is systematic way to capture, manage, and analyze log messages during test execution.

Simply put, logging involves recording information about the runtime behavior of code, allowing you to gain insights into the flow of your application.

Logging helps understand your application’s behavior, especially when things go wrong.

For detailed insights into best practices and effective utilization of Pytest Logging, this comprehensive guide on Pytest Logging is an invaluable resource.

Now that you are well familiar with logging, let us look at how Pytest Caplog is used to manage and capture log messages!

What is Pytest Caplog?

Pytest Caplog is an inbuilt Pytest feature that helps you capture log messages emitted during test execution.

It organizes log messages in a structured manner, providing you with a clear overview of the order and context in which log entries occurred during a test run.

This structured approach simplifies the debugging process and enhances the overall visibility of test results.

Moreover, the caplog fixture allows you to make assertions on the captured log messages. This means that you now have a mechanism to validate that specific log entries were generated during the execution of a test.

One of the best features of the caplog fixture is that the log output can be isolated between tests.

This isolation is crucial for maintaining the integrity of test results, preventing interference between different tests and avoiding unintended side effects.

Logging Levels and Granularity

There’s going to be a lot of discussion ahead on log levels during testing. That leads us to the question, what are logging levels?

Logging levels represent the severity or importance of a log message, and they help categorize and filter log entries based on their criticality.

The different logging levels with the heirarchy are as follows:

  • DEBUG: Detailed information, typically of interest only when diagnosing problems.
  • INFO: Confirmation that things are working as expected.
  • WARNING: An indication that something unexpected happened, or indicative of some problem in the near future (e.g. ‘disk space low’). The software is still working as expected.
  • ERROR: Due to a more serious problem, the software has not been able to perform some function.
  • CRITICAL: A serious error, indicating that the program itself may be unable to continue running.
  • NOTSET: The lowest possible rank and is intended to turn off logging.

Learn more in this comprehensive guide on Logging.

As you read ahead, you will understand how caplog allows you to control the granularity of log output during testing.

This ensures that the right level of detail is captured to facilitate effective debugging without overwhelming the test output with unnecessary information.

Pytest caplog allows you to perform all sorts of assertions on the captured log messages, such as checking if a specific log message was generated, or if a log message was generated with a specific severity level.

Now that you have some background on logging levels, let’s look at how caplog fixture is used to capture log messages during test execution.

Project Set Up

Getting Started

Our repo has the following structure:

1
2
3
4
5
6
7
8
.
├── .gitignore
├── README.md
├── requirements.txt
└── tests
├── test_pytest_caplog_basic.py
├── test_pytest_caplog_methods.py
└── test_pytest_capsys.py

It is a simple repo with a few test files explaining the key concepts of pytest caplog.

Prerequisites

To achieve the above objectives, the following is recommended:

  • Basic knowledge of the Python programming language (Python 3.12)
  • Basics of logging and Pytest

To get started, clone the repo here.

Then create a virtual environment and install dependencies using a tool of your choice.

1
pip install -r requirements.txt

Pytest Caplog Example

Let’s look at a simple example to understand how the caplog fixture is used to capture log messages during test execution.

tests/test_pytest_caplog_basic.py

1
2
3
4
5
6
7
8
9
import logging

def greet(name):
logging.info(f"Hello, {name}!")

def test_greet_logs_correct_message(caplog):
caplog.set_level(logging.INFO)
greet("Alice")
assert "Hello, Alice!" in caplog.text

In this code snippet, we have defined a function called greet that logs a greeting message to the console.

Then, we have a test function called test_greet_logs_correct_message that calls the greet function and asserts that the correct log message is captured using the caplog fixture.

Running this, we get

1
2
3
4
5
6
7
8
9
10
$ pytest tests/test_pytest_caplog_basic.py -v -s
============================================================= test session starts ==============================================================
platform darwin -- Python 3.12.0, pytest-7.4.3, pluggy-1.3.0 -- /usr/local/anaconda3/envs/pytest_dev/bin/python
cachedir: .pytest_cache
rootdir: /Users/ericsda/PycharmProjects/Pytest-with-Eric/pytest-caplog-example
collected 1 item

tests/test_pytest_caplog_basic.py::test_greet_logs_correct_message PASSED

============================================================== 1 passed in 0.01s ===============================================================

Pytest Caplog Properties and Methods

Now that we’ve seen how this works, let’s look at the different properties of Pytest caplog and how we can customize the behavior.

According to the official documentation, the caplog fixture provides the following attributes and methods:

  • caplog.messages -> list of format-interpolated log messages
  • caplog.text -> string containing formatted log output
  • caplog.records -> list of logging.LogRecord instances
  • caplog.record_tuples -> list of (logger_name, level, message) tuples
  • caplog.clear() -> clear captured records and formatted log output string

Let’s break down each one of these and understand how they work.

caplog.messages

caplog.messages is a property that provides a list of all the log messages captured during a test.

This property offers a convenient way to access individual log messages without the need to parse a single string or delve into the details of LogRecord objects.

For example,

tests/test_pytest_caplog_methods.py

1
2
3
4
5
6
7
8
9
10
11
12
13
import logging

def function_that_logs():
logging.info("Starting process")
logging.warning("An issue occurred")
logging.info("Ending process")

def test_function_logs_caplog_messages(caplog):
"""Testing the caplog.messages attribute"""
caplog.set_level(logging.INFO) # Set the caplog level to INFO and above
function_that_logs()
assert caplog.messages == ["Starting process", "An issue occurred", "Ending process"]

Running the test, we get

1
2
3
4
5
6
7
8
9
10
$ pytest tests/test_pytest_caplog_methods.py::test_function_logs_caplog_messages -v -s
============================================================= test session starts ==============================================================
platform darwin -- Python 3.12.0, pytest-7.4.3, pluggy-1.3.0 -- /usr/local/anaconda3/envs/pytest_dev/bin/python
cachedir: .pytest_cache
rootdir: /Users/ericsda/PycharmProjects/Pytest-with-Eric/pytest-caplog-example
collected 1 item

tests/test_pytest_caplog_methods.py::test_function_logs_caplog_messages PASSED

============================================================== 1 passed in 0.01s ===============================================================

caplog.messages is a straightforward, list-based way to access and assert against the log messages generated during your tests, enhancing the clarity and precision of your logging-related test assertions.

caplog.text

As the name suggests, the text attribute provides a convenient way to access the entire captured log output as a string during a test.

This attribute allows you to perform text-based assertions on the log messages generated during the execution of their tests.

tests/test_pytest_caplog_methods.py

1
2
3
4
5
6
7
def test_function_logs_caplog_text(caplog):
"""Testing the caplog.text attribute"""
caplog.set_level(logging.INFO) # Set the caplog level to INFO and above
function_that_logs()
assert "Starting process" in caplog.text
assert "An issue occurred" in caplog.text
assert "Ending process" in caplog.text

Using caplog.text is particularly useful when the focus is on verifying the presence or absence of specific log messages or patterns in the log records.

Keep in mind that caplog.text provides a consolidated view of all log messages captured during the test, making it the best option for straightforward assertions on the overall log output.

caplog.record_tuples

You can use record_tuples if all you want to do is to ensure that certain messages have been logged under a given logger name with a given severity and message.

caplog.record_tuples is a list of tuples where each tuple corresponds to a single log record.

Each tuple contains information about the log record, such as the log level, logger name, and log message.

Here’s an example

tests/test_pytest_caplog_methods.py

1
2
3
4
5
6
7
8
9
def test_function_logs_caplog_record_tuples(caplog):
"""Testing the caplog.record_tuples attribute"""
caplog.set_level(logging.INFO) # Set the caplog level to INFO and above
function_that_logs()
assert caplog.record_tuples == [
("root", logging.INFO, "Starting process"),
("root", logging.WARNING, "An issue occurred"),
("root", logging.INFO, "Ending process"),
]

So, in the above code, the assert statement checks if the first log record in record_tuples matches the expected tuple comprising logger, log level and log message.

In this case, it verifies that the first log record has a log level of INFO, a logger name of root, and a log message of Starting process. Similarly for others.

Using record_tuples provides a structured way to inspect log records, allowing for more detailed and fine-grained assertions on log messages.

caplog.records()

caplog.records in Pytest provides a list of logging.LogRecord objects for each log message captured during a test.

Similar to caplog.record_tuples(), each LogRecord contains detailed information about a log event, like the log level, message, logger name, and more, offering a granular and comprehensive way to inspect and assert against log output.

tests/test_pytest_caplog_methods.py

1
2
3
4
5
6
7
8
def test_function_logs_caplog_records(caplog):
"""Testing the caplog.records attribute"""
caplog.set_level(logging.INFO) # Set the caplog level to INFO and above
function_that_logs()
assert len(caplog.records) == 3
assert caplog.records[0].message == "Starting process"
assert caplog.records[1].message == "An issue occurred"
assert caplog.records[2].message == "Ending process"

caplog.clear()

caplog.clear() basically means a clean slate for log records and captured log texts.

This method is particularly useful when you want to isolate log messages between different sections of a test or between individual tests.

Thus, ensuring that the log output does not carry over from one part of the test to another.

tests/test_pytest_caplog_methods.py

1
2
3
4
5
6
7
def test_function_logs_caplog_clear(caplog):
"""Testing the caplog.clear() method"""
caplog.set_level(logging.INFO) # Set the caplog level to INFO and above
function_that_logs()
assert len(caplog.records) == 3
caplog.clear()
assert len(caplog.records) == 0

caplog.set_level(level, logger=name)

We’ve used the caplog.set_level command several times above. What is it?

The set_level method is used to dynamically set the logging level for a specific logger during a test.

This provides flexibility in controlling which log messages are captured based on their severity levels.

The method takes two parameters:

  • level: The logging level to set for the specified logger. This can be one of the standard Python logging levels such as logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, or logging.CRITICAL. If you want to dig deeper into Logging Levels, refer to this detailed guide

  • logger: (Optional) The name of the logger for which the logging level is being set. If not provided, the root logger is assumed.

    1
    2
    3
    4
    def test_logging_with_caplog_set_level(caplog):
    # Set the logging level for a specific logger
    caplog.set_level(logging.WARNING, logger='your_logger_name')
    # ...perform test actions...

This attribute provides fine-grained control over which log messages are included in the capture.

Note that the levels of the loggers changed by this function will be restored to their initial values at the end of the test.

caplog.at_level(level, logger=name)

In Pytest Caplog, the at_level method is used to filter and retrieve log records that match or exceed a specified logging level for a specific logger during a test.

This means that you can inspect and make assertions on log messages based on their severity levels. The method takes 2 parameters, which are exactly similar to set_level method’s parameter, as discussed above.

1
2
3
4
def test_logging_with_caplog_at_level(caplog):
# ...perform test actions...
errors = caplog.at_level(logging.ERROR)
assert len(errors) == 2

If we look at the above code snippet, the assert statement checks if there are exactly two log records with an error level.

Overall, all these methods discussed provide a foundation for interacting with the caplog fixture in Pytest.

Incorporate them into your test suite to gain better control over log output and to perform assertions based on logged messages during your tests.

Pytest Capsys

In Pytest, capsys is a built-in fixture that allows you to capture and test output printed to stdout and stderr in your tests. This is particularly useful when you want to verify what your code is printing out.

Here’s a simple example:

Suppose you have a function greet() that prints a greeting message:

tests/test_pytest_capsys.py

1
2
def greet(name):
print(f"Hello, {name}!")

You can use capsys to test the output of this function:

1
2
3
4
def test_greet(capsys):
greet("Alice")
captured = capsys.readouterr()
assert captured.out == "Hello, Alice!\n"

In this test, capsys.readouterr() captures the output of greet(“Alice”).

The captured.out contains the standard output (what was printed).

The test asserts that the output is exactly what we expect, verifying the behavior of the greet function. This way, capsys helps ensure your code’s output is correct.

1
2
3
4
5
6
7
8
9
10
$ pytest tests/test_pytest_capsys.py -v -s                                            
============================================================= test session starts ==============================================================
platform darwin -- Python 3.12.0, pytest-7.4.3, pluggy-1.3.0 -- /usr/local/anaconda3/envs/pytest_dev/bin/python
cachedir: .pytest_cache
rootdir: /Users/ericsda/PycharmProjects/Pytest-with-Eric/pytest-caplog-example
collected 1 item

tests/test_pytest_capsys.py::test_greet PASSED

============================================================== 1 passed in 0.01s ===============================================================

We’ll learn more about capsys in a subsequent dedicated article.

Pytest Capsys vs Pytest Caplog

Understanding the distinction between capsys and caplog in Pytest is crucial for effective testing in Python.

Capsys:

Purpose: capsys captures output to stdout and stderr during test execution. It’s ideal for testing what your program prints out.

Usage: Use capsys when you need to assert the contents of print statements or any output that your code sends to the standard output or error streams. It’s especially useful for CLI applications or scripts where output formatting and presentation are key aspects.

Caplog:

Purpose: caplog captures log messages created through Python’s logging framework during test execution.
Usage: Use caplog when your focus is on testing the logging in your application. It’s perfect for asserting the presence, level, and content of log messages. This is particularly valuable for applications where logging is used for debugging or monitoring purposes.

When to Use Which:

  • Use capsys when your test case revolves around the actual output to the console - what the user would see if they ran your program.
  • Use caplog when you want to test the internal logging of your application - what developers or sysadmins might review in logs to understand the application’s behavior or diagnose issues.

In summary, while both capsys and caplog are about capturing output, they serve distinct purposes. capsys is for testing user-facing output, and caplog is for testing internal logging.

The choice between capsys and caplog is not about which one is better overall; it depends on the testing context and what you are trying to achieve in your tests.

Pytest Caplog Best Practices

Let’s unwrap some tips and tricks to follow while using Pytest caplog for logging.

  • Clarity in Logging: Make your log messages clear and straightforward. They should guide the reader, not confuse them, making troubleshooting much easier
  • Leverage Caplog Assertions: Use Pytest caplog’s powerful assertion tools to ensure your logs are accurate and well-organized.
  • Decorate for Detail: Implement log decorators, such as @log_function_details, to highlight key parts of your code. This provides detailed logs that clearly convey your code’s intentions.
  • Start Fresh with Each Test: Use caplog.clear() to reset your logs at the beginning of each test, maintaining clean and independent log records. Even better, you can work with fixtures to automate this process via the setup/teardown mechanism.
  • Contextual Logging: When logging within a function or module, include context-specific information. For instance, in a database function, log the name of the table being accessed to provide clearer insights.
  • Collaborative Logging Standards: If you’re part of a team, establish and adhere to common logging practices. This unified approach makes understanding and analyzing logs a team effort.

Remember, effective logging is not just about capturing messages; it’s about crafting a narrative that helps you navigate the intricate paths of your code with confidence.

Conclusion

That’s all, folks!

In this guide, you uncovered the potential of Pytest Caplog as a versatile tool for effective logging within tests.

From crafting clear log messages to powerful assertions, Pytest Caplog is all you need to add clarity and visibility to your testing suites.

By adopting best practices, such as tailoring log levels, ensuring log output consistency, and utilizing smart log filtering, you can count on caplog fixture as a reliable testing ally.

With this knowledge, you’re now well-equipped to leverage Pytest Caplog to enhance the visibility of your test results and streamline the debugging process in your Python projects.

If you have ideas for improvement or would like me to cover anything specific, please send me a message via Twitter, GitHub or Email.

Till the next time… Cheers!

Additional Learning

Link to Example Code Repo
Official Docs - Pytest logging
How To Use Pytest Logging And Print To Console And File (A Comprehensive Guide)
What is Setup and Teardown in Pytest? (Importance of a Clean Test Environment)
How To Troubleshoot Pytest Caplog Fixture
Guide on Pytest Logging: Avoid Pitfalls!
Pytest Capsys