# vim: set tabstop=4 shiftwidth=4 noexpandtab filetype=python: # Copyright (C) 2017 T. Joseph Carter # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # You should have received a copy of the GNU General Public License along # 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 textwrap import logging # pylint: disable=unused-import from logging import ( CRITICAL, DEBUG, ERROR, FATAL, INFO, WARNING, Formatter, StreamHandler ) # For export # pylint: enable=unused-import from typing import List, Dict # pylint: disable=too-few-public-methods,missing-docstring class Message(object): def __init__(self, fmt: str, args: List) -> None: self.fmt = fmt self.args = args def __str__(self) -> str: return self.fmt.format(*self.args) # pylint: enable=too-few-public-methods,missing-docstring class StyleAdapter(logging.LoggerAdapter): """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: 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): 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('blocksfree'))