- Create feature_logger.py with JSON logging infrastructure - Add log_feature_usage() calls to all 9 routers after check_feature_access() - Logs written to /app/logs/feature-usage.log - Tracks all usage (not just violations) for future analysis - Phase 2: Non-blocking monitoring complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
77 lines
2.7 KiB
Python
77 lines
2.7 KiB
Python
"""
|
|
Feature Usage Logger for Mitai Jinkendo
|
|
|
|
Logs all feature access checks to a separate JSON log file for analysis.
|
|
Phase 2: Non-blocking monitoring of feature usage.
|
|
"""
|
|
import logging
|
|
import json
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
|
|
# ── Setup Feature Usage Logger ───────────────────────────────────────────────
|
|
feature_usage_logger = logging.getLogger('feature_usage')
|
|
feature_usage_logger.setLevel(logging.INFO)
|
|
feature_usage_logger.propagate = False # Don't propagate to root logger
|
|
|
|
# Ensure logs directory exists
|
|
LOG_DIR = Path('/app/logs')
|
|
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
# FileHandler for JSON logs
|
|
log_file = LOG_DIR / 'feature-usage.log'
|
|
file_handler = logging.FileHandler(log_file)
|
|
file_handler.setLevel(logging.INFO)
|
|
file_handler.setFormatter(logging.Formatter('%(message)s')) # JSON only
|
|
feature_usage_logger.addHandler(file_handler)
|
|
|
|
# Also log to console in dev (optional)
|
|
# console_handler = logging.StreamHandler()
|
|
# console_handler.setFormatter(logging.Formatter('[FEATURE-USAGE] %(message)s'))
|
|
# feature_usage_logger.addHandler(console_handler)
|
|
|
|
|
|
# ── Logging Function ──────────────────────────────────────────────────────────
|
|
def log_feature_usage(user_id: str, feature_id: str, access: dict, action: str):
|
|
"""
|
|
Log feature usage in structured JSON format.
|
|
|
|
Args:
|
|
user_id: Profile UUID
|
|
feature_id: Feature identifier (e.g., 'weight_entries', 'ai_calls')
|
|
access: Result from check_feature_access() containing:
|
|
- allowed: bool
|
|
- limit: int | None
|
|
- used: int
|
|
- remaining: int | None
|
|
- reason: str
|
|
action: Type of action (e.g., 'create', 'export', 'analyze')
|
|
|
|
Example log entry:
|
|
{
|
|
"timestamp": "2026-03-20T15:30:45.123456",
|
|
"user_id": "abc-123",
|
|
"feature": "weight_entries",
|
|
"action": "create",
|
|
"used": 5,
|
|
"limit": 100,
|
|
"remaining": 95,
|
|
"allowed": true,
|
|
"reason": "within_limit"
|
|
}
|
|
"""
|
|
entry = {
|
|
"timestamp": datetime.now().isoformat(),
|
|
"user_id": user_id,
|
|
"feature": feature_id,
|
|
"action": action,
|
|
"used": access.get('used', 0),
|
|
"limit": access.get('limit'), # None for unlimited
|
|
"remaining": access.get('remaining'), # None for unlimited
|
|
"allowed": access.get('allowed', True),
|
|
"reason": access.get('reason', 'unknown')
|
|
}
|
|
|
|
feature_usage_logger.info(json.dumps(entry))
|