Files
xiaobaifupan/next/backend/bootstrap/logging.py
T

80 lines
2.5 KiB
Python

from __future__ import annotations
import json
import logging
import re
from datetime import datetime
from logging.handlers import RotatingFileHandler
from pathlib import Path
from typing import Any
from zoneinfo import ZoneInfo
SENSITIVE_KEYS = (
"api_key",
"authorization",
"cookie",
"password",
"refresh_token",
"secret",
"token",
)
SENSITIVE_VALUE_PATTERN = re.compile(
r"(?i)\b(api[_-]?key|authorization|password|refresh[_-]?token|secret|token)"
r"\s*[:=]\s*[^\s,;]+"
)
BEARER_PATTERN = re.compile(r"(?i)\bbearer\s+[a-z0-9._~+/-]{8,}")
SHANGHAI_TIMEZONE = ZoneInfo("Asia/Shanghai")
def redact(value: Any) -> Any:
if isinstance(value, dict):
return {
str(key): "[REDACTED]" if _is_sensitive(str(key)) else redact(item)
for key, item in value.items()
}
if isinstance(value, (list, tuple)):
return [redact(item) for item in value]
if isinstance(value, str):
safe = SENSITIVE_VALUE_PATTERN.sub(lambda match: f"{match.group(1)}=[REDACTED]", value)
return BEARER_PATTERN.sub("Bearer [REDACTED]", safe)
return value
def _is_sensitive(key: str) -> bool:
normalized = key.lower().replace("-", "_")
return any(marker in normalized for marker in SENSITIVE_KEYS)
class JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
payload: dict[str, Any] = {
"timestamp": datetime.now(SHANGHAI_TIMEZONE).isoformat(timespec="milliseconds"),
"level": record.levelname.lower(),
"logger": record.name,
"message": record.getMessage(),
}
for field in ("request_id", "event", "context"):
if hasattr(record, field):
payload[field] = redact(getattr(record, field))
if record.exc_info:
payload["exception"] = record.exc_info[0].__name__
return json.dumps(redact(payload), ensure_ascii=False, separators=(",", ":"))
def configure_logging(level: str, log_file: Path | None) -> None:
handlers: list[logging.Handler] = [logging.StreamHandler()]
if log_file is not None:
log_file.parent.mkdir(parents=True, exist_ok=True)
handlers.append(
RotatingFileHandler(
log_file,
maxBytes=10 * 1024 * 1024,
backupCount=3,
encoding="utf-8",
)
)
formatter = JsonFormatter()
for handler in handlers:
handler.setFormatter(formatter)
logging.basicConfig(level=level, handlers=handlers, force=True)