We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
LOG_FILTER not working as it should?
Good evening folks,

I have a problem with LOG_FILTER. According to the docs, one can remove `TAG_SAVE_AS is set to False` by configuring `LOG_FILTER` this way:

```python
import logging
LOG_FILTER = [(logging.WARN, 'TAG_SAVE_AS is set to False')]
```

So to ignore `AUTHOR_SAVE_AS is set to False` and `CATEGORY_SAVE_AS is set to False`, it must be :

```python
import logging
LOG_FILTER = [
    (logging.WARN, 'AUTHOR_SAVE_AS is set to False'),
    (logging.WARN, 'CATEGORY_SAVE_AS is set to False')
]
```
Right?

So this is what I did:
```bash
$ head pelicanconf.py -n 20
#!/usr/bin/env python
# -*- coding: utf-8 -*- #
from __future__ import unicode_literals
import datetime
import logging

import pelican

PELICAN_VERSION = pelican.__version__

[...]

LOG_FILTER = [
    (logging.WARN, 'AUTHOR_SAVE_AS is set to False'),
    (logging.WARN, 'CATEGORY_SAVE_AS is set to False')
]

AUTHOR_SAVE_AS = False
AUTHORS_SAVE_AS = False
TAG_SAVE_AS = False
TAGS_SAVE_AS = False
CATEGORY_SAVE_AS = False
CATEGORIES_SAVE_AS = False
ARCHIVES_SAVE_AS = False

$ make publish
pelican /mnt/c/Users/max/Code/mlcdf/content -o /mnt/c/Users/max/Code/mlcdf/output -s /mnt/c/Users/max/Code/mlcdf/publishconf.py
WARNING: CATEGORY_SAVE_AS is set to False
WARNING: AUTHOR_SAVE_AS is set to False
Done: Processed 2 articles, 0 drafts, 4 pages, 1 hidden page and 0 draft pages in 0.52 seconds.
```
2 things here:
- it didn't work: `AUTHOR_SAVE_AS is set to False` and `CATEGORY_SAVE_AS is set to False` are still logged.
- and, you may have noticed that, despite setting `TAG_SAVE_AS = False`, and not filtering it, I did not see `TAG_SAVE_AS is set to False` in the logs.

Using the template to filter worked (but it will also filter out other logs matching the template which is not what you would necessary want):
```
import logging
LOG_FILTER = [
    (logging.WARN, '%s is set to %s'),
]
```

* Am I missing something?
* What's the intended behaviour here? Is there an actual bug in the code? Or is the code example in the docs just wrong?

I'm a bit lost. 🙃 

