67 lines
2.1 KiB
Python
67 lines
2.1 KiB
Python
"""采购工具日志的最小脱敏策略。"""
|
|||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import logging
|
||
|
|
from pathlib import Path
|
||
|
|
import re
|
||
|
|
|
||
|
|
from .runtime import RuntimePaths
|
||
|
|
|
||
|
|
|
||
|
|
LOGGER_NAME = "cmbuyer_client"
|
||
|
|
REDACTED = "[已隐藏]"
|
||
|
|
|
||
|
|
_SENSITIVE_KEY_PATTERN = (
|
||
|
|
r"token|authorization|password|secret|api[_-]?key|"
|
||
|
|
r"address|phone|mobile|payment|pay|card|bank[_-]?account|"
|
||
|
|
r"令牌|授权|密码|密钥|地址|手机号|电话|支付|银行卡"
|
||
|
|
)
|
||
|
|
_KEY_VALUE_PATTERN = re.compile(
|
||
|
|
rf"(?P<key>{_SENSITIVE_KEY_PATTERN})\s*(?P<separator>[:=])\s*"
|
||
|
|
r"(?P<value>\"[^\"]*\"|'[^']*'|[^\s,;]+)",
|
||
|
|
flags=re.IGNORECASE,
|
||
|
|
)
|
||
|
|
_PHONE_PATTERN = re.compile(r"(?<!\d)1[3-9]\d{9}(?!\d)")
|
||
|
|
|
||
|
|
|
||
|
|
def redact_text(message: str) -> str:
|
||
|
|
"""移除日志文本中的凭据、地址、手机号和支付字段值。"""
|
||
|
|
|
||
|
|
def replace_key_value(match: re.Match[str]) -> str:
|
||
|
|
return f"{match.group('key')}{match.group('separator')}{REDACTED}"
|
||
|
|
|
||
|
|
redacted = _KEY_VALUE_PATTERN.sub(replace_key_value, message)
|
||
|
|
return _PHONE_PATTERN.sub(REDACTED, redacted)
|
||
|
|
|
||
|
|
|
||
|
|
class SensitiveDataFilter(logging.Filter):
|
||
|
|
"""在任何 handler 格式化记录前,清除敏感字段。
|
||
|
|
|
||
|
|
该过滤器在日志写入前替换 ``msg`` 与 ``args``,确保文件 handler 不会得到原始值。
|
||
|
|
"""
|
||
|
|
|
||
|
|
def filter(self, record: logging.LogRecord) -> bool:
|
||
|
|
record.msg = redact_text(record.getMessage())
|
||
|
|
record.args = ()
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def configure_application_logger(paths: RuntimePaths) -> logging.Logger:
|
||
|
|
"""配置唯一的 UTF-8 文件日志,并确保其先经过脱敏过滤。"""
|
||
|
|
|
||
|
|
paths.ensure_exists()
|
||
|
|
logger = logging.getLogger(LOGGER_NAME)
|
||
|
|
logger.setLevel(logging.INFO)
|
||
|
|
logger.propagate = False
|
||
|
|
|
||
|
|
for handler in tuple(logger.handlers):
|
||
|
|
logger.removeHandler(handler)
|
||
|
|
handler.close()
|
||
|
|
|
||
|
|
handler = logging.FileHandler(Path(paths.logs) / "client.log", encoding="utf-8")
|
||
|
|
handler.addFilter(SensitiveDataFilter())
|
||
|
|
handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
|
||
|
|
logger.addHandler(handler)
|
||
|
|
return logger
|