get list of named loglevels

2020-07-10 07:27发布

问题:

In my application, I'm using python.logging for logging.

Now I want to control the loglevel interactively, so i created a combobox hat lets the user select "ERROR", "WARN", "INFO",...

What I don't really like is that currently the values in the combobox are hardcoded. Instead,Ii would like to have a list of all "named" loglevels (e.g. both the system defaults, but also those added via logging.addLevelName; but not the fake generated loglevels like "Level 42")

The best I have come up with so far is to use the logging._levelNames dictionary.

But then this seems to be a private member, and I somehow have a bad feeling accessing it directly.

So my question is: what's the proper way to list all currently defined "named" loglevels in Python.

回答1:

As you are only reading values, logging._levelNames looks an appropriate solution to me. Keep going with logging.addLevelName for setting new values though.



回答2:

There is no specific function to do what you want, but you have everything you need with logging._levelNames.

Take a look at the addLevelName definition for example:

def addLevelName(level, levelName):
    """
    Associate 'levelName' with 'level'.

    This is used when converting levels to text during message formatting.
    """
    _acquireLock()
    try:    #unlikely to cause an exception, but you never know...
        _levelNames[level] = levelName
        _levelNames[levelName] = level
    finally:
        _releaseLock()

So a getLevelNames() could be implemented like this:

import logging
def getLevelNames():
    for k, v in sorted(logging._levelNames.iteritems()):
        if isinstance(v, basestring):
            yield v, k

import pprint
pprint.pprint(list(getLevelNames()))

Example output:

[('NOTSET', 0),
 ('DEBUG', 10),
 ('INFO', 20),
 ('WARNING', 30),
 ('ERROR', 40),
 ('CRITICAL', 50)]


回答3:

I just had the same problem while writing the help string of a program that accepts the name of the log level on the command line. As _levelNames is a private member that was renamed to _levelToName in Python 3.4 and thus cannot/should not get used, I came up with this solution (simplified for SO):

print "Levelnames: {}".format(", ".join(
    logging.getLevelName(x)
    for x in xrange(1, 101)
    if not logging.getLevelName(x).startswith('Level')))

or, for Python 3.x:

print("Levelnames: {}".format(", ".join(
    logging.getLevelName(x)
    for x in range(1, 101)
    if not logging.getLevelName(x).startswith('Level'))))

It's not entirely pretty, but it seems portable across all Python versions, and it prints out the level names in ascending order:

Levelnames: DEBUG, INFO, WARNING, ERROR, CRITICAL


回答4:

What about something like this. Note Python 3.4 produces a slightly different dictionary than Python 2, but you can modify the function or the resultant dictionary to work around that if required.

import logging
import copy

def get_logging_level_names():

    #! find logging's internal dictionary for level names.
    #! the internal dict name changed in python 3.4.
    try:
        level_names = logging._levelToName
    except AttributeError:
        level_names = logging._levelNames

    #! return a copy to prevent modification logging's local dict.
    return copy.copy(level_names)

level_names = get_logging_level_names()
print('level_names = {!r}'.format(level_names))


回答5:

Actually, this might be better than my previous answer. It returns a sorted list of level names. You can modify to return a sorted list (by value) of dict pairs {val:name} or {name:val} if you prefer.

import logging

def get_logging_level_names():

    #! find logging's internal dictionary for level names.
    #! the internal dict name changed in python 3.4.
    try:
        level_to_name = logging._levelToName
        level_vals = level_to_name.keys()
    except AttributeError:
        level_to_name = logging._levelNames
        level_vals = [ key for key in level_to_name.keys() if isinstance(key,int) ]

    level_vals = sorted(level_vals)
    level_names = [ level_to_name[val] for val in level_vals ]
    return level_names

level_names = get_logging_level_names()
print('level_names = {!r}'.format(level_names))


回答6:

I too am dismayed that the module doesn't offer a public attr or method for this. Here's a terse solution that works in Python 2 and 3:

import logging
LOG_LEVEL_NAMES = [logging.getLevelName(v) for v in
                   sorted(getattr(logging, '_levelToName', None)
                          or logging._levelNames)
                   if getattr(v, "real", 0)]

Breakdown: The module name (logging) did not change, so we can avoid a try/except around an ImportError for py2/3 compatibility by using getattr(logging, '_levelToName', None) to get the Python 3 value, if possible.

In Python 2 we get logging._levelNames, which is slightly different -- it has both int->string and string->int mappings, so I use getattr(v, "real", 0) to ensure that the filter treats all the string values as 0. We also throw out logging.NOTSET this way.

Then we sort the values and map logging.getLevelName to get back to an ordered list of names.