# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['log_rate_limit']

package_data = \
{'': ['*']}

setup_kwargs = {
    'name': 'log-rate-limit',
    'version': '1.2.1',
    'description': "Limit excessive log output with Python's standard logging framework.",
    'long_description': '# log-rate-limit - limit excessive log output\n\n[![PyPI Version](https://img.shields.io/pypi/v/log-rate-limit)](https://pypi.org/project/log-rate-limit/)\n[![Build Status](https://github.com/samuller/log-rate-limit/actions/workflows/tests.yml/badge.svg)](https://github.com/samuller/log-rate-limit/actions/workflows/tests.yml)\n[![Code Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)](https://github.com/samuller/pgmerge/actions)\n[![Checked with mypy](https://img.shields.io/badge/mypy-strict-blue)](http://mypy-lang.org/)\n[![Formatted with black](https://img.shields.io/badge/code%20style-black-black)](https://black.readthedocs.io/en/stable/)\n\nA [logging filter](https://docs.python.org/3/library/logging.html#filter-objects) using Python\'s standard logging mechanisms to rate-limit logs - i.e. suppress logs when they are being output too fast.\n\nLog commands are grouped into separate **streams** that will each have their own rate limitation applied without affecting the logs in other streams. By default every log message is assigned a unique stream so that only repeated log messages will be suppressed.\n\nHowever, logs can also be assigned streams manually to achieve various outcomes:\n- A log can be assigned to an undefined/`None` stream so that rate-limiting doesn\'t apply to it.\n- Logs in different parts of the code can be grouped into the same stream so that they share a rate-limit, e.g. when they all trigger due to the same issue and only some are needed to indicate it.\n\nThe default can also be changed so that rate-limiting is disabled by default and only applies to logs for which a `stream_id` has been manually set.\n\n## Usage\n\n### Rate-limiting by default\n\nExample of rate-limiting with default options where each log message is assigned to its own stream:\n```python\nimport time\nimport logging\nfrom log_rate_limit import StreamRateLimitFilter, RateLimit\n# Setup logging\nlogging.basicConfig()\nlogger = logging.getLogger(__name__)\n\n# Add our filter\nlogger.addFilter(StreamRateLimitFilter(period_sec=1))\n# Log many warnings\nfor _ in range(100):\n    logger.warning("Wolf!")\nfor i in range(100):\n    logger.warning("No really, a wolf!")\n    if i == 98:\n        time.sleep(1)\n# Prevent rate-limited by setting/overriding the stream to be undefined (None)\nfor _ in range(3):\n    logger.warning("Sheep!", extra=RateLimit(stream_id=None))\n``` \nWhich only outputs the following:\n```log\nWARNING:__main__:Wolf!\nWARNING:__main__:No really, a wolf!\nWARNING:__main__:No really, a wolf!\n+ skipped 98 logs due to rate-limiting\nWARNING:__main__:Sheep!\nWARNING:__main__:Sheep!\nWARNING:__main__:Sheep!\n```\nNote that (unless overridden) logs were only repeated after the `sleep()` call, and the repeated log also included an extra summary message added afterwards.\n\nWhen we override rate-limiting above, you\'ll see our filter reads dynamic configs from logging\'s `extra` parameter.\n\n> Be very careful not to forget the `extra=` name part of the argument, as then the logging framework will assume you\'re passing arguments meant for formatting in the logging message and your options will silently be ignored!\n\n### Rate-limit only when specified\n\nIf you want most of your logs to be unaffected and you only have some you want to specifically rate-limit, then you can do the following:\n```python\nimport logging\nfrom log_rate_limit import StreamRateLimitFilter, RateLimit\n# Setup logging\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(__name__)\n\n# Add our filter, but don\'t assign unique streams to logs by default\nlogger.addFilter(StreamRateLimitFilter(period_sec=1, default_stream_id=None))\n# Normal logs are now not rate-limited\nfor i in range(3):\n    logger.info(f"Status update: {i}")\n# Only those we manually assign a stream will be rate-limited\nfor _ in range(3):\n    logger.warning("Issue!", extra=RateLimit(stream_id="issue"))\n```\nWhich only outputs the following:\n```log\nINFO:__main__:Status update: 0\nINFO:__main__:Status update: 1\nINFO:__main__:Status update: 2\nWARNING:__main__:Issue!\n```\n\n### Dynamically override configuration options\n\nSome options set during creation of the initial filter can be overridden for individual log calls. This is done by adding the `extra` parameter to any specific log call, e.g.:\n```python\n# Override the rate limit period for this specific log call\nlogger.warning("Test1", extra=RateLimit(stream_id="stream1", period_sec=30))\n# Override the allow_next_n value for a set of logs in the same stream so that this group of logs don\'t restrict one\n# another from occuring consecutively\nlogger.warning("Test", extra=RateLimit(stream_id="stream2", allow_next_n=2))\nlogger.info("Extra", extra=RateLimit(stream_id="stream2"))\nlogger.debug("Info", extra=RateLimit(stream_id="stream2"))\n```\n\nIf you want to set custom options for a large group of log calls without repeatedly adding the `extra` parameter, it\'s possible to use a [LoggerAdapter](https://docs.python.org/3/library/logging.html#loggeradapter-objects):\n```python\nimport logging\nfrom log_rate_limit import StreamRateLimitFilter, RateLimit\n\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(__name__)\n# Add our filter\nlogger.addFilter(StreamRateLimitFilter(period_sec=1))\n\n# Use LoggerAdapter to assign additional "extra" parameters to all calls using this logger\nglobal_extra = RateLimit(stream_id="custom_stream", period_sec=20)\nlogger = logging.LoggerAdapter(logger, global_extra)\n# Log many warnings\nfor _ in range(100):\n    logger.warning("Wolf!")\nfor i in range(100):\n    logger.warning("No really, a wolf!")\n```\nWhich merely outputs:\n```log\nWARNING:__main__:Wolf!\n```\nSince both log calls are in the same stream.\n\nAlternatively (to a LoggerAdapter), custom options can also be added by writing your own [logging.Filter](https://docs.python.org/3.8/howto/logging-cookbook.html#using-filters-to-impart-contextual-information).\n\n### Dynamic stream ID\n\nDynamic stream IDs can be assigned based on any criteria you want, e.g.:\n\n```python\nlogger.warning(f"Error occured on device {device_id}!", extra=RateLimit(stream_id=f"error_on_{device_id}"))\n```\n\n## Installation\n\n### Install from PyPI\n\nWith `Python 3` installed on your system, you can run:\n\n    pip install log-rate-limit\n\nTo test that installation worked, you can run:\n\n    python -c "import log_rate_limit"\n\nand you can uninstall at any time with:\n\n    pip uninstall log-rate-limit\n\nTo install with poetry:\n\n    poetry add log-rate-limit\n\n### Install from Github\n\nTo install the newest code directly from Github:\n\n    pip install git+https://github.com/samuller/log-rate-limit\n',
    'author': 'Simon Muller',
    'author_email': 'samullers@gmail.com',
    'maintainer': 'None',
    'maintainer_email': 'None',
    'url': 'https://github.com/samuller/log-rate-limit',
    'packages': packages,
    'package_data': package_data,
    'python_requires': '>=3.8,<4.0',
}


setup(**setup_kwargs)
