LCDict Features and Usage¶
LCDict subclasses LCDictBasic to contribute additional
conveniences. The class is fully documented in LCDict.
In this chapter we describe the features it adds:
- using formatter presets
- add_*_handler methods for several classes in logging.handlers
- optional automatic attaching of handlers to the root logger as they’re added
- easy multiprocessing-safe logging
- simplified creation and use of filters.
Using formatter presets¶
We’ve already seen simple examples of adding new formatters using
add_formatter. The documentation of that method in LCDictBasic
details its parameters and their possible values.
As our first example indicated,
often it’s not necessary to specify formatters from scratch,
because prelogging provides an extensible, modifiable collection of formatter
presets — predefined formatter specifications which cover many needs.
You can use the name of any of these presets as the formatter argument
to add_*_handler methods and to set_handler_formatter. prelogging ships
with about a dozen of them, shown in this table.
Formatter presets are added to an LCDict “just in time”, when they’re used:
>>> lcd = LCDict()
>>> # The underlying dict of a "blank" LCDict
>>> # is the same as that of a blank LCDictBasic --
>>> # lcd.formatters is empty:
>>> lcd.dump()
{'disable_existing_loggers': False,
'filters': {},
'formatters': {},
'handlers': {},
'incremental': False,
'loggers': {},
'root': {'handlers': [], 'level': 'WARNING'},
'version': 1}
>>> # Using the 'level_msg' preset adds it to lcd.formatters:
>>> _ = lcd.add_stderr_handler('console', formatter='level_msg')
>>> lcd.dump()
{'disable_existing_loggers': False,
'filters': {},
'formatters': {'level_msg': {'format': '%(levelname)-8s: %(message)s'}},
'handlers': {'console': {'class': 'logging.StreamHandler',
'formatter': 'level_msg',
'level': 'NOTSET',
'stream': 'ext://sys.stderr'}},
'incremental': False,
'loggers': {},
'root': {'handlers': [], 'level': 'WARNING'},
'version': 1}
Only 'level_msg' has been added to lcd.formatters.
Of course, the dozen or so formatter presets that prelogging contains, aren’t a comprehensive collection, and probably won’t meet everyone’s needs or suit everyone’s tastes. Therefore prelogging provides two functions that let you add your own presets, and/or modify existing ones:
update_formatter_presets_from_file(filename), andupdate_formatter_presets(multiline_str).
These functions, and the formats of their arguments, are described in the chapter Formatter Presets following this one.
Handler classes encapsulated by LCDict¶
logging defines more than a dozen handler classes — subclasses of
logging.Handler — in the modules logging and logging.handlers.
The package defines the basic stream, file and null handler classes,
for which LCDictBasic supplies add_*_handler methods. Its handlers
module defines more specialized handler classes, for about half of which (presently)
LCDict provides corresponding add_*_handler methods.
Handler classes that LCDict configures¶
LCDict provides methods for configuring these logging handler classes, with optional “locking” support in most cases:
method creates optionallocking?add_stream_handleradd_stderr_handleradd_stdout_handleradd_file_handleradd_rotating_file_handleradd_syslog_handleradd_email_handleradd_queue_handleradd_null_handlerStreamHandlerstderrStreamHandlerstdoutStreamHandlerFileHandlerRotatingFileHandlerSyslogHandlerSMTPHandlerQueueHandlerNullHandler yesyesyesyesyesyes
Adding other kinds of handlers¶
The following logging handler classes presently have no corresponding
add_*_handler methods:
- logging.handlers.WatchedFileHandler
- logging.handlers.TimedRotatingFileHandler
- logging.handlers.SocketHandler
- logging.handlers.DatagramHandler
- logging.handlers.MemoryHandler
- logging.handlers.NTEventLogHandler
- logging.handlers.HTTPHandler
Future versions of prelogging may supply methods for at least some of these.
In any case, all can be configured using prelogging. It’s straightforward to
write add_*_handler methods for any or all of these classes, on the model of
the existing methods: call add_handler with the appropriate handler class as
value of the class_ keyword, and pass any other class-specific key/value
pairs as keyword arguments.
Automatically attaching handlers to the root logger¶
Because handlers are so commonly attached to the root logger,
LCDict makes it easy to do that. Two parameters and their defaults
govern this:
The initializer method
LCDict.__init__has a boolean parameterattach_handlers_to_root[default:False].Each instance saves the value passed to its constructor, and exposes it as the read-only property
attach_handlers_to_root. Whenattach_handlers_to_rootis true, by default the handler-adding methods of this class automatically attach handlers to the root logger after adding them to thehandlerssubdictionary.
All
add_*_handlermethods called on anLCDict, as well as theclone_handlermethod, have anattach_to_rootparameter [type:boolorNone; default:None]. Theattach_to_rootparameter allows overriding of the valueattach_handlers_to_rootpassed to the constructor.The default value of
attach_to_rootisNone, which is interpreted to mean: use the value ofattach_handlers_to_rootpassed to the constructor. Ifattach_to_roothas any value other thanNone, the handler will be attached iffattach_to_rootis true/truthy.
Thus, if lcd is an LCDict created with attach_handlers_to_root=True,
lcd = LCDict(attach_handlers_to_root=True, ...)
you can still add a handler to lcd without attaching it to the root:
lcd.add_stdout_handler('stdout', attach_to_root=False, ...)
Similarly, if lcd`` is created with attach_handlers_to_root=False (the default),
lcd = LCDict(...)
you can attach a handler to the root as soon as you add it to lcd:
lcd.add_file_handler('fh', filename='myfile.log', attach_to_root=True, ...)
without having to subsequently call lcd.attach_root_handlers('fh', ...).
Easy multiprocessing-safe logging¶
As we’ve mentioned, most recently in the this chapter’s earlier section
Handler classes that LCDict configures,
prelogging provides multiprocessing-safe (“locking”) versions of the essential
handler classes that write to the console, streams, files, rotating files, and
syslog. These subclasses of handler classes defined by
logging are documented in Locking Handlers. The following LCDict
methods:
add_stream_handleradd_stderr_handleradd_stdout_handleradd_file_handleradd_rotating_file_handleradd_syslog_handler
can create either a standard, logging handler or a locking version thereof. Two keyword parameters and their defaults govern which type of handler will be created:
The initializer method
LCDict.__init__has a boolean parameterlocking[default:False].Each
LCDictinstance saves the value passed to its constructor, and exposes it as the read-only propertylocking. Whenlockingis true, by default theadd_*_handlermethods listed above will create locking handlers.
The
add_*_handlermethods listed above have alockingparameter [type:boolorNone; default:None], which allows overriding of the valuelockingpassed to the constructor.The default value of the
add_*_handlerparameterlockingisNone, which is interpreted to mean: use the value oflockingpassed to the constructor. If theadd_*_handlerparameterlockinghas any value other thanNone, a locking handler will be created iff the parameter’s value is true/truthy.
Simplified creation and use of filters¶
Filters allow finer control than mere loglevel comparison over which messages actually get logged.
There are two kinds of filters: class filters and callable filters.
LCDict provides a pair of convenience methods, add_class_filter and
add_callable_filter, which are easier to use than the lower-level
LCDictBasic method add_filter.
In Python 2, the logging module imposes a fussy requirement on callables
that can be used as filters, which the Python 3 implementation of logging
removes. The add_callable_filter method provides a single interface for
adding callable filters that works in both Python versions.
Defining filters¶
Here are a couple of examples of filters, both of which suppress certain kinds of messages. Each has the side effect of incrementing a distinct global variable.
Class filters¶
Classic filters are instances of any class that implement a filter method
with the following signature:
filter(self, record: logging.LogRecord) -> int
where int is treated like bool — nonzero means true (log the record),
zero means false (don’t). These include subclasses of logging.Filter, but
a filter class doesn’t have to inherit from that logging class.
Class filter example¶
_info_count = 0 # incremented by the following class filter
class CountInfoSquelchOdd():
def filter(self, record):
"""Suppress odd-numbered messages (records) whose level == INFO,
where the "first" message is the 0-th hence is even-numbered.
:param self: unused
:param record: logging.LogRecord
:return: int -- true (nonzero) ==> let record through,
false (0) ==> squelch
"""
global _info_count
if record.levelno == logging.INFO:
_info_count += 1
return _info_count % 2
else:
return True
Callable filters¶
A filter can also be a callable, of signature logging.LogRecord -> int.
(In fact, prelogging lets you use callables of signature
(logging.LogRecord, **kwargs) -> int; see the section below on
providing extra, static data to callable filters
for discussion and an example.)
Callable filter example¶
_debug_count = 0 # incremented by the following callable filter
def count_debug_allow_2(record):
"""
Allow at most 2 messages with loglevel ``DEBUG``.
:param record: ``logging.LogRecord``
:return: ``bool`` -- True ==> let record through, False ==> squelch
"""
global _debug_count
if record.levelno == logging.DEBUG:
_debug_count += 1
return _debug_count <= 2
else:
return True
Filters on the root logger¶
Let’s configure the root logger to use both filters shown above:
lcd = LCDict(
attach_handlers_to_root=True,
root_level='DEBUG')
lcd.add_stdout_handler(
'console',
level='DEBUG',
formatter='level_msg')
lcd.add_callable_filter('count_d', count_debug_allow_2)
lcd.add_class_filter('count_i', CountInfoSquelchOdd)
lcd.attach_root_filters('count_d', 'count_i')
lcd.config()
Now use the root logger:
import logging
root = logging.getLogger()
for i in range(5):
root.debug(str(i))
root.info(str(i))
print("_debug_count:", _debug_count)
print("_info_count:", _info_count)
This passage writes the following to stdout:
DEBUG : 0
INFO : 0
DEBUG : 1
INFO : 2
INFO : 4
_debug_count: 5
_info_count: 5
Note
This example is the test test_add_xxx_filter.py, with little
modification.
Filters on a non-root logger¶
Attaching the example filters to a non-root logger 'mylogger' requires just
one change: instead of using attach_root_filters to
attach the filters to the root logger, now we have to attach them to an
arbitrary logger. This can be accomplished in either of two ways:
Attach the filters when calling
add_loggerfor'mylogger', using thefilterskeyword parameter:lcd.add_logger('mylogger', filters=['count_d', 'count_i'], ... )
The value of the
filtersparameter can be either the name of a single filter (astr) or a sequence (list, tuple, etc.) of names of filters.
Add the logger with
add_logger, without using thefiltersparameter:lcd.add_logger('mylogger', ... )
and then attach filters to it with
attach_logger_filters:lcd.attach_logger_filters('mylogger', 'count_d', 'count_i')
Filters on a handler¶
There are two ways to attach filters to a handler:
Attach the filters in the same method call that adds the handler. Every
add_*_handlermethod takes afilterskeyword parameter — all those methods funnel throughLCDictBasic.add_handler. As with theadd_loggermethod, the value of thefiltersparameter can be either the name of a single filter (astr) or a sequence (list, tuple, etc.) of names of filters.For example, each of the following method calls adds a handler with only the
'count_d'filter attached:lcd.add_stderr_handler('con-err', filters='count_d' ).add_file_handler('fh', filename='some-logfile.log', filters=['count_d'])
For another example, the following statement adds a rotating file handler with both the
'count_i'and'count_d'filters attached:lcd.add_rotating_file_handler('rfh', filename='some-rotating-logfile.log', max_bytes=1024, backup_count=5, filters=['count_i', 'count_d'])
Add the handler using any
add_*_handlermethod, then useadd_handler_filtersto attach filters to the handler. For example:lcd.add_file_handler('myhandler', filename='mylogfile.log' ).attach_handler_filters('myhandler', 'count_d', 'count_i')
In a later chapter we’ll
discuss providing extra data to filters, in addition to the LogRecords
they’re called with.