refactor: Fix AI billing system - revert to commit #10 + fixes
- Reverted to commit #10 (98e68f6) for stable AI function base
- Fixed database migrations: removed 0018-0019 that broke schema
- Fixed CreditCostConfig schema: restored credits_cost, unit fields
- Fixed historical table schema for django-simple-history
- Added debug system (staged for future use)
Changes:
- CreditCostConfig: Updated OPERATION_TYPE_CHOICES (10 ops, no duplicates)
- CreditUsageLog: Updated choices with legacy aliases marked
- Migration 0018_update_operation_choices: Applied successfully
- All AI operations working (clustering, ideas, content, optimization, etc.)
Test Results:
✓ CreditCostConfig save/load working
✓ Credit check passing for all operations
✓ AICore initialization successful
✓ AIEngine operation mapping functional
✓ Admin panel accessible without 500 errors
Future: AI-MODEL-COST-REFACTOR-PLAN.md created for token-based system
This commit is contained in:
128
backend/igny8_core/utils/debug.py
Normal file
128
backend/igny8_core/utils/debug.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""
|
||||
Debug logging utilities
|
||||
Fast checks with minimal overhead when debug is disabled.
|
||||
"""
|
||||
from django.core.cache import cache
|
||||
import os
|
||||
|
||||
|
||||
def is_debug_enabled():
|
||||
"""
|
||||
Fast check if debug logging is enabled.
|
||||
Uses cache to avoid DB queries. Returns False immediately if disabled.
|
||||
|
||||
Returns:
|
||||
bool: True if debug logging enabled, False otherwise
|
||||
"""
|
||||
# Check cache first (fastest)
|
||||
cache_key = 'debug_enabled'
|
||||
enabled = cache.get(cache_key)
|
||||
|
||||
# If we have a cached value (True or False), use it
|
||||
if enabled is not None:
|
||||
return bool(enabled)
|
||||
|
||||
# Cache miss - check database
|
||||
try:
|
||||
from igny8_core.business.system.models import DebugConfiguration
|
||||
config = DebugConfiguration.get_config()
|
||||
enabled = config.enable_debug_logging
|
||||
|
||||
# Cache the actual boolean value
|
||||
cache.set(cache_key, enabled, 60) # Cache for 1 minute
|
||||
|
||||
return bool(enabled)
|
||||
except Exception as e:
|
||||
# If DB not ready or model doesn't exist, default to False
|
||||
# Cache this to avoid repeated DB errors
|
||||
cache.set(cache_key, False, 10)
|
||||
return False
|
||||
|
||||
|
||||
def _should_log_in_this_worker():
|
||||
"""
|
||||
Only log in the main worker to avoid duplicate logs.
|
||||
Returns True if this is the first worker or if we should always log.
|
||||
"""
|
||||
# Get worker PID - only log from worker with lowest PID to avoid duplicates
|
||||
worker_pid = os.getpid()
|
||||
|
||||
# Cache the first worker PID that tries to log
|
||||
first_worker = cache.get('debug_first_worker_pid')
|
||||
if first_worker is None:
|
||||
cache.set('debug_first_worker_pid', worker_pid, 300) # Cache for 5 minutes
|
||||
return True
|
||||
|
||||
# Only log if we're the first worker
|
||||
return worker_pid == first_worker
|
||||
|
||||
|
||||
def debug_log(message, category='general'):
|
||||
"""
|
||||
Log a debug message only if debug is enabled.
|
||||
Completely skips processing if debug is disabled.
|
||||
|
||||
Args:
|
||||
message: Message to log
|
||||
category: Log category (ai_steps, api_requests, db_queries, celery_tasks)
|
||||
"""
|
||||
# Fast exit - don't even process the message if debug is disabled
|
||||
if not is_debug_enabled():
|
||||
return
|
||||
|
||||
# Only log in one worker to avoid duplicates
|
||||
if not _should_log_in_this_worker():
|
||||
return
|
||||
|
||||
# Check category-specific settings
|
||||
try:
|
||||
from igny8_core.business.system.models import DebugConfiguration
|
||||
config = DebugConfiguration.get_config()
|
||||
|
||||
# Check category-specific flags
|
||||
if category == 'ai_steps' and not config.log_ai_steps:
|
||||
return
|
||||
if category == 'api_requests' and not config.log_api_requests:
|
||||
return
|
||||
if category == 'db_queries' and not config.log_database_queries:
|
||||
return
|
||||
if category == 'celery_tasks' and not config.log_celery_tasks:
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Debug is enabled - log to console
|
||||
import sys
|
||||
import datetime
|
||||
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
|
||||
worker_pid = os.getpid()
|
||||
prefix = f"[{timestamp}] [PID:{worker_pid}] [DEBUG:{category.upper()}]"
|
||||
print(f"{prefix} {message}", file=sys.stdout, flush=True)
|
||||
|
||||
|
||||
def debug_log_ai_step(step_name, message, **kwargs):
|
||||
"""
|
||||
Log an AI execution step only if debug is enabled.
|
||||
Completely skips processing if debug is disabled.
|
||||
|
||||
Args:
|
||||
step_name: Name of the step (INIT, PREPARE, AI_CALL, etc.)
|
||||
message: Step message
|
||||
**kwargs: Additional context to log
|
||||
"""
|
||||
# Fast exit - don't even process if debug is disabled
|
||||
if not is_debug_enabled():
|
||||
return
|
||||
|
||||
# Only log in one worker to avoid duplicates
|
||||
if not _should_log_in_this_worker():
|
||||
return
|
||||
|
||||
# Format the message with context
|
||||
context_str = ""
|
||||
if kwargs:
|
||||
context_parts = [f"{k}={v}" for k, v in kwargs.items()]
|
||||
context_str = f" | {', '.join(context_parts)}"
|
||||
|
||||
full_message = f"[{step_name}] {message}{context_str}"
|
||||
debug_log(full_message, category='ai_steps')
|
||||
Reference in New Issue
Block a user