question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Pytest and caplog doesn't work if InterceptHandler exists

See original GitHub issue

I would like route all logs through loguru and configure them as I see fit, and for that I have created an InterceptHandler as descriped here. I also want to test the output of some of my logs via the caplog trick described here.

It seems that I can’t have both at the same time, as it creates a hanging loop.

Is there another way to test the contents of the logs when using an InterceptHandler ?

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:1
  • Comments:14 (7 by maintainers)

github_iconTop GitHub Comments

6reactions
Delgancommented, Sep 24, 2021

Thanks for the reproducible example, @mehd-io! It’s all clear now.

Basically, Loguru’s handlers are made thread-safe by using a Lock to protect concurrent access of sinks. While processing a log message, the handler first acquires the Lock and release it once the message is logged. If another message tries to be logged at the same time, it won’t be handled and will wait until the Lock is released.

You’re facing a dead-lock due to recursive call of logger.log(). It is very similar to the following scenario:

from loguru import logger

def my_custom_sink(message):
    logger.debug("Processing the message...")
    print(message, end="")

logger.add(my_custom_sink)

logger.info("Hanging...")

The program is hanging because the debug() call is waiting for the Lock acquired by info() to be released, which never happens because my_custom_sink() can’t terminate.

In your case, an InterceptHandler() is attached to the standard root logger. Every message logged using the standard logging library will also be processed by Loguru added handlers. That’s fine, but there’s also a PropagateHandler() which causes every Loguru message to be propagated to standard logging.

Consequently, when logger.info() is used, the Lock of the PropagateHandler() is acquired, then the log message is sent to the logger returned by logging.getLogger(record.name) which itself call the emit() function of the InterceptHandler(). The latter will tries to call logger.log() but will hang forever because the Lock is still not released.

If this wasn’t hanging you would face infinite recursion anyway.

I never realized it, but InterceptHandler and PropagateHandler don’t mix well.

The solution is to find a trick so that the InterceptHandler does not process the messages generated by the PropagateHandler. I can suggest the following updated handlers:

class PropogateHandler(logging.Handler):
        def emit(self, record):
            record.from_loguru = True
            logging.getLogger(record.name).handle(record)

class InterceptHandler(logging.Handler):
    def emit(self, record):
        # Ignore messages from PropagateHandler to avoid hanging
        if getattr(record, "from_loguru", False):
            return

        # Get corresponding Loguru level if it exists
        try:
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno

        # Find caller from where originated the logged message
        frame, depth = logging.currentframe(), 2
        while frame.f_code.co_filename == logging.__file__:
            frame = frame.f_back
            depth += 1

        logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())

I’m not entirely satisfied with this solution, but this is the best I’ve found so far.

2reactions
mehd-iocommented, Sep 24, 2021

I confirm that the workaround works 🎉 , thanks @Delgan for the awesome support & library 🙌

Read more comments on GitHub >

github_iconTop Results From Across the Web

Pytest caplog works with custom logger when created with a ...
I have been testing my custom loggers with Pytest. Created a custom logger from Yaml config file, and wrote the following test:
Read more >
loguru Documentation - Read the Docs
Loguru is a library which aims to bring enjoyable logging in Python. Did you ever feel lazy about configuring a logger and used...
Read more >
How to manage logging — pytest documentation
Setting log_level will set the level that is captured globally so if a specific test requires a lower level than this, use the...
Read more >
loguru Documentation
Loguru is a library which aims to bring enjoyable logging in Python. Did you ever feel lazy about configuring a logger and used...
Read more >
loguru.logger.add Example - Program Talk
Here are the examples of the python api loguru.logger.add taken from open source ... -things-work-with-pytest-and-caplog """ class PropogateHandler(logging.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found