50 lines
1.7 KiB
Python
50 lines
1.7 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import re
|
|
|
|
|
|
class ScrubbingFormatter(logging.Formatter):
|
|
_KEY_VALUE_PATTERNS = (
|
|
re.compile(r"\b([A-Za-z_][A-Za-z0-9_]*id)=([^\s,]+)"),
|
|
re.compile(r"\b([A-Za-z_][A-Za-z0-9_]*_key)=([^\s,]+)"),
|
|
)
|
|
_TEXT_PATTERNS = (
|
|
re.compile(r"\b(index|task|dialog|rag|session|plan|artifact|evidence|symbol|edge|entry) id\b[:=]\s*([^\s,]+)", re.IGNORECASE),
|
|
)
|
|
|
|
def format(self, record: logging.LogRecord) -> str:
|
|
rendered = super().format(record)
|
|
scrubbed = self._scrub(rendered).rstrip("\n")
|
|
return scrubbed + "\n"
|
|
|
|
def _scrub(self, message: str) -> str:
|
|
output = message
|
|
for pattern in self._KEY_VALUE_PATTERNS:
|
|
output = pattern.sub(self._replace_key_value, output)
|
|
for pattern in self._TEXT_PATTERNS:
|
|
output = pattern.sub(self._replace_text, output)
|
|
return output
|
|
|
|
def _replace_key_value(self, match: re.Match[str]) -> str:
|
|
return f"{match.group(1)}=<redacted>"
|
|
|
|
def _replace_text(self, match: re.Match[str]) -> str:
|
|
return f"{match.group(1)} id=<redacted>"
|
|
|
|
|
|
def configure_logging() -> None:
|
|
logging.basicConfig(
|
|
level=logging.WARNING,
|
|
force=True,
|
|
format="%(levelname)s:%(name)s:%(message)s",
|
|
)
|
|
root_logger = logging.getLogger()
|
|
root_logger.setLevel(logging.WARNING)
|
|
formatter = ScrubbingFormatter("%(levelname)s:%(name)s:%(message)s")
|
|
for handler in root_logger.handlers:
|
|
handler.setFormatter(formatter)
|
|
logging.getLogger("uvicorn").setLevel(logging.WARNING)
|
|
logging.getLogger("uvicorn.error").setLevel(logging.WARNING)
|
|
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
|