All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m42s
- Introduced `probe_club_feature_access` to check club feature limits and log access attempts without blocking by default. - Added `_live_inventory_count` function to retrieve current counts for specific features, enhancing feature limit management. - Updated various endpoints to utilize the new probing functionality, ensuring compliance with club feature access rules. - Incremented version to 1.1.0 in version.py to reflect these enhancements in club feature management.
75 lines
2.3 KiB
Python
75 lines
2.3 KiB
Python
"""
|
|
JSON-Log für Vereins-Feature-Zugriffe (Phase 2: nur Monitoring, kein Block).
|
|
|
|
Spez: CLUB_MEMBERSHIP_AND_FEATURES.v1.md §9 Phase 2 — analog Mitai feature_logger.py.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional
|
|
|
|
|
|
def _log_dir() -> Path:
|
|
custom = (os.getenv("CLUB_FEATURE_LOG_DIR") or "").strip()
|
|
if custom:
|
|
return Path(custom)
|
|
return Path("/app/logs")
|
|
|
|
|
|
feature_usage_logger = logging.getLogger("shinkan.club_feature_usage")
|
|
feature_usage_logger.setLevel(logging.INFO)
|
|
feature_usage_logger.propagate = False
|
|
|
|
if not feature_usage_logger.handlers:
|
|
log_dir = _log_dir()
|
|
try:
|
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
log_file = log_dir / "club-feature-usage.log"
|
|
file_handler = logging.FileHandler(log_file, encoding="utf-8")
|
|
file_handler.setLevel(logging.INFO)
|
|
file_handler.setFormatter(logging.Formatter("%(message)s"))
|
|
feature_usage_logger.addHandler(file_handler)
|
|
except OSError:
|
|
# Dev ohne /app/logs: Fallback stderr
|
|
stream_handler = logging.StreamHandler()
|
|
stream_handler.setFormatter(logging.Formatter("[club-feature-usage] %(message)s"))
|
|
feature_usage_logger.addHandler(stream_handler)
|
|
|
|
|
|
def log_club_feature_usage(
|
|
*,
|
|
club_id: Optional[int],
|
|
profile_id: Optional[int],
|
|
feature_id: str,
|
|
action: str,
|
|
access: Dict[str, Any],
|
|
endpoint: Optional[str] = None,
|
|
phase: str = "probe",
|
|
) -> None:
|
|
"""
|
|
Strukturiertes JSON-Log eines Feature-Checks.
|
|
|
|
phase: probe (Phase 2, non-blocking) | enforce (Phase 4, nach Block-Entscheid)
|
|
"""
|
|
entry = {
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
"club_id": club_id,
|
|
"profile_id": profile_id,
|
|
"feature": feature_id,
|
|
"action": action,
|
|
"endpoint": endpoint,
|
|
"phase": phase,
|
|
"plan_id": access.get("plan_id"),
|
|
"used": access.get("used", 0),
|
|
"limit": access.get("limit"),
|
|
"remaining": access.get("remaining"),
|
|
"allowed": access.get("allowed", True),
|
|
"reason": access.get("reason", "unknown"),
|
|
"enforcement": os.getenv("CLUB_FEATURE_ENFORCE", "0") == "1",
|
|
}
|
|
feature_usage_logger.info(json.dumps(entry, ensure_ascii=False))
|