Files
secure-online-shop/app/core/logging.py
T

87 lines
2.4 KiB
Python

from __future__ import annotations
import logging
import logging.config
import re
from collections.abc import Mapping, Sequence
from typing import Any
SENSITIVE_KEYS = {
"password",
"password_hash",
"token",
"access_token",
"refresh_token",
"authorization",
"email",
}
TOKEN_PATTERN = re.compile(r"Bearer\s+[A-Za-z0-9\-._~+/]+=*", re.IGNORECASE)
def _sanitize_value(value: Any) -> Any:
if isinstance(value, str):
redacted = TOKEN_PATTERN.sub("Bearer [REDACTED]", value)
for key in SENSITIVE_KEYS:
redacted = re.sub(
rf"({key}\s*=\s*)([^,\s]+)",
r"\1[REDACTED]",
redacted,
flags=re.IGNORECASE,
)
return redacted
if isinstance(value, Mapping):
return {
key: "[REDACTED]" if str(key).lower() in SENSITIVE_KEYS else _sanitize_value(item)
for key, item in value.items()
}
if isinstance(value, tuple):
return tuple(_sanitize_value(item) for item in value)
if isinstance(value, list):
return [_sanitize_value(item) for item in value]
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
return [_sanitize_value(item) for item in value]
return value
class SensitiveDataFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
record.msg = _sanitize_value(record.msg)
if record.args:
record.args = _sanitize_value(record.args)
return True
def setup_logging(log_level: str) -> None:
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"filters": {
"sensitive_data_filter": {
"()": "app.core.logging.SensitiveDataFilter",
}
},
"formatters": {
"standard": {
"format": "%(asctime)s %(levelname)s [%(name)s] %(message)s",
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"filters": ["sensitive_data_filter"],
"formatter": "standard",
}
},
"root": {
"level": log_level.upper(),
"handlers": ["console"],
},
}
)