Mostly style improvements to logging.py

Our changes to the built-in logging module of Python are kind of a hack designed
to be as lightweight as possible way to replace the built-in logging module with
one that operates using newer str.format based string expansion.  It's not
really complete we probably should change that at some point.

Changes include:

 - Docstrings, lots of docstrings
 - Type hinting
 - log is now LOG
 - pylint warnings disabled on things that will not change and are on purpose
 - StyleAdapter.log does not dedent msg anymore unless dedent=True is passed
   which hopefully should make it a little less DWIM.
This commit is contained in:
T. Joseph Carter 2017-07-18 09:17:31 -07:00
parent db6c481ad6
commit 8212c2f848
1 changed files with 74 additions and 16 deletions

View File

@ -16,37 +16,95 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""BlocksFree LoggingAdapter using str.format and textwrap.dedent
Traditionally Python has used a % operator on strings to perform a somewhat
printf-like string formatting operation. It has limitations and str.format was
made to replace it. The old way isn't deprecated yet, and it won't be removed
any time soon, but str.format is how new code should work.
The issue is that Python's logging module is used by old and new code alike,
and so calls to the basic logger require the use of %-style format strings in
order to work with new and old code alike, forcing you to use the less flexible
"old way".
There isn't a perfectly clean solution to this--either you need ugliness in
each logging call, or you need a bit different ugliness up front to hide the
mess.
Details are at the bottom of this section of the Logging Cookbook:
https://docs.python.org/howto/logging-cookbook.html\
#use-of-alternative-formatting-styles
The next issue was that multi-line strings either break code indentation flow,
or they have to deal with indentation. If you don't have a special case for
the first line, textwrap.dedent works great for that. In order to avoid the
special case, just use a line continuation immediately after your opening
quotes. Another imperfect solution, but it does the job.
"""
import sys
import logging
import textwrap
from typing import List, Dict
### LOGGING
# *sigh* No clean/simple way to use str.format() type log strings without
# jumping through a few hoops
# pylint: disable=too-few-public-methods,missing-docstring
class Message(object):
def __init__(self, fmt, args):
def __init__(self, fmt: str, args: List) -> None:
self.fmt = fmt
self.args = args
def __str__(self):
def __str__(self) -> str:
return self.fmt.format(*self.args)
# pylint: enable=too-few-public-methods,missing-docstring
class StyleAdapter(logging.LoggerAdapter):
def __init__(self, logger, extra=None):
"""Return a LoggerAdapter that uses str.format expansions in log messages
StyleAdapter wraps the standard logger (e.g. logging.getLogger) so that it
appears to take str.format strings rather than the classic str % tuple
strings.
Args:
logger: A logging.Logger instance/logging channel
extra: A context object (see the Python logging cookbook)
"""
def __init__(self, logger: logging.Logger, extra=None) -> None:
super(StyleAdapter, self).__init__(logger, extra or {})
def log(self, level, msg, *args, **kwargs):
def log(self, level: int, msg: str, *args: List, **kwargs: Dict) -> None:
"""Logs msg.format(*args) at the given level
Effectively functions as if we were subclassing logging.Logger's log
method to change arg convention from msg % args to msg.format(*args).
See the documentation for the standard logging module for more info.
Additionally can perform textwrap.dedent() on msg before logging if
dedent=True.
Args:
level: Integer logging level
msg: A log message in the form of a str.format format string
dedent: Whether to dedent format string
args/kwargs: Positional and keyword arguments passed to _log
"""
if self.isEnabledFor(level):
msg, kwargs = self.process(textwrap.dedent(msg), kwargs)
if kwargs.get('dedent', False):
msg = textwrap.dedent(msg)
msg, kwargs = self.process(msg, kwargs)
# pylint: disable=protected-access
self.logger._log(level, Message(str(msg), args), (), **kwargs)
# pylint: enable=protected-access
log = StyleAdapter(logging.getLogger(__name__))
LOG = StyleAdapter(logging.getLogger(__name__))
# Set up our logging facility
_handler = logging.StreamHandler(sys.stdout)
_formatter = logging.Formatter('%(message)s')
_handler.setFormatter(_formatter)
log.logger.addHandler(_handler)
log.setLevel(logging.DEBUG)
# FIXME(tjcarter): get rid of log, let caller handle where it's going
log = LOG
_HANDLER = logging.StreamHandler(sys.stdout)
_FORMATTER = logging.Formatter('{message}', style='{')
_HANDLER.setFormatter(_FORMATTER)
LOG.logger.addHandler(_HANDLER)
LOG.setLevel(logging.DEBUG)