shinkan-jinkendo/backend/club_feature_logger.py
Lars 7cfbca40bb
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
Implement Club Feature Access Probing and Inventory Count
- 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.
2026-06-06 21:00:42 +02:00

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))