2015-06-16 09:25:09 +02:00
|
|
|
import logging
|
2018-11-24 21:59:40 +01:00
|
|
|
from collections import defaultdict
|
2021-06-30 22:47:32 -06:00
|
|
|
|
2021-07-08 21:33:22 -06:00
|
|
|
from rich.console import Console
|
2021-06-30 22:47:32 -06:00
|
|
|
from rich.logging import RichHandler
|
2012-03-07 12:17:39 +00:00
|
|
|
|
2023-10-29 22:18:29 +01:00
|
|
|
__all__ = ["init"]
|
2015-06-16 09:25:09 +02:00
|
|
|
|
2021-07-08 21:33:22 -06:00
|
|
|
console = Console()
|
|
|
|
|
|
2015-06-16 09:25:09 +02:00
|
|
|
|
2014-04-01 20:44:09 +02:00
|
|
|
class LimitFilter(logging.Filter):
|
|
|
|
|
"""
|
|
|
|
|
Remove duplicates records, and limit the number of records in the same
|
|
|
|
|
group.
|
|
|
|
|
|
|
|
|
|
Groups are specified by the message to use when the number of records in
|
|
|
|
|
the same group hit the limit.
|
|
|
|
|
E.g.: log.warning(('43 is not the answer', 'More erroneous answers'))
|
|
|
|
|
"""
|
|
|
|
|
|
2017-08-15 16:26:03 +02:00
|
|
|
LOGS_DEDUP_MIN_LEVEL = logging.WARNING
|
|
|
|
|
|
2014-07-22 11:48:15 -04:00
|
|
|
_ignore = set()
|
2016-09-12 03:01:22 +03:00
|
|
|
_raised_messages = set()
|
2014-07-22 11:48:15 -04:00
|
|
|
_threshold = 5
|
|
|
|
|
_group_count = defaultdict(int)
|
2014-04-01 20:44:09 +02:00
|
|
|
|
|
|
|
|
def filter(self, record):
|
2014-04-14 16:18:07 -04:00
|
|
|
# don't limit log messages for anything above "warning"
|
2017-08-15 16:26:03 +02:00
|
|
|
if record.levelno > self.LOGS_DEDUP_MIN_LEVEL:
|
2014-07-22 11:48:15 -04:00
|
|
|
return True
|
|
|
|
|
|
2014-04-01 20:44:09 +02:00
|
|
|
# extract group
|
2023-10-29 22:18:29 +01:00
|
|
|
group = record.__dict__.get("limit_msg", None)
|
|
|
|
|
group_args = record.__dict__.get("limit_args", ())
|
2014-07-22 11:48:15 -04:00
|
|
|
|
2014-04-01 20:44:09 +02:00
|
|
|
# ignore record if it was already raised
|
2016-09-12 03:01:22 +03:00
|
|
|
message_key = (record.levelno, record.getMessage())
|
|
|
|
|
if message_key in self._raised_messages:
|
2014-04-01 20:44:09 +02:00
|
|
|
return False
|
2014-07-22 11:48:15 -04:00
|
|
|
else:
|
2016-09-12 03:01:22 +03:00
|
|
|
self._raised_messages.add(message_key)
|
|
|
|
|
|
2020-04-12 20:56:07 +03:00
|
|
|
# ignore LOG_FILTER records by templates or messages
|
|
|
|
|
# when "debug" isn't enabled
|
2016-09-12 03:02:29 +03:00
|
|
|
logger_level = logging.getLogger().getEffectiveLevel()
|
|
|
|
|
if logger_level > logging.DEBUG:
|
2020-04-12 20:56:07 +03:00
|
|
|
template_key = (record.levelno, record.msg)
|
|
|
|
|
message_key = (record.levelno, record.getMessage())
|
2023-10-29 22:18:29 +01:00
|
|
|
if template_key in self._ignore or message_key in self._ignore:
|
2016-09-12 03:02:29 +03:00
|
|
|
return False
|
2014-07-22 11:48:15 -04:00
|
|
|
|
2014-04-01 20:44:09 +02:00
|
|
|
# check if we went over threshold
|
|
|
|
|
if group:
|
|
|
|
|
key = (record.levelno, group)
|
2014-07-22 11:48:15 -04:00
|
|
|
self._group_count[key] += 1
|
|
|
|
|
if self._group_count[key] == self._threshold:
|
2014-04-01 20:44:09 +02:00
|
|
|
record.msg = group
|
2014-07-22 11:48:15 -04:00
|
|
|
record.args = group_args
|
|
|
|
|
elif self._group_count[key] > self._threshold:
|
2014-04-01 20:44:09 +02:00
|
|
|
return False
|
2014-07-22 11:48:15 -04:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
2019-11-05 23:17:19 -08:00
|
|
|
class LimitLogger(logging.Logger):
|
2014-04-01 20:44:09 +02:00
|
|
|
"""
|
2014-04-14 16:18:07 -04:00
|
|
|
A logger which adds LimitFilter automatically
|
2014-04-01 20:44:09 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
limit_filter = LimitFilter()
|
|
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
2019-11-18 20:28:48 +03:00
|
|
|
super().__init__(*args, **kwargs)
|
2015-10-19 16:22:43 +02:00
|
|
|
self.enable_filter()
|
|
|
|
|
|
|
|
|
|
def disable_filter(self):
|
|
|
|
|
self.removeFilter(LimitLogger.limit_filter)
|
|
|
|
|
|
|
|
|
|
def enable_filter(self):
|
2014-04-01 20:44:09 +02:00
|
|
|
self.addFilter(LimitLogger.limit_filter)
|
|
|
|
|
|
2015-11-15 20:34:35 +01:00
|
|
|
|
|
|
|
|
class FatalLogger(LimitLogger):
|
|
|
|
|
warnings_fatal = False
|
|
|
|
|
errors_fatal = False
|
|
|
|
|
|
2024-01-27 10:47:54 -07:00
|
|
|
def warning(self, *args, stacklevel=1, **kwargs):
|
|
|
|
|
"""
|
|
|
|
|
Displays a logging warning.
|
|
|
|
|
|
|
|
|
|
Wrapping it here allows Pelican to filter warnings, and conditionally
|
|
|
|
|
make warnings fatal.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
stacklevel (int): the stacklevel that would be used to display the
|
|
|
|
|
calling location, except for this function. Adjusting the
|
|
|
|
|
stacklevel allows you to see the "true" calling location of the
|
|
|
|
|
warning, rather than this wrapper location.
|
|
|
|
|
"""
|
|
|
|
|
stacklevel += 1
|
|
|
|
|
super().warning(*args, stacklevel=stacklevel, **kwargs)
|
2015-11-15 20:34:35 +01:00
|
|
|
if FatalLogger.warnings_fatal:
|
2023-10-29 22:18:29 +01:00
|
|
|
raise RuntimeError("Warning encountered")
|
2015-11-15 20:34:35 +01:00
|
|
|
|
2024-01-27 10:47:54 -07:00
|
|
|
def error(self, *args, stacklevel=1, **kwargs):
|
|
|
|
|
"""
|
|
|
|
|
Displays a logging error.
|
|
|
|
|
|
|
|
|
|
Wrapping it here allows Pelican to filter errors, and conditionally
|
|
|
|
|
make errors non-fatal.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
stacklevel (int): the stacklevel that would be used to display the
|
|
|
|
|
calling location, except for this function. Adjusting the
|
|
|
|
|
stacklevel allows you to see the "true" calling location of the
|
|
|
|
|
error, rather than this wrapper location.
|
|
|
|
|
"""
|
|
|
|
|
stacklevel += 1
|
|
|
|
|
super().error(*args, stacklevel=stacklevel, **kwargs)
|
2015-11-15 20:34:35 +01:00
|
|
|
if FatalLogger.errors_fatal:
|
2023-10-29 22:18:29 +01:00
|
|
|
raise RuntimeError("Error encountered")
|
2015-11-15 20:34:35 +01:00
|
|
|
|
2017-01-24 00:07:15 +01:00
|
|
|
|
2015-11-15 20:34:35 +01:00
|
|
|
logging.setLoggerClass(FatalLogger)
|
2020-06-05 01:48:11 +02:00
|
|
|
# force root logger to be of our preferred class
|
|
|
|
|
logging.getLogger().__class__ = FatalLogger
|
2014-04-01 20:44:09 +02:00
|
|
|
|
2024-05-30 17:13:27 +02:00
|
|
|
DEFAULT_LOG_HANDLER = RichHandler(console=console)
|
|
|
|
|
|
2014-04-01 20:44:09 +02:00
|
|
|
|
2023-10-29 22:18:29 +01:00
|
|
|
def init(
|
|
|
|
|
level=None,
|
|
|
|
|
fatal="",
|
2024-05-30 17:13:27 +02:00
|
|
|
handler=DEFAULT_LOG_HANDLER,
|
2023-10-29 22:18:29 +01:00
|
|
|
name=None,
|
|
|
|
|
logs_dedup_min_level=None,
|
|
|
|
|
):
|
|
|
|
|
FatalLogger.warnings_fatal = fatal.startswith("warning")
|
2015-11-15 20:34:35 +01:00
|
|
|
FatalLogger.errors_fatal = bool(fatal)
|
2015-10-19 22:38:23 +02:00
|
|
|
|
2021-06-30 22:47:32 -06:00
|
|
|
LOG_FORMAT = "%(message)s"
|
|
|
|
|
logging.basicConfig(
|
2024-05-30 17:13:27 +02:00
|
|
|
level=level,
|
|
|
|
|
format=LOG_FORMAT,
|
|
|
|
|
datefmt="[%H:%M:%S]",
|
|
|
|
|
handlers=[handler] if handler else [],
|
2021-06-30 22:47:32 -06:00
|
|
|
)
|
2015-10-19 22:38:23 +02:00
|
|
|
|
2021-06-30 22:47:32 -06:00
|
|
|
logger = logging.getLogger(name)
|
2012-03-20 13:01:21 +00:00
|
|
|
|
2011-04-20 14:44:25 +02:00
|
|
|
if level:
|
|
|
|
|
logger.setLevel(level)
|
2017-08-15 16:26:03 +02:00
|
|
|
if logs_dedup_min_level:
|
|
|
|
|
LimitFilter.LOGS_DEDUP_MIN_LEVEL = logs_dedup_min_level
|
2011-04-18 17:58:48 +02:00
|
|
|
|
|
|
|
|
|
2015-10-19 22:38:23 +02:00
|
|
|
def log_warnings():
|
|
|
|
|
import warnings
|
2023-10-29 22:18:29 +01:00
|
|
|
|
2015-10-19 22:38:23 +02:00
|
|
|
logging.captureWarnings(True)
|
|
|
|
|
warnings.simplefilter("default", DeprecationWarning)
|
2023-10-29 22:18:29 +01:00
|
|
|
init(logging.DEBUG, name="py.warnings")
|
2015-10-19 22:38:23 +02:00
|
|
|
|
|
|
|
|
|
2023-10-29 22:18:29 +01:00
|
|
|
if __name__ == "__main__":
|
2021-06-30 22:47:32 -06:00
|
|
|
init(level=logging.DEBUG, name=__name__)
|
2011-04-25 12:13:44 +02:00
|
|
|
|
2021-06-30 22:47:32 -06:00
|
|
|
root_logger = logging.getLogger(__name__)
|
2023-10-29 22:18:29 +01:00
|
|
|
root_logger.debug("debug")
|
|
|
|
|
root_logger.info("info")
|
|
|
|
|
root_logger.warning("warning")
|
|
|
|
|
root_logger.error("error")
|
|
|
|
|
root_logger.critical("critical")
|