2

A library that I use emits warnings and errors through the logging module (logging.Logger's warn() and error() methods). I would like to implement an option to turn the warnings into errors (i.e., fail on warnings).

Is there an easy way to achieve this?

From looking at the documentation, I cannot see a ready-made solution. I assume it is possible by adding a custom Handler object, but I am not sure how to do it "right". Any pointers?

4
  • Can you provide a link to the library's code if it is freely available? Commented Nov 21, 2017 at 14:55
  • @hoefling sorry, no, it's proprietary. Commented Nov 21, 2017 at 14:56
  • Please clarify which you want: 1) turn logging warnings into logging errors, or 2) turn both logging warnings and errors into exceptions raised. Commented Nov 21, 2017 at 15:45
  • @wim The former, but the latter would also be acceptable. Commented Nov 21, 2017 at 15:52

4 Answers 4

7

@hoefling's answer is close, but I would change it like so:

class LevelRaiser(logging.Filter):
    def filter(self, record):
        if record.levelno == logging.WARNING:
            record.levelno = logging.ERROR
            record.levelname = logging.getLevelName(logging.ERROR)
        return True

def configure_library_logging():
    library_root_logger = logging.getLogger(library.__name__)
    library_root_logger.addFilter(LevelRaiser())

The reason is that filters are used to change LogRecord attributes and filter stuff out, whereas handlers are used to do I/O. What you're trying to do here isn't I/O, and so doesn't really belong in a handler.

Sign up to request clarification or add additional context in comments.

1 Comment

Indeed, this answer fits more in the logging concept than mine and should be the accepted one! I updated my answer with a reference.
3

Update: I like the proposal of Vinay made in this answer, injecting a custom Filter instead of a Handler is a much cleaner way. Please check it out!


You are on the right track with implementing own Handler. This is pretty easy to implement. I would do it like that: write a handler that edits the LogRecord in-place and attach one handler instance to the library's root loggers. Example:

# library.py

import logging


_LOGGER = logging.getLogger(__name__)


def library_stuff():
    _LOGGER.warning('library stuff')

This is a script that uses the library:

import logging
import library


class LevelRaiser(logging.Handler):

    def emit(self, record: logging.LogRecord):
        if record.levelno == logging.WARNING:
            record.levelno = logging.ERROR
            record.levelname = logging.getLevelName(logging.ERROR)


def configure_library_logging():
    library_root_logger = logging.getLogger(library.__name__)
    library_root_logger.addHandler(LevelRaiser())


if __name__ ==  '__main__':

    # do some example global logging config
    logging.basicConfig(level=logging.INFO)

    # additional configuration for the library logging
    configure_library_logging()

    # play with different loggers
    our_logger = logging.getLogger(__name__)
    root_logger = logging.getLogger()

    root_logger.warning('spam')
    our_logger.warning('eggs')

    library.library_stuff()

    root_logger.warning('foo')
    our_logger.warning('bar')

    library.library_stuff()

Run the script:

WARNING:root:spam
WARNING:__main__:eggs
ERROR:library:library stuff
WARNING:root:foo
WARNING:__main__:bar
ERROR:library:library stuff

Note that warning level is elevated to error level only on library's logging prints, all the rest remains unchanged.

Comments

0

You can assign logging.warn to logging.error before calling methods from your library:

import logging
warn_log_original = logging.warn
logging.warn = logging.error
library_call()
logging.warn = warn_log_original

3 Comments

This is dangerous as it will turn any warning into error, regardless who emitted it.
I have to agree that this is a bit of a hack, but it might actually work for me.
Updated my answer to make logger hack more isolated to library stuff. However @hoefling solution looks like a much better way to solve this problem :).
0

To use a specific example, I have used this pattern to catch .env parsing errors, by turning logging warnings into errors. Like this:

import warnings
import logging
import dotenv

class WarningHandler(logging.Handler):
    def emit(self, record):
        warnings.warn(record.getMessage())

# Treat dotenv logger warnings as real warnings
logging.getLogger("dotenv.main").addHandler(WarningHandler())

# Treat warnings as exceptions (which will halt the program)
warnings.filterwarnings("error")

# Try loading the `.env`
dotenv.load_dotenv()

If there is a parsing problem, then the program will halt as desired after a line like this:

Python-dotenv could not parse statement starting at line 109

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.