Maxime

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pelican/log.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 import logging
4 import os
5 import sys
6 from collections import defaultdict
7 
8 __all__ = [
9     'init'
10 ]
11 
12 
13 class BaseFormatter(logging.Formatter):
14     def __init__(self, fmt=None, datefmt=None):
15         FORMAT = '%(customlevelname)s %(message)s'
16         super().__init__(fmt=FORMAT, datefmt=datefmt)
17 
18     def format(self, record):
19         customlevel = self._get_levelname(record.levelname)
20         record.__dict__['customlevelname'] = customlevel
21         # format multiline messages 'nicely' to make it clear they are together
22         record.msg = record.msg.replace('\n', '\n  | ')
23         record.args = tuple(arg.replace('\n', '\n  | ') if
24                             isinstance(arg, str) else
25                             arg for arg in record.args)
26         return super().format(record)
27 
28     def formatException(self, ei):
29         ''' prefix traceback info for better representation '''
30         s = super().formatException(ei)
31         # fancy format traceback
32         s = '\n'.join('  | ' + line for line in s.splitlines())
33         # separate the traceback from the preceding lines
34         s = '  |___\n{}'.format(s)
35         return s
36 
37     def _get_levelname(self, name):
38         ''' NOOP: overridden by subclasses '''
39         return name
40 
41 
42 class ANSIFormatter(BaseFormatter):
43     ANSI_CODES = {
44         'red': '\033[1;31m',
45         'yellow': '\033[1;33m',
46         'cyan': '\033[1;36m',
47         'white': '\033[1;37m',
48         'bgred': '\033[1;41m',
49         'bggrey': '\033[1;100m',
50         'reset': '\033[0;m'}
51 
52     LEVEL_COLORS = {
53         'INFO': 'cyan',
54         'WARNING': 'yellow',
55         'ERROR': 'red',
56         'CRITICAL': 'bgred',
57         'DEBUG': 'bggrey'}
58 
59     def _get_levelname(self, name):
60         color = self.ANSI_CODES[self.LEVEL_COLORS.get(name, 'white')]
61         if name == 'INFO':
62             fmt = '{0}->{2}'
63         else:
64             fmt = '{0}{1}{2}:'
65         return fmt.format(color, name, self.ANSI_CODES['reset'])
66 
67 
68 class TextFormatter(BaseFormatter):
69     """
70     Convert a `logging.LogRecord' object into text.
71     """
72 
73     def _get_levelname(self, name):
74         if name == 'INFO':
75             return '->'
76         else:
77             return name + ':'
78 
79 
80 class LimitFilter(logging.Filter):
81     """
82     Remove duplicates records, and limit the number of records in the same
83     group.
84 
85     Groups are specified by the message to use when the number of records in
86     the same group hit the limit.
87     E.g.: log.warning(('43 is not the answer', 'More erroneous answers'))
88     """
89 
90     LOGS_DEDUP_MIN_LEVEL = logging.WARNING
91 
92     _ignore = set()
93     _raised_messages = set()
94     _threshold = 5
95     _group_count = defaultdict(int)
96 
97     def filter(self, record):
98         # don't limit log messages for anything above "warning"
99         if record.levelno > self.LOGS_DEDUP_MIN_LEVEL:
100             return True
101 
102         # extract group
103         group = record.__dict__.get('limit_msg', None)
104         group_args = record.__dict__.get('limit_args', ())
105 
106         # ignore record if it was already raised
107         message_key = (record.levelno, record.getMessage())
108         if message_key in self._raised_messages:
109             return False
110         else:
111             self._raised_messages.add(message_key)
112 
113         # ignore LOG_FILTER records by templates when "debug" isn't enabled
114         logger_level = logging.getLogger().getEffectiveLevel()
115         if logger_level > logging.DEBUG:
116             ignore_key = (record.levelno, record.msg)
117             if ignore_key in self._ignore:
118                 return False
119 
120         # check if we went over threshold
121         if group:
122             key = (record.levelno, group)
123             self._group_count[key] += 1
124             if self._group_count[key] == self._threshold:
125                 record.msg = group
126                 record.args = group_args
127             elif self._group_count[key] > self._threshold:
128                 return False
129         return True
130 
131 
132 class LimitLogger(logging.Logger):
133     """
134     A logger which adds LimitFilter automatically
135     """
136 
137     limit_filter = LimitFilter()
138 
139     def __init__(self, *args, **kwargs):
140         super().__init__(*args, **kwargs)
141         self.enable_filter()
142 
143     def disable_filter(self):
144         self.removeFilter(LimitLogger.limit_filter)
145 
146     def enable_filter(self):
147         self.addFilter(LimitLogger.limit_filter)
148 
149 
150 class FatalLogger(LimitLogger):
151     warnings_fatal = False
152     errors_fatal = False
153 
154     def warning(self, *args, **kwargs):
155         super().warning(*args, **kwargs)
156         if FatalLogger.warnings_fatal:
157             raise RuntimeError('Warning encountered')
158 
159     def error(self, *args, **kwargs):
160         super().error(*args, **kwargs)
161         if FatalLogger.errors_fatal:
162             raise RuntimeError('Error encountered')
163 
164 
165 logging.setLoggerClass(FatalLogger)
166 
167 
168 def supports_color():
169     """
170     Returns True if the running system's terminal supports color,
171     and False otherwise.
172 
173     from django.core.management.color
174     """
175     plat = sys.platform
176     supported_platform = plat != 'Pocket PC' and \
177         (plat != 'win32' or 'ANSICON' in os.environ)
178 
179     # isatty is not always implemented, #6223.
180     is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
181     if not supported_platform or not is_a_tty:
182         return False
183     return True
184 
185 
186 def get_formatter():
187     if supports_color():
188         return ANSIFormatter()
189     else:
190         return TextFormatter()
191 
192 
193 def init(level=None, fatal='', handler=logging.StreamHandler(), name=None,
194          logs_dedup_min_level=None):
195     FatalLogger.warnings_fatal = fatal.startswith('warning')
196     FatalLogger.errors_fatal = bool(fatal)
197 
198     logger = logging.getLogger(name)
199 
200     handler.setFormatter(get_formatter())
201     logger.addHandler(handler)
202 
203     if level:
204         logger.setLevel(level)
205     if logs_dedup_min_level:
206         LimitFilter.LOGS_DEDUP_MIN_LEVEL = logs_dedup_min_level
207 
208 
209 def log_warnings():
210     import warnings
211     logging.captureWarnings(True)
212     warnings.simplefilter("default", DeprecationWarning)
213     init(logging.DEBUG, name='py.warnings')
214 
215 
216 if __name__ == '__main__':
217     init(level=logging.DEBUG)
218 
219     root_logger = logging.getLogger()
220     root_logger.debug('debug')
221     root_logger.info('info')
222     root_logger.warning('warning')
223     root_logger.error('error')
224     root_logger.critical('critical')
225
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pelican/log.py b/pelican/log.py
--- a/pelican/log.py
+++ b/pelican/log.py
@@ -110,11 +110,13 @@
         else:
             self._raised_messages.add(message_key)
 
