Reputation: 31304
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, I 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.
Upvotes: 17
Views: 10603
Reputation: 89
With Python 3.11, logging.getLevelNamesMapping()
was added, which allows to easily inquire the list of log level names:
>>> import logging
>>> logging.getLevelNamesMapping()
{'CRITICAL': 50, 'FATAL': 50, 'ERROR': 40, 'WARN': 30, 'WARNING': 30, 'INFO': 20, 'DEBUG': 10, 'NOTSET': 0}
>>> list(logging.getLevelNamesMapping())
['CRITICAL', 'FATAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET']
Custom levels are also included in the mapping:
>>> logging.addLevelName(5, "TRACE")
>>> logging.getLevelNamesMapping()["TRACE"]
5
Upvotes: 6
Reputation: 658
You should have a bad feeling about using internal variables, because they can and will change. Case in point, you can no longer access logging._levelNames
in python 3.
Per the docs the levels are more or less stated as constants, so you can just assume those ones are always available, and then any you add yourself are under your control anyway.
That said, it's a bit silly they don't have a simple method like logging.listLevels()
to list out all the named levels currently loaded. You should write them a strongly worded letter. Or feature request depending on how much you really care :)
And it should be strongly worded, because the changes is absurdly simple:
file: /usr/local/lib/python3.9/logging/__init__.py
def listLevelNames():
return sorted(_nameToLevel.keys())
If you wanted to get really fancy, you could even sort the names by level number.
edit: I made a topic in comp.lang.python. Don't worry, I was very polite.
Upvotes: 7
Reputation: 77137
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.
Upvotes: -1
Reputation: 711
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))
Upvotes: -1
Reputation: 711
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))
Upvotes: 0
Reputation: 12950
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
Upvotes: 5
Reputation: 216
As you are only reading values, logging._levelNames
looks an appropriate solution to me. Keep going with logging.addLevelName
for setting new values though.
Upvotes: 9
Reputation: 80061
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)]
Upvotes: 2