def __init__(self, name_or_logger, fill_rate=None, capacity=None, verbosity=HINT): """Set up Logger-like object with some swailing goodness. name_or_logger is either a (possibly unicode) string or a logging.Logger-like object. If supplied as a string, we simply use logging.getLogger. fill_rate is the number of tokens per second Logger accumulates. Each log output consumes one token. capacity is the maximum number of tokens the Logger is allowed to have accumulated. verbosity is the initial verbosity level (defaults to HINT). Setting it to PRIMARY will stop detail and hint outputs. Setting it to DETAIL will stop hint outputs. Setting it to HINT allows all output. """ if fill_rate and capacity: self._tb = TokenBucket(fill_rate, capacity) else: self._tb = None self._tb_lock = threading.Lock() if isinstance(name_or_logger, basestring): self._logger = logging.getLogger(name_or_logger) else: self._logger = name_or_logger self._verbosity = verbosity
def __init__(self, name_or_logger, fill_rate=None, capacity=None, verbosity=HINT, structured_detail=False, with_prefix=True): """Set up Logger-like object with some swailing goodness. name_or_logger is either a (possibly unicode) string or a logging.Logger-like object. If supplied as a string, we simply use logging.getLogger. fill_rate is the number of tokens per second Logger accumulates. Each log output consumes one token. capacity is the maximum number of tokens the Logger is allowed to have accumulated. verbosity is the initial verbosity level (defaults to HINT). Setting it to PRIMARY will stop detail and hint outputs. Setting it to DETAIL will stop hint outputs. Setting it to HINT allows all output. When structured_detail is True, logger.detail(msg) expects msg to be a dict and json dumps it when logging. When with_prefix is True, logger.detail and logger.hint will prefix their output with "DETAIL: " and "HINT: ", respectively. """ if fill_rate and capacity: self._tb = TokenBucket(fill_rate, capacity) else: self._tb = None self._tb_lock = threading.Lock() if isinstance(name_or_logger, basestring): self._logger = logging.getLogger(name_or_logger) else: self._logger = name_or_logger self._verbosity = verbosity self._structured_detail = structured_detail self._with_prefix = with_prefix
class Logger(object): """A logging.Logger-like object with some swailing goodness. """ def __init__(self, name_or_logger, fill_rate=None, capacity=None, verbosity=HINT): """Set up Logger-like object with some swailing goodness. name_or_logger is either a (possibly unicode) string or a logging.Logger-like object. If supplied as a string, we simply use logging.getLogger. fill_rate is the number of tokens per second Logger accumulates. Each log output consumes one token. capacity is the maximum number of tokens the Logger is allowed to have accumulated. verbosity is the initial verbosity level (defaults to HINT). Setting it to PRIMARY will stop detail and hint outputs. Setting it to DETAIL will stop hint outputs. Setting it to HINT allows all output. """ if fill_rate and capacity: self._tb = TokenBucket(fill_rate, capacity) else: self._tb = None self._tb_lock = threading.Lock() if isinstance(name_or_logger, basestring): self._logger = logging.getLogger(name_or_logger) else: self._logger = name_or_logger self._verbosity = verbosity def debug(self, msg=None, *args, **kwargs): """Write log at DEBUG level. Same arguments as Python's built-in Logger. """ return self._log(logging.DEBUG, msg, args, kwargs) def info(self, msg=None, *args, **kwargs): """Similar to DEBUG but at INFO level.""" return self._log(logging.INFO, msg, args, kwargs) def warning(self, msg=None, *args, **kwargs): """Similar to DEBUG but at WARNING level.""" return self._log(logging.WARNING, msg, args, kwargs) def error(self, msg=None, *args, **kwargs): """Similar to DEBUG but at ERROR level.""" return self._log(logging.ERROR, msg, args, kwargs) def critical(self, msg=None, *args, **kwargs): """Similar to DEBUG but at CRITICAL level.""" return self._log(logging.CRITICAL, msg, args, kwargs) def log(self, level, msg=None, *args, **kwargs): """Writes log out at any arbitray level.""" return self._log(level, msg, args, kwargs) def set_verbosity(self, level): self._verbosity = level def _log(self, level, msg, args, kwargs): """Throttled log output.""" with self._tb_lock: if self._tb is None: throttled = 0 should_log = True else: throttled = self._tb.throttle_count should_log = self._tb.check_and_consume() if should_log: if throttled > 0: self._logger.log(level, "") self._logger.log( level, "(... throttled %d messages ...)", throttled, ) self._logger.log(level, "") if msg is not None: self._logger.log(level, msg, *args, **kwargs) return FancyLogContext(self._logger, level, self._verbosity) else: return NoopLogContext()
def test_throttle(self, clock): clock.return_value = 1 tb = TokenBucket(10, 100) # we get to consume 100 tokens... for i in xrange(100): self.assertTrue(tb.check_and_consume()) # ...then next one fails self.assertFalse(tb.check_and_consume()) # fill halfway and check level clock.return_value += 0.5 tb._fill() self.assertEqual(len(tb), 5) clock.return_value += 0.5 tb._fill() self.assertEqual(len(tb), 10) # this fill maxes out capacity clock.return_value += 10 tb._fill() self.assertEqual(len(tb), 100) # attempt to consume more than allowed then check throttle # count for i in xrange(123): tb.check_and_consume() self.assertEqual(tb.throttle_count, 23) # fill, consume, then check throttle count again clock.return_value += 1 tb.check_and_consume() self.assertEqual(tb.throttle_count, 0)
def test_simple(self, clock): clock.return_value = 1 tb = TokenBucket(10, 100) self.assertEqual(len(tb), 100) self.assertTrue(tb.check_and_consume())
class Logger(object): """A logging.Logger-like object with some swailing goodness. """ def __init__(self, name_or_logger, fill_rate=None, capacity=None, verbosity=HINT, structured_detail=False, with_prefix=True): """Set up Logger-like object with some swailing goodness. name_or_logger is either a (possibly unicode) string or a logging.Logger-like object. If supplied as a string, we simply use logging.getLogger. fill_rate is the number of tokens per second Logger accumulates. Each log output consumes one token. capacity is the maximum number of tokens the Logger is allowed to have accumulated. verbosity is the initial verbosity level (defaults to HINT). Setting it to PRIMARY will stop detail and hint outputs. Setting it to DETAIL will stop hint outputs. Setting it to HINT allows all output. When structured_detail is True, logger.detail(msg) expects msg to be a dict and json dumps it when logging. When with_prefix is True, logger.detail and logger.hint will prefix their output with "DETAIL: " and "HINT: ", respectively. """ if fill_rate and capacity: self._tb = TokenBucket(fill_rate, capacity) else: self._tb = None self._tb_lock = threading.Lock() if isinstance(name_or_logger, basestring): self._logger = logging.getLogger(name_or_logger) else: self._logger = name_or_logger self._verbosity = verbosity self._structured_detail = structured_detail self._with_prefix = with_prefix def debug(self, msg=None, *args, **kwargs): """Write log at DEBUG level. Same arguments as Python's built-in Logger. """ return self._log(logging.DEBUG, msg, args, kwargs) def info(self, msg=None, *args, **kwargs): """Similar to DEBUG but at INFO level.""" return self._log(logging.INFO, msg, args, kwargs) def warning(self, msg=None, *args, **kwargs): """Similar to DEBUG but at WARNING level.""" return self._log(logging.WARNING, msg, args, kwargs) def error(self, msg=None, *args, **kwargs): """Similar to DEBUG but at ERROR level.""" return self._log(logging.ERROR, msg, args, kwargs) def exception(self, msg=None, *args, **kwargs): """Similar to DEBUG but at ERROR level with exc_info set. https://github.com/python/cpython/blob/2.7/Lib/logging/__init__.py#L1472 """ kwargs['exc_info'] = 1 return self._log(logging.ERROR, msg, args, kwargs) def critical(self, msg=None, *args, **kwargs): """Similar to DEBUG but at CRITICAL level.""" return self._log(logging.CRITICAL, msg, args, kwargs) def log(self, level, msg=None, *args, **kwargs): """Writes log out at any arbitray level.""" return self._log(level, msg, args, kwargs) def set_verbosity(self, level): self._verbosity = level def _log(self, level, msg, args, kwargs): """Throttled log output.""" with self._tb_lock: if self._tb is None: throttled = 0 should_log = True else: throttled = self._tb.throttle_count should_log = self._tb.check_and_consume() if should_log: if throttled > 0: self._logger.log(level, "") self._logger.log( level, "(... throttled %d messages ...)", throttled, ) self._logger.log(level, "") if msg is not None: self._logger.log(level, msg, *args, **kwargs) return FancyLogContext(self._logger, level, self._verbosity, self._structured_detail, self._with_prefix) else: return NoopLogContext()