Configuring Python's logging Module with argparse

This post assumes you're familiar with and using Python's logging module to log info from your script. If you don't care about how it works skip to the end to see the code for the full solution.

Goal

Our goal is to control the log level via the command line using an additional argument --log_level that takes a one of the logging module's log levels.

$> script.py --log_level DEBUG

And it would be nice if the parsed variable returned by argparse.ArgumentParser.parse_args() could be used directly as an argument to logging.Logger.setLevel.

parsed_args = parser.parse_args()

root_logger = logging.getLogger()
root_logger.setLevel(parsed_args.log_level)

Implementation

To start with we'll need a list of all the possible log levels. Unfortunately there isn't a nice way of gathering all the log levels programmatically. We could try and capture all constants (everything in the logging module with all capital letters) but that would capture some extra constants that we don't want (logging.BASIC_FORMAT, logging.WARN, etc.). So instead of that let's start by manually listing the log levels.

_LOG_LEVEL_STRINGS = ['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG']

Next we'll need a function to convert from the log level string to an int that can be passed to logging.Logger.setLevel. This function will be the type= keyword argument when calling argparse.ArgumentParser.add_argument.

Let's check that the value received from the command line is one of the allowed values and then if it is we can retrieve its int value from the logging namespace using getattr. The extra assert checks that the logging library hasn't removed or altered the meanings of one of our expected log levels.

def _log_level_string_to_int(log_level_string):
    if not log_level_string in _LOG_LEVEL_STRINGS:
        message = 'invalid choice: {0} (choose from {1})'.format(log_level_string, _LOG_LEVEL_STRINGS)
        raise argparse.ArgumentTypeError(message)

    log_level_int = getattr(logging, log_level_string, logging.INFO)
    # check the logging log_level_choices have not changed from our expected values
    assert isinstance(log_level_int, int)

    return log_level_int

Now all that's left to do is add our new argument to our ArgumentParser.

parser = argparse.ArgumentParser()

parser.add_argument('--log-level',
                    default='INFO',
                    dest='log_level',
                    type=_log_level_string_to_int,
                    nargs='?',
                    help='Set the logging output level. {0}'.format(_LOG_LEVEL_STRINGS))

Full solution

Here is our final solution all together.

import argparse
import logging

_LOG_LEVEL_STRINGS = ['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG']

def _log_level_string_to_int(log_level_string):
    if not log_level_string in _LOG_LEVEL_STRINGS:
        message = 'invalid choice: {0} (choose from {1})'.format(log_level_string, _LOG_LEVEL_STRINGS)
        raise argparse.ArgumentTypeError(message)

    log_level_int = getattr(logging, log_level_string, logging.INFO)
    # check the logging log_level_choices have not changed from our expected values
    assert isinstance(log_level_int, int)

    return log_level_int

def main():
    parser = argparse.ArgumentParser()

    parser.add_argument('--log-level',
                        default='INFO',
                        dest='log_level',
                        type=_log_level_string_to_int,
                        nargs='?',
                        help='Set the logging output level. {0}'.format(_LOG_LEVEL_STRINGS))

    parsed_args = parser.parse_args()

    root_logger = logging.getLogger()
    root_logger.setLevel(parsed_args.log_level)

if __name__ == '__main__':
    main()