Adds a few new logging utility classes. One to save to files with a date, one to support optional fields in formats, last to filter partial log messages. ref: N25B-401
68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
import logging
|
|
import re
|
|
|
|
|
|
class OptionalFieldFormatter(logging.Formatter):
|
|
"""
|
|
Logging formatter that supports optional fields marked by `?`.
|
|
|
|
Optional fields are denoted by placing a `?` after the field name inside
|
|
the parentheses, e.g., `%(role?)s`. If the field is not provided in the
|
|
log call's `extra` dict, it will use the default value from `defaults`
|
|
or `None` if no default is specified.
|
|
|
|
:param fmt: Format string with optional `%(name?)s` style fields.
|
|
:type fmt: str or None
|
|
:param datefmt: Date format string, passed to parent :class:`logging.Formatter`.
|
|
:type datefmt: str or None
|
|
:param style: Formatting style, must be '%'. Passed to parent.
|
|
:type style: str
|
|
:param defaults: Default values for optional fields when not provided.
|
|
:type defaults: dict or None
|
|
|
|
:example:
|
|
|
|
>>> formatter = OptionalFieldFormatter(
|
|
... fmt="%(asctime)s %(levelname)s %(role?)s %(message)s",
|
|
... defaults={"role": ""-""}
|
|
... )
|
|
>>> handler = logging.StreamHandler()
|
|
>>> handler.setFormatter(formatter)
|
|
>>> logger = logging.getLogger(__name__)
|
|
>>> logger.addHandler(handler)
|
|
>>>
|
|
>>> logger.chat("Hello there!", extra={"role": "USER"})
|
|
2025-01-15 10:30:00 CHAT USER Hello there!
|
|
>>>
|
|
>>> logger.info("A logging message")
|
|
2025-01-15 10:30:01 INFO - A logging message
|
|
|
|
.. note::
|
|
Only `%`-style formatting is supported. The `{` and `$` styles are not
|
|
compatible with this formatter.
|
|
|
|
.. seealso::
|
|
:class:`logging.Formatter` for base formatter documentation.
|
|
"""
|
|
|
|
# Match %(name?)s or %(name?)d etc.
|
|
OPTIONAL_PATTERN = re.compile(r"%\((\w+)\?\)([sdifFeEgGxXocrba%])")
|
|
|
|
def __init__(self, fmt=None, datefmt=None, style="%", defaults=None):
|
|
self.defaults = defaults or {}
|
|
|
|
self.optional_fields = set(self.OPTIONAL_PATTERN.findall(fmt or ""))
|
|
|
|
# Convert %(name?)s to %(name)s for standard formatting
|
|
normalized_fmt = self.OPTIONAL_PATTERN.sub(r"%(\1)\2", fmt or "")
|
|
|
|
super().__init__(normalized_fmt, datefmt, style)
|
|
|
|
def format(self, record):
|
|
for field, _ in self.optional_fields:
|
|
if not hasattr(record, field):
|
|
default = self.defaults.get(field, None)
|
|
setattr(record, field, default)
|
|
|
|
return super().format(record)
|