-        # ignore LOG_FILTER records by templates when "debug" isn't enabled
+        # ignore LOG_FILTER records by templates or messages
+        # when "debug" isn't enabled
         logger_level = logging.getLogger().getEffectiveLevel()
         if logger_level > logging.DEBUG:
-            ignore_key = (record.levelno, record.msg)
-            if ignore_key in self._ignore:
+            template_key = (record.levelno, record.msg)
+            message_key = (record.levelno, record.getMessage())
+            if (template_key in self._ignore or message_key in self._ignore):
                 return False
 
         # check if we went over threshold

{"golden_diff": "diff --git a/pelican/log.py b/pelican/log.py\n--- a/pelican/log.py\n+++ b/pelican/log.py\n@@ -110,11 +110,13 @@\n         else:\n             self._raised_messages.add(message_key)\n \n-        # ignore LOG_FILTER records by templates when \"debug\" isn't enabled\n+        # ignore LOG_FILTER records by templates or messages\n+        # when \"debug\" isn't enabled\n         logger_level = logging.getLogger().getEffectiveLevel()\n         if logger_level > logging.DEBUG:\n-            ignore_key = (record.levelno, record.msg)\n-            if ignore_key in self._ignore:\n+            template_key = (record.levelno, record.msg)\n+            message_key = (record.levelno, record.getMessage())\n+            if (template_key in self._ignore or message_key in self._ignore):\n                 return False\n \n         # check if we went over threshold\n", "issue": "LOG_FILTER not working as it should?\nGood evening folks,\r\n\r\nI have a problem with LOG_FILTER. According to the docs, one can remove `TAG_SAVE_AS is set to False` by configuring `LOG_FILTER` this way:\r\n\r\n```python\r\nimport logging\r\nLOG_FILTER = [(logging.WARN, 'TAG_SAVE_AS is set to False')]\r\n```\r\n\r\nSo to ignore `AUTHOR_SAVE_AS is set to False` and `CATEGORY_SAVE_AS is set to False`, it must be :\r\n\r\n```python\r\nimport logging\r\nLOG_FILTER = [\r\n    (logging.WARN, 'AUTHOR_SAVE_AS is set to False'),\r\n    (logging.WARN, 'CATEGORY_SAVE_AS is set to False')\r\n]\r\n```\r\nRight?\r\n\r\nSo this is what I did:\r\n```bash\r\n$ head pelicanconf.py -n 20\r\n#!/usr/bin/env python\r\n# -*- coding: utf-8 -*- #\r\nfrom __future__ import unicode_literals\r\nimport datetime\r\nimport logging\r\n\r\nimport pelican\r\n\r\nPELICAN_VERSION = pelican.__version__\r\n\r\n[...]\r\n\r\nLOG_FILTER = [\r\n    (logging.WARN, 'AUTHOR_SAVE_AS is set to False'),\r\n    (logging.WARN, 'CATEGORY_SAVE_AS is set to False')\r\n]\r\n\r\nAUTHOR_SAVE_AS = False\r\nAUTHORS_SAVE_AS = False\r\nTAG_SAVE_AS = False\r\nTAGS_SAVE_AS = False\r\nCATEGORY_SAVE_AS = False\r\nCATEGORIES_SAVE_AS = False\r\nARCHIVES_SAVE_AS = False\r\n\r\n$ make publish\r\npelican /mnt/c/Users/max/Code/mlcdf/content -o /mnt/c/Users/max/Code/mlcdf/output -s /mnt/c/Users/max/Code/mlcdf/publishconf.py\r\nWARNING: CATEGORY_SAVE_AS is set to False\r\nWARNING: AUTHOR_SAVE_AS is set to False\r\nDone: Processed 2 articles, 0 drafts, 4 pages, 1 hidden page and 0 draft pages in 0.52 seconds.\r\n```\r\n2 things here:\r\n- it didn't work: `AUTHOR_SAVE_AS is set to False` and `CATEGORY_SAVE_AS is set to False` are still logged.\r\n- and, you may have noticed that, despite setting `TAG_SAVE_AS = False`, and not filtering it, I did not see `TAG_SAVE_AS is set to False` in the logs.\r\n\r\nUsing the template to filter worked (but it will also filter out other logs matching the template which is not what you would necessary want):\r\n```\r\nimport logging\r\nLOG_FILTER = [\r\n    (logging.WARN, '%s is set to %s'),\r\n]\r\n```\r\n\r\n* Am I missing something?\r\n* What's the intended behaviour here? Is there an actual bug in the code? Or is the code example in the docs just wrong?\r\n\r\nI'm a bit lost. \ud83d\ude43 \r\n\r\nMaxime\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\nimport logging\nimport os\nimport sys\nfrom collections import defaultdict\n\n__all__ = [\n    'init'\n]\n\n\nclass BaseFormatter(logging.Formatter):\n    def __init__(self, fmt=None, datefmt=None):\n        FORMAT = '%(customlevelname)s %(message)s'\n        super().__init__(fmt=FORMAT, datefmt=datefmt)\n\n    def format(self, record):\n        customlevel = self._get_levelname(record.levelname)\n        record.__dict__['customlevelname'] = customlevel\n        # format multiline messages 'nicely' to make it clear they are together\n        record.msg = record.msg.replace('\\n', '\\n  | ')\n        record.args = tuple(arg.replace('\\n', '\\n  | ') if\n                            isinstance(arg, str) else\n                            arg for arg in record.args)\n        return super().format(record)\n\n    def formatException(self, ei):\n        ''' prefix traceback info for better representation '''\n        s = super().formatException(ei)\n        # fancy format traceback\n        s = '\\n'.join('  | ' + line for line in s.splitlines())\n        # separate the traceback from the preceding lines\n        s = '  |___\\n{}'.format(s)\n        return s\n\n    def _get_levelname(self, name):\n        ''' NOOP: overridden by subclasses '''\n        return name\n\n\nclass ANSIFormatter(BaseFormatter):\n    ANSI_CODES = {\n        'red': '\\033[1;31m',\n        'yellow': '\\033[1;33m',\n        'cyan': '\\033[1;36m',\n        'white': '\\033[1;37m',\n        'bgred': '\\033[1;41m',\n        'bggrey': '\\033[1;100m',\n        'reset': '\\033[0;m'}\n\n    LEVEL_COLORS = {\n        'INFO': 'cyan',\n        'WARNING': 'yellow',\n        'ERROR': 'red',\n        'CRITICAL': 'bgred',\n        'DEBUG': 'bggrey'}\n\n    def _get_levelname(self, name):\n        color = self.ANSI_CODES[self.LEVEL_COLORS.get(name, 'white')]\n        if name == 'INFO':\n            fmt = '{0}->{2}'\n        else:\n            fmt = '{0}{1}{2}:'\n        return fmt.format(color, name, self.ANSI_CODES['reset'])\n\n\nclass TextFormatter(BaseFormatter):\n    \"\"\"\n    Convert a `logging.LogRecord' object into text.\n    \"\"\"\n\n    def _get_levelname(self, name):\n        if name == 'INFO':\n            return '->'\n        else:\n            return name + ':'\n\n\nclass LimitFilter(logging.Filter):\n    \"\"\"\n    Remove duplicates records, and limit the number of records in the same\n    group.\n\n    Groups are specified by the message to use when the number of records in\n    the same group hit the limit.\n    E.g.: log.warning(('43 is not the answer', 'More erroneous answers'))\n    \"\"\"\n\n    LOGS_DEDUP_MIN_LEVEL = logging.WARNING\n\n    _ignore = set()\n    _raised_messages = set()\n    _threshold = 5\n    _group_count = defaultdict(int)\n\n    def filter(self, record):\n        # don't limit log messages for anything above \"warning\"\n        if record.levelno > self.LOGS_DEDUP_MIN_LEVEL:\n            return True\n\n        # extract group\n        group = record.__dict__.get('limit_msg', None)\n        group_args = record.__dict__.get('limit_args', ())\n\n        # ignore record if it was already raised\n        message_key = (record.levelno, record.getMessage())\n        if message_key in self._raised_messages:\n            return False\n        else:\n            self._raised_messages.add(message_key)\n\n        # ignore LOG_FILTER records by templates when \"debug\" isn't enabled\n        logger_level = logging.getLogger().getEffectiveLevel()\n        if logger_level > logging.DEBUG:\n            ignore_key = (record.levelno, record.msg)\n            if ignore_key in self._ignore:\n                return False\n\n        # check if we went over threshold\n        if group:\n            key = (record.levelno, group)\n            self._group_count[key] += 1\n            if self._group_count[key] == self._threshold:\n                record.msg = group\n                record.args = group_args\n            elif self._group_count[key] > self._threshold:\n                return False\n        return True\n\n\nclass LimitLogger(logging.Logger):\n    \"\"\"\n    A logger which adds LimitFilter automatically\n    \"\"\"\n\n    limit_filter = LimitFilter()\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.enable_filter()\n\n    def disable_filter(self):\n        self.removeFilter(LimitLogger.limit_filter)\n\n    def enable_filter(self):\n        self.addFilter(LimitLogger.limit_filter)\n\n\nclass FatalLogger(LimitLogger):\n    warnings_fatal = False\n    errors_fatal = False\n\n    def warning(self, *args, **kwargs):\n        super().warning(*args, **kwargs)\n        if FatalLogger.warnings_fatal:\n            raise RuntimeError('Warning encountered')\n\n    def error(self, *args, **kwargs):\n        super().error(*args, **kwargs)\n        if FatalLogger.errors_fatal:\n            raise RuntimeError('Error encountered')\n\n\nlogging.setLoggerClass(FatalLogger)\n\n\ndef supports_color():\n    \"\"\"\n    Returns True if the running system's terminal supports color,\n    and False otherwise.\n\n    from django.core.management.color\n    \"\"\"\n    plat = sys.platform\n    supported_platform = plat != 'Pocket PC' and \\\n        (plat != 'win32' or 'ANSICON' in os.environ)\n\n    # isatty is not always implemented, #6223.\n    is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()\n    if not supported_platform or not is_a_tty:\n        return False\n    return True\n\n\ndef get_formatter():\n    if supports_color():\n        return ANSIFormatter()\n    else:\n        return TextFormatter()\n\n\ndef init(level=None, fatal='', handler=logging.StreamHandler(), name=None,\n         logs_dedup_min_level=None):\n    FatalLogger.warnings_fatal = fatal.startswith('warning')\n    FatalLogger.errors_fatal = bool(fatal)\n\n    logger = logging.getLogger(name)\n\n    handler.setFormatter(get_formatter())\n    logger.addHandler(handler)\n\n    if level:\n        logger.setLevel(level)\n    if logs_dedup_min_level:\n        LimitFilter.LOGS_DEDUP_MIN_LEVEL = logs_dedup_min_level\n\n\ndef log_warnings():\n    import warnings\n    logging.captureWarnings(True)\n    warnings.simplefilter(\"default\", DeprecationWarning)\n    init(logging.DEBUG, name='py.warnings')\n\n\nif __name__ == '__main__':\n    init(level=logging.DEBUG)\n\n    root_logger = logging.getLogger()\n    root_logger.debug('debug')\n    root_logger.info('info')\n    root_logger.warning('warning')\n    root_logger.error('error')\n    root_logger.critical('critical')\n", "path": "pelican/log.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\nimport logging\nimport os\nimport sys\nfrom collections import defaultdict\n\n__all__ = [\n    'init'\n]\n\n\nclass BaseFormatter(logging.Formatter):\n    def __init__(self, fmt=None, datefmt=None):\n        FORMAT = '%(customlevelname)s %(message)s'\n        super().__init__(fmt=FORMAT, datefmt=datefmt)\n\n    def format(self, record):\n        customlevel = self._get_levelname(record.levelname)\n        record.__dict__['customlevelname'] = customlevel\n        # format multiline messages 'nicely' to make it clear they are together\n        record.msg = record.msg.replace('\\n', '\\n  | ')\n        record.args = tuple(arg.replace('\\n', '\\n  | ') if\n                            isinstance(arg, str) else\n                            arg for arg in record.args)\n        return super().format(record)\n\n    def formatException(self, ei):\n        ''' prefix traceback info for better representation '''\n        s = super().formatException(ei)\n        # fancy format traceback\n        s = '\\n'.join('  | ' + line for line in s.splitlines())\n        # separate the traceback from the preceding lines\n        s = '  |___\\n{}'.format(s)\n        return s\n\n    def _get_levelname(self, name):\n        ''' NOOP: overridden by subclasses '''\n        return name\n\n\nclass ANSIFormatter(BaseFormatter):\n    ANSI_CODES = {\n        'red': '\\033[1;31m',\n        'yellow': '\\033[1;33m',\n        'cyan': '\\033[1;36m',\n        'white': '\\033[1;37m',\n        'bgred': '\\033[1;41m',\n        'bggrey': '\\033[1;100m',\n        'reset': '\\033[0;m'}\n\n    LEVEL_COLORS = {\n        'INFO': 'cyan',\n        'WARNING': 'yellow',\n        'ERROR': 'red',\n        'CRITICAL': 'bgred',\n        'DEBUG': 'bggrey'}\n\n    def _get_levelname(self, name):\n        color = self.ANSI_CODES[self.LEVEL_COLORS.get(name, 'white')]\n        if name == 'INFO':\n            fmt = '{0}->{2}'\n        else:\n            fmt = '{0}{1}{2}:'\n        return fmt.format(color, name, self.ANSI_CODES['reset'])\n\n\nclass TextFormatter(BaseFormatter):\n    \"\"\"\n    Convert a `logging.LogRecord' object into text.\n    \"\"\"\n\n    def _get_levelname(self, name):\n        if name == 'INFO':\n            return '->'\n        else:\n            return name + ':'\n\n\nclass LimitFilter(logging.Filter):\n    \"\"\"\n    Remove duplicates records, and limit the number of records in the same\n    group.\n\n    Groups are specified by the message to use when the number of records in\n    the same group hit the limit.\n    E.g.: log.warning(('43 is not the answer', 'More erroneous answers'))\n    \"\"\"\n\n    LOGS_DEDUP_MIN_LEVEL = logging.WARNING\n\n    _ignore = set()\n    _raised_messages = set()\n    _threshold = 5\n    _group_count = defaultdict(int)\n\n    def filter(self, record):\n        # don't limit log messages for anything above \"warning\"\n        if record.levelno > self.LOGS_DEDUP_MIN_LEVEL:\n            return True\n\n        # extract group\n        group = record.__dict__.get('limit_msg', None)\n        group_args = record.__dict__.get('limit_args', ())\n\n        # ignore record if it was already raised\n        message_key = (record.levelno, record.getMessage())\n        if message_key in self._raised_messages:\n            return False\n        else:\n            self._raised_messages.add(message_key)\n\n        # ignore LOG_FILTER records by templates or messages\n        # when \"debug\" isn't enabled\n        logger_level = logging.getLogger().getEffectiveLevel()\n        if logger_level > logging.DEBUG:\n            template_key = (record.levelno, record.msg)\n            message_key = (record.levelno, record.getMessage())\n            if (template_key in self._ignore or message_key in self._ignore):\n                return False\n\n        # check if we went over threshold\n        if group:\n            key = (record.levelno, group)\n            self._group_count[key] += 1\n            if self._group_count[key] == self._threshold:\n                record.msg = group\n                record.args = group_args\n            elif self._group_count[key] > self._threshold:\n                return False\n        return True\n\n\nclass LimitLogger(logging.Logger):\n    \"\"\"\n    A logger which adds LimitFilter automatically\n    \"\"\"\n\n    limit_filter = LimitFilter()\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.enable_filter()\n\n    def disable_filter(self):\n        self.removeFilter(LimitLogger.limit_filter)\n\n    def enable_filter(self):\n        self.addFilter(LimitLogger.limit_filter)\n\n\nclass FatalLogger(LimitLogger):\n    warnings_fatal = False\n    errors_fatal = False\n\n    def warning(self, *args, **kwargs):\n        super().warning(*args, **kwargs)\n        if FatalLogger.warnings_fatal:\n            raise RuntimeError('Warning encountered')\n\n    def error(self, *args, **kwargs):\n        super().error(*args, **kwargs)\n        if FatalLogger.errors_fatal:\n            raise RuntimeError('Error encountered')\n\n\nlogging.setLoggerClass(FatalLogger)\n\n\ndef supports_color():\n    \"\"\"\n    Returns True if the running system's terminal supports color,\n    and False otherwise.\n\n    from django.core.management.color\n    \"\"\"\n    plat = sys.platform\n    supported_platform = plat != 'Pocket PC' and \\\n        (plat != 'win32' or 'ANSICON' in os.environ)\n\n    # isatty is not always implemented, #6223.\n    is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()\n    if not supported_platform or not is_a_tty:\n        return False\n    return True\n\n\ndef get_formatter():\n    if supports_color():\n        return ANSIFormatter()\n    else:\n        return TextFormatter()\n\n\ndef init(level=None, fatal='', handler=logging.StreamHandler(), name=None,\n         logs_dedup_min_level=None):\n    FatalLogger.warnings_fatal = fatal.startswith('warning')\n    FatalLogger.errors_fatal = bool(fatal)\n\n    logger = logging.getLogger(name)\n\n    handler.setFormatter(get_formatter())\n    logger.addHandler(handler)\n\n    if level:\n        logger.setLevel(level)\n    if logs_dedup_min_level:\n        LimitFilter.LOGS_DEDUP_MIN_LEVEL = logs_dedup_min_level\n\n\ndef log_warnings():\n    import warnings\n    logging.captureWarnings(True)\n    warnings.simplefilter(\"default\", DeprecationWarning)\n    init(logging.DEBUG, name='py.warnings')\n\n\nif __name__ == '__main__':\n    init(level=logging.DEBUG)\n\n    root_logger = logging.getLogger()\n    root_logger.debug('debug')\n    root_logger.info('info')\n    root_logger.warning('warning')\n    root_logger.error('error')\n    root_logger.critical('critical')\n", "path": "pelican/log.py"}]}