aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/misc/loggingTools.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools/misc/loggingTools.py')
-rw-r--r--Lib/fontTools/misc/loggingTools.py989
1 files changed, 498 insertions, 491 deletions
diff --git a/Lib/fontTools/misc/loggingTools.py b/Lib/fontTools/misc/loggingTools.py
index d1baa839..78704f5a 100644
--- a/Lib/fontTools/misc/loggingTools.py
+++ b/Lib/fontTools/misc/loggingTools.py
@@ -13,524 +13,531 @@ TIME_LEVEL = logging.DEBUG
# per-level format strings used by the default formatter
# (the level name is not printed for INFO and DEBUG messages)
DEFAULT_FORMATS = {
- "*": "%(levelname)s: %(message)s",
- "INFO": "%(message)s",
- "DEBUG": "%(message)s",
- }
+ "*": "%(levelname)s: %(message)s",
+ "INFO": "%(message)s",
+ "DEBUG": "%(message)s",
+}
class LevelFormatter(logging.Formatter):
- """Log formatter with level-specific formatting.
-
- Formatter class which optionally takes a dict of logging levels to
- format strings, allowing to customise the log records appearance for
- specific levels.
-
-
- Attributes:
- fmt: A dictionary mapping logging levels to format strings.
- The ``*`` key identifies the default format string.
- datefmt: As per py:class:`logging.Formatter`
- style: As per py:class:`logging.Formatter`
-
- >>> import sys
- >>> handler = logging.StreamHandler(sys.stdout)
- >>> formatter = LevelFormatter(
- ... fmt={
- ... '*': '[%(levelname)s] %(message)s',
- ... 'DEBUG': '%(name)s [%(levelname)s] %(message)s',
- ... 'INFO': '%(message)s',
- ... })
- >>> handler.setFormatter(formatter)
- >>> log = logging.getLogger('test')
- >>> log.setLevel(logging.DEBUG)
- >>> log.addHandler(handler)
- >>> log.debug('this uses a custom format string')
- test [DEBUG] this uses a custom format string
- >>> log.info('this also uses a custom format string')
- this also uses a custom format string
- >>> log.warning("this one uses the default format string")
- [WARNING] this one uses the default format string
- """
-
- def __init__(self, fmt=None, datefmt=None, style="%"):
- if style != '%':
- raise ValueError(
- "only '%' percent style is supported in both python 2 and 3")
- if fmt is None:
- fmt = DEFAULT_FORMATS
- if isinstance(fmt, str):
- default_format = fmt
- custom_formats = {}
- elif isinstance(fmt, Mapping):
- custom_formats = dict(fmt)
- default_format = custom_formats.pop("*", None)
- else:
- raise TypeError('fmt must be a str or a dict of str: %r' % fmt)
- super(LevelFormatter, self).__init__(default_format, datefmt)
- self.default_format = self._fmt
- self.custom_formats = {}
- for level, fmt in custom_formats.items():
- level = logging._checkLevel(level)
- self.custom_formats[level] = fmt
-
- def format(self, record):
- if self.custom_formats:
- fmt = self.custom_formats.get(record.levelno, self.default_format)
- if self._fmt != fmt:
- self._fmt = fmt
- # for python >= 3.2, _style needs to be set if _fmt changes
- if PercentStyle:
- self._style = PercentStyle(fmt)
- return super(LevelFormatter, self).format(record)
+ """Log formatter with level-specific formatting.
+
+ Formatter class which optionally takes a dict of logging levels to
+ format strings, allowing to customise the log records appearance for
+ specific levels.
+
+
+ Attributes:
+ fmt: A dictionary mapping logging levels to format strings.
+ The ``*`` key identifies the default format string.
+ datefmt: As per py:class:`logging.Formatter`
+ style: As per py:class:`logging.Formatter`
+
+ >>> import sys
+ >>> handler = logging.StreamHandler(sys.stdout)
+ >>> formatter = LevelFormatter(
+ ... fmt={
+ ... '*': '[%(levelname)s] %(message)s',
+ ... 'DEBUG': '%(name)s [%(levelname)s] %(message)s',
+ ... 'INFO': '%(message)s',
+ ... })
+ >>> handler.setFormatter(formatter)
+ >>> log = logging.getLogger('test')
+ >>> log.setLevel(logging.DEBUG)
+ >>> log.addHandler(handler)
+ >>> log.debug('this uses a custom format string')
+ test [DEBUG] this uses a custom format string
+ >>> log.info('this also uses a custom format string')
+ this also uses a custom format string
+ >>> log.warning("this one uses the default format string")
+ [WARNING] this one uses the default format string
+ """
+
+ def __init__(self, fmt=None, datefmt=None, style="%"):
+ if style != "%":
+ raise ValueError(
+ "only '%' percent style is supported in both python 2 and 3"
+ )
+ if fmt is None:
+ fmt = DEFAULT_FORMATS
+ if isinstance(fmt, str):
+ default_format = fmt
+ custom_formats = {}
+ elif isinstance(fmt, Mapping):
+ custom_formats = dict(fmt)
+ default_format = custom_formats.pop("*", None)
+ else:
+ raise TypeError("fmt must be a str or a dict of str: %r" % fmt)
+ super(LevelFormatter, self).__init__(default_format, datefmt)
+ self.default_format = self._fmt
+ self.custom_formats = {}
+ for level, fmt in custom_formats.items():
+ level = logging._checkLevel(level)
+ self.custom_formats[level] = fmt
+
+ def format(self, record):
+ if self.custom_formats:
+ fmt = self.custom_formats.get(record.levelno, self.default_format)
+ if self._fmt != fmt:
+ self._fmt = fmt
+ # for python >= 3.2, _style needs to be set if _fmt changes
+ if PercentStyle:
+ self._style = PercentStyle(fmt)
+ return super(LevelFormatter, self).format(record)
def configLogger(**kwargs):
- """A more sophisticated logging system configuation manager.
-
- This is more or less the same as :py:func:`logging.basicConfig`,
- with some additional options and defaults.
-
- The default behaviour is to create a ``StreamHandler`` which writes to
- sys.stderr, set a formatter using the ``DEFAULT_FORMATS`` strings, and add
- the handler to the top-level library logger ("fontTools").
-
- A number of optional keyword arguments may be specified, which can alter
- the default behaviour.
-
- Args:
-
- logger: Specifies the logger name or a Logger instance to be
- configured. (Defaults to "fontTools" logger). Unlike ``basicConfig``,
- this function can be called multiple times to reconfigure a logger.
- If the logger or any of its children already exists before the call is
- made, they will be reset before the new configuration is applied.
- filename: Specifies that a ``FileHandler`` be created, using the
- specified filename, rather than a ``StreamHandler``.
- filemode: Specifies the mode to open the file, if filename is
- specified. (If filemode is unspecified, it defaults to ``a``).
- format: Use the specified format string for the handler. This
- argument also accepts a dictionary of format strings keyed by
- level name, to allow customising the records appearance for
- specific levels. The special ``'*'`` key is for 'any other' level.
- datefmt: Use the specified date/time format.
- level: Set the logger level to the specified level.
- stream: Use the specified stream to initialize the StreamHandler. Note
- that this argument is incompatible with ``filename`` - if both
- are present, ``stream`` is ignored.
- handlers: If specified, this should be an iterable of already created
- handlers, which will be added to the logger. Any handler in the
- list which does not have a formatter assigned will be assigned the
- formatter created in this function.
- filters: If specified, this should be an iterable of already created
- filters. If the ``handlers`` do not already have filters assigned,
- these filters will be added to them.
- propagate: All loggers have a ``propagate`` attribute which determines
- whether to continue searching for handlers up the logging hierarchy.
- If not provided, the "propagate" attribute will be set to ``False``.
- """
- # using kwargs to enforce keyword-only arguments in py2.
- handlers = kwargs.pop("handlers", None)
- if handlers is None:
- if "stream" in kwargs and "filename" in kwargs:
- raise ValueError("'stream' and 'filename' should not be "
- "specified together")
- else:
- if "stream" in kwargs or "filename" in kwargs:
- raise ValueError("'stream' or 'filename' should not be "
- "specified together with 'handlers'")
- if handlers is None:
- filename = kwargs.pop("filename", None)
- mode = kwargs.pop("filemode", 'a')
- if filename:
- h = logging.FileHandler(filename, mode)
- else:
- stream = kwargs.pop("stream", None)
- h = logging.StreamHandler(stream)
- handlers = [h]
- # By default, the top-level library logger is configured.
- logger = kwargs.pop("logger", "fontTools")
- if not logger or isinstance(logger, str):
- # empty "" or None means the 'root' logger
- logger = logging.getLogger(logger)
- # before (re)configuring, reset named logger and its children (if exist)
- _resetExistingLoggers(parent=logger.name)
- # use DEFAULT_FORMATS if 'format' is None
- fs = kwargs.pop("format", None)
- dfs = kwargs.pop("datefmt", None)
- # XXX: '%' is the only format style supported on both py2 and 3
- style = kwargs.pop("style", '%')
- fmt = LevelFormatter(fs, dfs, style)
- filters = kwargs.pop("filters", [])
- for h in handlers:
- if h.formatter is None:
- h.setFormatter(fmt)
- if not h.filters:
- for f in filters:
- h.addFilter(f)
- logger.addHandler(h)
- if logger.name != "root":
- # stop searching up the hierarchy for handlers
- logger.propagate = kwargs.pop("propagate", False)
- # set a custom severity level
- level = kwargs.pop("level", None)
- if level is not None:
- logger.setLevel(level)
- if kwargs:
- keys = ', '.join(kwargs.keys())
- raise ValueError('Unrecognised argument(s): %s' % keys)
+ """A more sophisticated logging system configuation manager.
+
+ This is more or less the same as :py:func:`logging.basicConfig`,
+ with some additional options and defaults.
+
+ The default behaviour is to create a ``StreamHandler`` which writes to
+ sys.stderr, set a formatter using the ``DEFAULT_FORMATS`` strings, and add
+ the handler to the top-level library logger ("fontTools").
+
+ A number of optional keyword arguments may be specified, which can alter
+ the default behaviour.
+
+ Args:
+
+ logger: Specifies the logger name or a Logger instance to be
+ configured. (Defaults to "fontTools" logger). Unlike ``basicConfig``,
+ this function can be called multiple times to reconfigure a logger.
+ If the logger or any of its children already exists before the call is
+ made, they will be reset before the new configuration is applied.
+ filename: Specifies that a ``FileHandler`` be created, using the
+ specified filename, rather than a ``StreamHandler``.
+ filemode: Specifies the mode to open the file, if filename is
+ specified. (If filemode is unspecified, it defaults to ``a``).
+ format: Use the specified format string for the handler. This
+ argument also accepts a dictionary of format strings keyed by
+ level name, to allow customising the records appearance for
+ specific levels. The special ``'*'`` key is for 'any other' level.
+ datefmt: Use the specified date/time format.
+ level: Set the logger level to the specified level.
+ stream: Use the specified stream to initialize the StreamHandler. Note
+ that this argument is incompatible with ``filename`` - if both
+ are present, ``stream`` is ignored.
+ handlers: If specified, this should be an iterable of already created
+ handlers, which will be added to the logger. Any handler in the
+ list which does not have a formatter assigned will be assigned the
+ formatter created in this function.
+ filters: If specified, this should be an iterable of already created
+ filters. If the ``handlers`` do not already have filters assigned,
+ these filters will be added to them.
+ propagate: All loggers have a ``propagate`` attribute which determines
+ whether to continue searching for handlers up the logging hierarchy.
+ If not provided, the "propagate" attribute will be set to ``False``.
+ """
+ # using kwargs to enforce keyword-only arguments in py2.
+ handlers = kwargs.pop("handlers", None)
+ if handlers is None:
+ if "stream" in kwargs and "filename" in kwargs:
+ raise ValueError(
+ "'stream' and 'filename' should not be " "specified together"
+ )
+ else:
+ if "stream" in kwargs or "filename" in kwargs:
+ raise ValueError(
+ "'stream' or 'filename' should not be "
+ "specified together with 'handlers'"
+ )
+ if handlers is None:
+ filename = kwargs.pop("filename", None)
+ mode = kwargs.pop("filemode", "a")
+ if filename:
+ h = logging.FileHandler(filename, mode)
+ else:
+ stream = kwargs.pop("stream", None)
+ h = logging.StreamHandler(stream)
+ handlers = [h]
+ # By default, the top-level library logger is configured.
+ logger = kwargs.pop("logger", "fontTools")
+ if not logger or isinstance(logger, str):
+ # empty "" or None means the 'root' logger
+ logger = logging.getLogger(logger)
+ # before (re)configuring, reset named logger and its children (if exist)
+ _resetExistingLoggers(parent=logger.name)
+ # use DEFAULT_FORMATS if 'format' is None
+ fs = kwargs.pop("format", None)
+ dfs = kwargs.pop("datefmt", None)
+ # XXX: '%' is the only format style supported on both py2 and 3
+ style = kwargs.pop("style", "%")
+ fmt = LevelFormatter(fs, dfs, style)
+ filters = kwargs.pop("filters", [])
+ for h in handlers:
+ if h.formatter is None:
+ h.setFormatter(fmt)
+ if not h.filters:
+ for f in filters:
+ h.addFilter(f)
+ logger.addHandler(h)
+ if logger.name != "root":
+ # stop searching up the hierarchy for handlers
+ logger.propagate = kwargs.pop("propagate", False)
+ # set a custom severity level
+ level = kwargs.pop("level", None)
+ if level is not None:
+ logger.setLevel(level)
+ if kwargs:
+ keys = ", ".join(kwargs.keys())
+ raise ValueError("Unrecognised argument(s): %s" % keys)
def _resetExistingLoggers(parent="root"):
- """ Reset the logger named 'parent' and all its children to their initial
- state, if they already exist in the current configuration.
- """
- root = logging.root
- # get sorted list of all existing loggers
- existing = sorted(root.manager.loggerDict.keys())
- if parent == "root":
- # all the existing loggers are children of 'root'
- loggers_to_reset = [parent] + existing
- elif parent not in existing:
- # nothing to do
- return
- elif parent in existing:
- loggers_to_reset = [parent]
- # collect children, starting with the entry after parent name
- i = existing.index(parent) + 1
- prefixed = parent + "."
- pflen = len(prefixed)
- num_existing = len(existing)
- while i < num_existing:
- if existing[i][:pflen] == prefixed:
- loggers_to_reset.append(existing[i])
- i += 1
- for name in loggers_to_reset:
- if name == "root":
- root.setLevel(logging.WARNING)
- for h in root.handlers[:]:
- root.removeHandler(h)
- for f in root.filters[:]:
- root.removeFilters(f)
- root.disabled = False
- else:
- logger = root.manager.loggerDict[name]
- logger.level = logging.NOTSET
- logger.handlers = []
- logger.filters = []
- logger.propagate = True
- logger.disabled = False
+ """Reset the logger named 'parent' and all its children to their initial
+ state, if they already exist in the current configuration.
+ """
+ root = logging.root
+ # get sorted list of all existing loggers
+ existing = sorted(root.manager.loggerDict.keys())
+ if parent == "root":
+ # all the existing loggers are children of 'root'
+ loggers_to_reset = [parent] + existing
+ elif parent not in existing:
+ # nothing to do
+ return
+ elif parent in existing:
+ loggers_to_reset = [parent]
+ # collect children, starting with the entry after parent name
+ i = existing.index(parent) + 1
+ prefixed = parent + "."
+ pflen = len(prefixed)
+ num_existing = len(existing)
+ while i < num_existing:
+ if existing[i][:pflen] == prefixed:
+ loggers_to_reset.append(existing[i])
+ i += 1
+ for name in loggers_to_reset:
+ if name == "root":
+ root.setLevel(logging.WARNING)
+ for h in root.handlers[:]:
+ root.removeHandler(h)
+ for f in root.filters[:]:
+ root.removeFilters(f)
+ root.disabled = False
+ else:
+ logger = root.manager.loggerDict[name]
+ logger.level = logging.NOTSET
+ logger.handlers = []
+ logger.filters = []
+ logger.propagate = True
+ logger.disabled = False
class Timer(object):
- """ Keeps track of overall time and split/lap times.
-
- >>> import time
- >>> timer = Timer()
- >>> time.sleep(0.01)
- >>> print("First lap:", timer.split())
- First lap: ...
- >>> time.sleep(0.02)
- >>> print("Second lap:", timer.split())
- Second lap: ...
- >>> print("Overall time:", timer.time())
- Overall time: ...
-
- Can be used as a context manager inside with-statements.
-
- >>> with Timer() as t:
- ... time.sleep(0.01)
- >>> print("%0.3f seconds" % t.elapsed)
- 0... seconds
-
- If initialised with a logger, it can log the elapsed time automatically
- upon exiting the with-statement.
-
- >>> import logging
- >>> log = logging.getLogger("my-fancy-timer-logger")
- >>> configLogger(logger=log, level="DEBUG", format="%(message)s", stream=sys.stdout)
- >>> with Timer(log, 'do something'):
- ... time.sleep(0.01)
- Took ... to do something
-
- The same Timer instance, holding a reference to a logger, can be reused
- in multiple with-statements, optionally with different messages or levels.
-
- >>> timer = Timer(log)
- >>> with timer():
- ... time.sleep(0.01)
- elapsed time: ...s
- >>> with timer('redo it', level=logging.INFO):
- ... time.sleep(0.02)
- Took ... to redo it
-
- It can also be used as a function decorator to log the time elapsed to run
- the decorated function.
-
- >>> @timer()
- ... def test1():
- ... time.sleep(0.01)
- >>> @timer('run test 2', level=logging.INFO)
- ... def test2():
- ... time.sleep(0.02)
- >>> test1()
- Took ... to run 'test1'
- >>> test2()
- Took ... to run test 2
- """
-
- # timeit.default_timer choses the most accurate clock for each platform
- _time = timeit.default_timer
- default_msg = "elapsed time: %(time).3fs"
- default_format = "Took %(time).3fs to %(msg)s"
-
- def __init__(self, logger=None, msg=None, level=None, start=None):
- self.reset(start)
- if logger is None:
- for arg in ('msg', 'level'):
- if locals().get(arg) is not None:
- raise ValueError(
- "'%s' can't be specified without a 'logger'" % arg)
- self.logger = logger
- self.level = level if level is not None else TIME_LEVEL
- self.msg = msg
-
- def reset(self, start=None):
- """ Reset timer to 'start_time' or the current time. """
- if start is None:
- self.start = self._time()
- else:
- self.start = start
- self.last = self.start
- self.elapsed = 0.0
-
- def time(self):
- """ Return the overall time (in seconds) since the timer started. """
- return self._time() - self.start
-
- def split(self):
- """ Split and return the lap time (in seconds) in between splits. """
- current = self._time()
- self.elapsed = current - self.last
- self.last = current
- return self.elapsed
-
- def formatTime(self, msg, time):
- """ Format 'time' value in 'msg' and return formatted string.
- If 'msg' contains a '%(time)' format string, try to use that.
- Otherwise, use the predefined 'default_format'.
- If 'msg' is empty or None, fall back to 'default_msg'.
- """
- if not msg:
- msg = self.default_msg
- if msg.find("%(time)") < 0:
- msg = self.default_format % {"msg": msg, "time": time}
- else:
- try:
- msg = msg % {"time": time}
- except (KeyError, ValueError):
- pass # skip if the format string is malformed
- return msg
-
- def __enter__(self):
- """ Start a new lap """
- self.last = self._time()
- self.elapsed = 0.0
- return self
-
- def __exit__(self, exc_type, exc_value, traceback):
- """ End the current lap. If timer has a logger, log the time elapsed,
- using the format string in self.msg (or the default one).
- """
- time = self.split()
- if self.logger is None or exc_type:
- # if there's no logger attached, or if any exception occurred in
- # the with-statement, exit without logging the time
- return
- message = self.formatTime(self.msg, time)
- # Allow log handlers to see the individual parts to facilitate things
- # like a server accumulating aggregate stats.
- msg_parts = { 'msg': self.msg, 'time': time }
- self.logger.log(self.level, message, msg_parts)
-
- def __call__(self, func_or_msg=None, **kwargs):
- """ If the first argument is a function, return a decorator which runs
- the wrapped function inside Timer's context manager.
- Otherwise, treat the first argument as a 'msg' string and return an updated
- Timer instance, referencing the same logger.
- A 'level' keyword can also be passed to override self.level.
- """
- if isinstance(func_or_msg, Callable):
- func = func_or_msg
- # use the function name when no explicit 'msg' is provided
- if not self.msg:
- self.msg = "run '%s'" % func.__name__
-
- @wraps(func)
- def wrapper(*args, **kwds):
- with self:
- return func(*args, **kwds)
- return wrapper
- else:
- msg = func_or_msg or kwargs.get("msg")
- level = kwargs.get("level", self.level)
- return self.__class__(self.logger, msg, level)
-
- def __float__(self):
- return self.elapsed
-
- def __int__(self):
- return int(self.elapsed)
-
- def __str__(self):
- return "%.3f" % self.elapsed
+ """Keeps track of overall time and split/lap times.
+
+ >>> import time
+ >>> timer = Timer()
+ >>> time.sleep(0.01)
+ >>> print("First lap:", timer.split())
+ First lap: ...
+ >>> time.sleep(0.02)
+ >>> print("Second lap:", timer.split())
+ Second lap: ...
+ >>> print("Overall time:", timer.time())
+ Overall time: ...
+
+ Can be used as a context manager inside with-statements.
+
+ >>> with Timer() as t:
+ ... time.sleep(0.01)
+ >>> print("%0.3f seconds" % t.elapsed)
+ 0... seconds
+
+ If initialised with a logger, it can log the elapsed time automatically
+ upon exiting the with-statement.
+
+ >>> import logging
+ >>> log = logging.getLogger("my-fancy-timer-logger")
+ >>> configLogger(logger=log, level="DEBUG", format="%(message)s", stream=sys.stdout)
+ >>> with Timer(log, 'do something'):
+ ... time.sleep(0.01)
+ Took ... to do something
+
+ The same Timer instance, holding a reference to a logger, can be reused
+ in multiple with-statements, optionally with different messages or levels.
+
+ >>> timer = Timer(log)
+ >>> with timer():
+ ... time.sleep(0.01)
+ elapsed time: ...s
+ >>> with timer('redo it', level=logging.INFO):
+ ... time.sleep(0.02)
+ Took ... to redo it
+
+ It can also be used as a function decorator to log the time elapsed to run
+ the decorated function.
+
+ >>> @timer()
+ ... def test1():
+ ... time.sleep(0.01)
+ >>> @timer('run test 2', level=logging.INFO)
+ ... def test2():
+ ... time.sleep(0.02)
+ >>> test1()
+ Took ... to run 'test1'
+ >>> test2()
+ Took ... to run test 2
+ """
+
+ # timeit.default_timer choses the most accurate clock for each platform
+ _time = timeit.default_timer
+ default_msg = "elapsed time: %(time).3fs"
+ default_format = "Took %(time).3fs to %(msg)s"
+
+ def __init__(self, logger=None, msg=None, level=None, start=None):
+ self.reset(start)
+ if logger is None:
+ for arg in ("msg", "level"):
+ if locals().get(arg) is not None:
+ raise ValueError("'%s' can't be specified without a 'logger'" % arg)
+ self.logger = logger
+ self.level = level if level is not None else TIME_LEVEL
+ self.msg = msg
+
+ def reset(self, start=None):
+ """Reset timer to 'start_time' or the current time."""
+ if start is None:
+ self.start = self._time()
+ else:
+ self.start = start
+ self.last = self.start
+ self.elapsed = 0.0
+
+ def time(self):
+ """Return the overall time (in seconds) since the timer started."""
+ return self._time() - self.start
+
+ def split(self):
+ """Split and return the lap time (in seconds) in between splits."""
+ current = self._time()
+ self.elapsed = current - self.last
+ self.last = current
+ return self.elapsed
+
+ def formatTime(self, msg, time):
+ """Format 'time' value in 'msg' and return formatted string.
+ If 'msg' contains a '%(time)' format string, try to use that.
+ Otherwise, use the predefined 'default_format'.
+ If 'msg' is empty or None, fall back to 'default_msg'.
+ """
+ if not msg:
+ msg = self.default_msg
+ if msg.find("%(time)") < 0:
+ msg = self.default_format % {"msg": msg, "time": time}
+ else:
+ try:
+ msg = msg % {"time": time}
+ except (KeyError, ValueError):
+ pass # skip if the format string is malformed
+ return msg
+
+ def __enter__(self):
+ """Start a new lap"""
+ self.last = self._time()
+ self.elapsed = 0.0
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ """End the current lap. If timer has a logger, log the time elapsed,
+ using the format string in self.msg (or the default one).
+ """
+ time = self.split()
+ if self.logger is None or exc_type:
+ # if there's no logger attached, or if any exception occurred in
+ # the with-statement, exit without logging the time
+ return
+ message = self.formatTime(self.msg, time)
+ # Allow log handlers to see the individual parts to facilitate things
+ # like a server accumulating aggregate stats.
+ msg_parts = {"msg": self.msg, "time": time}
+ self.logger.log(self.level, message, msg_parts)
+
+ def __call__(self, func_or_msg=None, **kwargs):
+ """If the first argument is a function, return a decorator which runs
+ the wrapped function inside Timer's context manager.
+ Otherwise, treat the first argument as a 'msg' string and return an updated
+ Timer instance, referencing the same logger.
+ A 'level' keyword can also be passed to override self.level.
+ """
+ if isinstance(func_or_msg, Callable):
+ func = func_or_msg
+ # use the function name when no explicit 'msg' is provided
+ if not self.msg:
+ self.msg = "run '%s'" % func.__name__
+
+ @wraps(func)
+ def wrapper(*args, **kwds):
+ with self:
+ return func(*args, **kwds)
+
+ return wrapper
+ else:
+ msg = func_or_msg or kwargs.get("msg")
+ level = kwargs.get("level", self.level)
+ return self.__class__(self.logger, msg, level)
+
+ def __float__(self):
+ return self.elapsed
+
+ def __int__(self):
+ return int(self.elapsed)
+
+ def __str__(self):
+ return "%.3f" % self.elapsed
class ChannelsFilter(logging.Filter):
- """Provides a hierarchical filter for log entries based on channel names.
-
- Filters out records emitted from a list of enabled channel names,
- including their children. It works the same as the ``logging.Filter``
- class, but allows the user to specify multiple channel names.
-
- >>> import sys
- >>> handler = logging.StreamHandler(sys.stdout)
- >>> handler.setFormatter(logging.Formatter("%(message)s"))
- >>> filter = ChannelsFilter("A.B", "C.D")
- >>> handler.addFilter(filter)
- >>> root = logging.getLogger()
- >>> root.addHandler(handler)
- >>> root.setLevel(level=logging.DEBUG)
- >>> logging.getLogger('A.B').debug('this record passes through')
- this record passes through
- >>> logging.getLogger('A.B.C').debug('records from children also pass')
- records from children also pass
- >>> logging.getLogger('C.D').debug('this one as well')
- this one as well
- >>> logging.getLogger('A.B.').debug('also this one')
- also this one
- >>> logging.getLogger('A.F').debug('but this one does not!')
- >>> logging.getLogger('C.DE').debug('neither this one!')
- """
-
- def __init__(self, *names):
- self.names = names
- self.num = len(names)
- self.lengths = {n: len(n) for n in names}
-
- def filter(self, record):
- if self.num == 0:
- return True
- for name in self.names:
- nlen = self.lengths[name]
- if name == record.name:
- return True
- elif (record.name.find(name, 0, nlen) == 0
- and record.name[nlen] == "."):
- return True
- return False
+ """Provides a hierarchical filter for log entries based on channel names.
+
+ Filters out records emitted from a list of enabled channel names,
+ including their children. It works the same as the ``logging.Filter``
+ class, but allows the user to specify multiple channel names.
+
+ >>> import sys
+ >>> handler = logging.StreamHandler(sys.stdout)
+ >>> handler.setFormatter(logging.Formatter("%(message)s"))
+ >>> filter = ChannelsFilter("A.B", "C.D")
+ >>> handler.addFilter(filter)
+ >>> root = logging.getLogger()
+ >>> root.addHandler(handler)
+ >>> root.setLevel(level=logging.DEBUG)
+ >>> logging.getLogger('A.B').debug('this record passes through')
+ this record passes through
+ >>> logging.getLogger('A.B.C').debug('records from children also pass')
+ records from children also pass
+ >>> logging.getLogger('C.D').debug('this one as well')
+ this one as well
+ >>> logging.getLogger('A.B.').debug('also this one')
+ also this one
+ >>> logging.getLogger('A.F').debug('but this one does not!')
+ >>> logging.getLogger('C.DE').debug('neither this one!')
+ """
+
+ def __init__(self, *names):
+ self.names = names
+ self.num = len(names)
+ self.lengths = {n: len(n) for n in names}
+
+ def filter(self, record):
+ if self.num == 0:
+ return True
+ for name in self.names:
+ nlen = self.lengths[name]
+ if name == record.name:
+ return True
+ elif record.name.find(name, 0, nlen) == 0 and record.name[nlen] == ".":
+ return True
+ return False
class CapturingLogHandler(logging.Handler):
- def __init__(self, logger, level):
- super(CapturingLogHandler, self).__init__(level=level)
- self.records = []
- if isinstance(logger, str):
- self.logger = logging.getLogger(logger)
- else:
- self.logger = logger
-
- def __enter__(self):
- self.original_disabled = self.logger.disabled
- self.original_level = self.logger.level
- self.original_propagate = self.logger.propagate
-
- self.logger.addHandler(self)
- self.logger.setLevel(self.level)
- self.logger.disabled = False
- self.logger.propagate = False
-
- return self
-
- def __exit__(self, type, value, traceback):
- self.logger.removeHandler(self)
- self.logger.setLevel(self.original_level)
- self.logger.disabled = self.original_disabled
- self.logger.propagate = self.original_propagate
-
- return self
-
- def emit(self, record):
- self.records.append(record)
-
- def assertRegex(self, regexp, msg=None):
- import re
- pattern = re.compile(regexp)
- for r in self.records:
- if pattern.search(r.getMessage()):
- return True
- if msg is None:
- msg = "Pattern '%s' not found in logger records" % regexp
- assert 0, msg
+ def __init__(self, logger, level):
+ super(CapturingLogHandler, self).__init__(level=level)
+ self.records = []
+ if isinstance(logger, str):
+ self.logger = logging.getLogger(logger)
+ else:
+ self.logger = logger
+
+ def __enter__(self):
+ self.original_disabled = self.logger.disabled
+ self.original_level = self.logger.level
+ self.original_propagate = self.logger.propagate
+
+ self.logger.addHandler(self)
+ self.logger.setLevel(self.level)
+ self.logger.disabled = False
+ self.logger.propagate = False
+
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.logger.removeHandler(self)
+ self.logger.setLevel(self.original_level)
+ self.logger.disabled = self.original_disabled
+ self.logger.propagate = self.original_propagate
+
+ return self
+
+ def emit(self, record):
+ self.records.append(record)
+
+ def assertRegex(self, regexp, msg=None):
+ import re
+
+ pattern = re.compile(regexp)
+ for r in self.records:
+ if pattern.search(r.getMessage()):
+ return True
+ if msg is None:
+ msg = "Pattern '%s' not found in logger records" % regexp
+ assert 0, msg
class LogMixin(object):
- """ Mixin class that adds logging functionality to another class.
-
- You can define a new class that subclasses from ``LogMixin`` as well as
- other base classes through multiple inheritance.
- All instances of that class will have a ``log`` property that returns
- a ``logging.Logger`` named after their respective ``<module>.<class>``.
-
- For example:
-
- >>> class BaseClass(object):
- ... pass
- >>> class MyClass(LogMixin, BaseClass):
- ... pass
- >>> a = MyClass()
- >>> isinstance(a.log, logging.Logger)
- True
- >>> print(a.log.name)
- fontTools.misc.loggingTools.MyClass
- >>> class AnotherClass(MyClass):
- ... pass
- >>> b = AnotherClass()
- >>> isinstance(b.log, logging.Logger)
- True
- >>> print(b.log.name)
- fontTools.misc.loggingTools.AnotherClass
- """
-
- @property
- def log(self):
- if not hasattr(self, "_log"):
- name = ".".join(
- (self.__class__.__module__, self.__class__.__name__)
- )
- self._log = logging.getLogger(name)
- return self._log
+ """Mixin class that adds logging functionality to another class.
+
+ You can define a new class that subclasses from ``LogMixin`` as well as
+ other base classes through multiple inheritance.
+ All instances of that class will have a ``log`` property that returns
+ a ``logging.Logger`` named after their respective ``<module>.<class>``.
+
+ For example:
+
+ >>> class BaseClass(object):
+ ... pass
+ >>> class MyClass(LogMixin, BaseClass):
+ ... pass
+ >>> a = MyClass()
+ >>> isinstance(a.log, logging.Logger)
+ True
+ >>> print(a.log.name)
+ fontTools.misc.loggingTools.MyClass
+ >>> class AnotherClass(MyClass):
+ ... pass
+ >>> b = AnotherClass()
+ >>> isinstance(b.log, logging.Logger)
+ True
+ >>> print(b.log.name)
+ fontTools.misc.loggingTools.AnotherClass
+ """
+
+ @property
+ def log(self):
+ if not hasattr(self, "_log"):
+ name = ".".join((self.__class__.__module__, self.__class__.__name__))
+ self._log = logging.getLogger(name)
+ return self._log
def deprecateArgument(name, msg, category=UserWarning):
- """ Raise a warning about deprecated function argument 'name'. """
- warnings.warn(
- "%r is deprecated; %s" % (name, msg), category=category, stacklevel=3)
+ """Raise a warning about deprecated function argument 'name'."""
+ warnings.warn("%r is deprecated; %s" % (name, msg), category=category, stacklevel=3)
def deprecateFunction(msg, category=UserWarning):
- """ Decorator to raise a warning when a deprecated function is called. """
- def decorator(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- warnings.warn(
- "%r is deprecated; %s" % (func.__name__, msg),
- category=category, stacklevel=2)
- return func(*args, **kwargs)
- return wrapper
- return decorator
+ """Decorator to raise a warning when a deprecated function is called."""
+
+ def decorator(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ warnings.warn(
+ "%r is deprecated; %s" % (func.__name__, msg),
+ category=category,
+ stacklevel=2,
+ )
+ return func(*args, **kwargs)
+
+ return wrapper
+
+ return decorator
if __name__ == "__main__":
- import doctest
- sys.exit(doctest.testmod(optionflags=doctest.ELLIPSIS).failed)
+ import doctest
+
+ sys.exit(doctest.testmod(optionflags=doctest.ELLIPSIS).failed)