Log-based testing 🪵¶

logot makes it easy to test whether your code is logging correctly:

from logot import Logot, logged

def test_something(logot: Logot) -> None:
   do_something()
   logot.assert_logged(logged.info("Something was done"))

Note

logot integrates with popular testing frameworks (e.g. pytest, unittest). It supports many 3rd-party asynchronous and logging frameworks, and can be extended to support many more. 💪

Why test logging? 🤔¶

Good logging ensures your code is debuggable at runtime, but why bother actually testing your logs?

Sometimes, testing logs is the only reasonable way to known your code has actually run correctly! This is particularly the case in threaded or asynchronous code.

For example, imagine the following code running in a thread:

def poll_daemon(app: App) -> None:
   while not app.stopping:
      sleep(app.poll_interval)
      logger.debug("Poll started")
      try:
         app.data = app.get("http://is-everything-ok.com/")
      except HTTPError:
         logger.exception("Poll error")
      else:
         logger.debug("Poll finished")

Note

While it’s possible to rewrite this code in a way that can be tested without logot, that risks making the code less clear or more verbose. For complex threaded or asynchronous code, this can quickly become burdensome. 👎

Testing this code with logot is easy!

from logot import Logot, logged

def test_poll_daemon(logot: Logot) -> None:
   app.start_poll()
   for _ in range(3):
      logot.wait_for(logged.info("Poll started"))
      logot.wait_for(logged.info("Poll finished"))

Testing threaded code¶

Use Logot.wait_for() to pause your test until the expected logs arrive or the timeout expires:

from logot import Logot, logged

def test_app(logot: Logot) -> None:
   app.start()
   logot.wait_for(logged.info("App started"))

Note

Use the timeout argument to Logot.wait_for() to configure how long to wait before the test fails. This can be configured globally with the timeout argument to Logot, defaulting to Logot.DEFAULT_TIMEOUT.

See also

See Log pattern matching for examples of how to wait for logs that may arrive in an unpredictable order.

Testing asynchronous code¶

Use Logot.await_for() to pause your test until the expected logs arrive or the timeout expires:

from logot import Logot, logged

async def test_app(logot: Logot) -> None:
   asyncio.create_task(app.start())
   await logot.await_for(logged.info("App started"))

Note

Use the timeout argument to Logot.await_for() to configure how long to wait before the test fails. This can be configured globally with the timeout argument to Logot, defaulting to Logot.DEFAULT_TIMEOUT.

See also

See Log pattern matching for examples of how to wait for logs that may arrive in an unpredictable order.

See Asynchronous frameworks for other supported asynchronous frameworks.

Testing synchronous code¶

Use Logot.assert_logged() to fail immediately if the expected logs have not arrived:

from logot import Logot, logged

def test_something(logot: Logot) -> None:
   do_something()
   logot.assert_logged(logged.info("Something was done"))

Note

You can also use Logot.wait_for() to test for expected logs, but since this only fails after a timeout, using Logot.assert_logged() will give more immediate feedback if your test fails.

See also

Use Logot.assert_not_logged() to fail immediately if the expected logs do arrive.

Further reading¶

Learn more about logot with the following guides: