setu-trafficmonitor 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. setu_trafficmonitor-2.0.0.dist-info/LICENSE +21 -0
  2. setu_trafficmonitor-2.0.0.dist-info/METADATA +401 -0
  3. setu_trafficmonitor-2.0.0.dist-info/RECORD +42 -0
  4. setu_trafficmonitor-2.0.0.dist-info/WHEEL +5 -0
  5. setu_trafficmonitor-2.0.0.dist-info/top_level.txt +1 -0
  6. trafficmonitor/__init__.py +11 -0
  7. trafficmonitor/admin.py +217 -0
  8. trafficmonitor/analytics/__init__.py +0 -0
  9. trafficmonitor/analytics/enhanced_queries.py +286 -0
  10. trafficmonitor/analytics/serializers.py +238 -0
  11. trafficmonitor/analytics/tests.py +757 -0
  12. trafficmonitor/analytics/urls.py +18 -0
  13. trafficmonitor/analytics/views.py +694 -0
  14. trafficmonitor/apps.py +7 -0
  15. trafficmonitor/circuit_breaker.py +63 -0
  16. trafficmonitor/conf.py +154 -0
  17. trafficmonitor/dashboard_security.py +111 -0
  18. trafficmonitor/db_utils.py +37 -0
  19. trafficmonitor/exceptions.py +93 -0
  20. trafficmonitor/health.py +66 -0
  21. trafficmonitor/load_test.py +423 -0
  22. trafficmonitor/load_test_api.py +307 -0
  23. trafficmonitor/management/__init__.py +1 -0
  24. trafficmonitor/management/commands/__init__.py +1 -0
  25. trafficmonitor/management/commands/cleanup_request_logs.py +77 -0
  26. trafficmonitor/middleware.py +383 -0
  27. trafficmonitor/migrations/0001_initial.py +93 -0
  28. trafficmonitor/migrations/__init__.py +0 -0
  29. trafficmonitor/models.py +206 -0
  30. trafficmonitor/monitoring.py +104 -0
  31. trafficmonitor/permissions.py +64 -0
  32. trafficmonitor/security.py +180 -0
  33. trafficmonitor/settings_production.py +105 -0
  34. trafficmonitor/static/analytics/css/dashboard.css +99 -0
  35. trafficmonitor/static/analytics/js/dashboard-production.js +339 -0
  36. trafficmonitor/static/analytics/js/dashboard-v2.js +697 -0
  37. trafficmonitor/static/analytics/js/dashboard.js +693 -0
  38. trafficmonitor/tasks.py +137 -0
  39. trafficmonitor/templates/analytics/dashboard.html +500 -0
  40. trafficmonitor/tests.py +246 -0
  41. trafficmonitor/views.py +3 -0
  42. trafficmonitor/websocket_consumers.py +128 -0
@@ -0,0 +1,63 @@
1
+ """
2
+ Circuit breaker pattern for database resilience
3
+ """
4
+ import time
5
+ import threading
6
+ from enum import Enum
7
+ from typing import Callable, Any
8
+ import logging
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class CircuitState(Enum):
14
+ CLOSED = "closed"
15
+ OPEN = "open"
16
+ HALF_OPEN = "half_open"
17
+
18
+
19
+ class CircuitBreaker:
20
+ """Circuit breaker for database operations"""
21
+
22
+ def __init__(self, failure_threshold: int = 5, timeout: int = 60):
23
+ self.failure_threshold = failure_threshold
24
+ self.timeout = timeout
25
+ self.failure_count = 0
26
+ self.last_failure_time = None
27
+ self.state = CircuitState.CLOSED
28
+ self._lock = threading.Lock()
29
+
30
+ def call(self, func: Callable, *args, **kwargs) -> Any:
31
+ """Execute function with circuit breaker protection"""
32
+ with self._lock:
33
+ if self.state == CircuitState.OPEN:
34
+ if time.time() - self.last_failure_time > self.timeout:
35
+ self.state = CircuitState.HALF_OPEN
36
+ else:
37
+ raise Exception("Circuit breaker is OPEN")
38
+
39
+ try:
40
+ result = func(*args, **kwargs)
41
+ self._on_success()
42
+ return result
43
+ except Exception as e:
44
+ self._on_failure()
45
+ raise e
46
+
47
+ def _on_success(self):
48
+ """Reset circuit breaker on successful operation"""
49
+ self.failure_count = 0
50
+ self.state = CircuitState.CLOSED
51
+
52
+ def _on_failure(self):
53
+ """Handle failure in circuit breaker"""
54
+ self.failure_count += 1
55
+ self.last_failure_time = time.time()
56
+
57
+ if self.failure_count >= self.failure_threshold:
58
+ self.state = CircuitState.OPEN
59
+ logger.error(f"Circuit breaker opened after {self.failure_count} failures")
60
+
61
+
62
+ # Global circuit breaker instance
63
+ db_circuit_breaker = CircuitBreaker()
trafficmonitor/conf.py ADDED
@@ -0,0 +1,154 @@
1
+ """
2
+ TrafficMonitor Configuration
3
+ Enterprise-grade configuration management with environment support.
4
+ """
5
+ import os
6
+ from django.conf import settings
7
+ from typing import List, Dict, Any
8
+
9
+
10
+ class TrafficMonitorConfig:
11
+ """
12
+ Centralized configuration for TrafficMonitor.
13
+ Supports environment variables and Django settings override.
14
+ """
15
+
16
+ # Environment Detection
17
+ ENVIRONMENT = os.getenv('DJANGO_ENV', getattr(settings, 'ENVIRONMENT', 'development'))
18
+ IS_PRODUCTION = ENVIRONMENT.lower() in ['production', 'prod']
19
+
20
+ # Role-based Access Control
21
+ VIEWER_ROLES = getattr(settings, 'TRAFFICMONITOR_VIEWER_ROLES',
22
+ os.getenv('TRAFFICMONITOR_VIEWER_ROLES', 'analyst,viewer,developer').split(','))
23
+ ADMIN_ROLES = getattr(settings, 'TRAFFICMONITOR_ADMIN_ROLES',
24
+ os.getenv('TRAFFICMONITOR_ADMIN_ROLES', 'admin,superuser').split(','))
25
+
26
+ # Authentication Headers
27
+ USER_ID_HEADER = getattr(settings, 'TRAFFICMONITOR_USER_ID_HEADER',
28
+ os.getenv('TRAFFICMONITOR_USER_ID_HEADER', 'X-User-ID'))
29
+ USER_ROLE_HEADER = getattr(settings, 'TRAFFICMONITOR_USER_ROLE_HEADER',
30
+ os.getenv('TRAFFICMONITOR_USER_ROLE_HEADER', 'X-User-Role'))
31
+
32
+ # Data Retention
33
+ RETENTION_DAYS = int(getattr(settings, 'TRAFFICMONITOR_RETENTION_DAYS',
34
+ os.getenv('TRAFFICMONITOR_RETENTION_DAYS', '90')))
35
+
36
+ # Performance Settings
37
+ ASYNC_LOGGING = getattr(settings, 'TRAFFICMONITOR_ASYNC_LOGGING',
38
+ os.getenv('TRAFFICMONITOR_ASYNC_LOGGING', 'false').lower() == 'true')
39
+
40
+ ENABLE_SAMPLING = getattr(settings, 'TRAFFICMONITOR_ENABLE_SAMPLING',
41
+ os.getenv('TRAFFICMONITOR_ENABLE_SAMPLING', 'false').lower() == 'true')
42
+
43
+ SAMPLE_RATE = float(getattr(settings, 'TRAFFICMONITOR_SAMPLE_RATE',
44
+ os.getenv('TRAFFICMONITOR_SAMPLE_RATE', '1.0')))
45
+
46
+ # Database Settings
47
+ BULK_CREATE_BATCH_SIZE = int(getattr(settings, 'TRAFFICMONITOR_BULK_BATCH_SIZE',
48
+ os.getenv('TRAFFICMONITOR_BULK_BATCH_SIZE', '1000')))
49
+
50
+ # Circuit Breaker Settings
51
+ CIRCUIT_BREAKER_ENABLED = getattr(settings, 'TRAFFICMONITOR_CIRCUIT_BREAKER_ENABLED',
52
+ os.getenv('TRAFFICMONITOR_CIRCUIT_BREAKER_ENABLED', 'true').lower() == 'true')
53
+
54
+ CIRCUIT_BREAKER_FAILURE_THRESHOLD = int(getattr(settings, 'TRAFFICMONITOR_CB_FAILURE_THRESHOLD',
55
+ os.getenv('TRAFFICMONITOR_CB_FAILURE_THRESHOLD', '5')))
56
+
57
+ # Security Settings
58
+ ENABLE_PII_SANITIZATION = getattr(settings, 'TRAFFICMONITOR_ENABLE_PII_SANITIZATION',
59
+ os.getenv('TRAFFICMONITOR_ENABLE_PII_SANITIZATION', 'true').lower() == 'true')
60
+
61
+ MAX_BODY_LENGTH = int(getattr(settings, 'TRAFFICMONITOR_MAX_BODY_LENGTH',
62
+ os.getenv('TRAFFICMONITOR_MAX_BODY_LENGTH', '10000')))
63
+
64
+ # Excluded Paths (production optimized)
65
+ DEFAULT_EXCLUDED_PATHS = [
66
+ '/admin/jsi18n/',
67
+ '/static/',
68
+ '/media/',
69
+ '/__debug__/',
70
+ '/favicon.ico',
71
+ '/health/',
72
+ '/healthz/',
73
+ '/metrics/',
74
+ '/prometheus/',
75
+ ]
76
+
77
+ EXCLUDED_PATHS = getattr(settings, 'TRAFFICMONITOR_EXCLUDED_PATHS', DEFAULT_EXCLUDED_PATHS)
78
+
79
+ # Monitoring & Alerting
80
+ ENABLE_METRICS = getattr(settings, 'TRAFFICMONITOR_ENABLE_METRICS',
81
+ os.getenv('TRAFFICMONITOR_ENABLE_METRICS', 'true').lower() == 'true')
82
+
83
+ ALERT_ERROR_THRESHOLD = int(getattr(settings, 'TRAFFICMONITOR_ALERT_ERROR_THRESHOLD',
84
+ os.getenv('TRAFFICMONITOR_ALERT_ERROR_THRESHOLD', '100')))
85
+
86
+ @classmethod
87
+ def validate_configuration(cls) -> List[str]:
88
+ """Validate configuration and return list of issues"""
89
+ issues = []
90
+
91
+ if cls.SAMPLE_RATE < 0 or cls.SAMPLE_RATE > 1:
92
+ issues.append("SAMPLE_RATE must be between 0 and 1")
93
+
94
+ if cls.RETENTION_DAYS < 1:
95
+ issues.append("RETENTION_DAYS must be positive")
96
+
97
+ if cls.BULK_CREATE_BATCH_SIZE < 1:
98
+ issues.append("BULK_CREATE_BATCH_SIZE must be positive")
99
+
100
+ return issues
101
+
102
+ @classmethod
103
+ def get_production_settings(cls) -> Dict[str, Any]:
104
+ """Get recommended production settings"""
105
+ return {
106
+ 'ASYNC_LOGGING': True,
107
+ 'ENABLE_SAMPLING': True,
108
+ 'SAMPLE_RATE': 0.1, # 10% sampling in production
109
+ 'CIRCUIT_BREAKER_ENABLED': True,
110
+ 'ENABLE_PII_SANITIZATION': True,
111
+ 'ENABLE_METRICS': True,
112
+ 'BULK_CREATE_BATCH_SIZE': 5000,
113
+ }
114
+
115
+ @classmethod
116
+ def is_user_authorized(cls, user_role, required_roles=None):
117
+ """
118
+ Check if user role has access.
119
+
120
+ Args:
121
+ user_role: Role from header (X-User-Role)
122
+ required_roles: List of required roles (defaults to VIEWER_ROLES + ADMIN_ROLES)
123
+
124
+ Returns:
125
+ Boolean indicating if user is authorized
126
+ """
127
+ if not user_role:
128
+ return False
129
+
130
+ if required_roles is None:
131
+ required_roles = cls.VIEWER_ROLES + cls.ADMIN_ROLES
132
+
133
+ return user_role.lower() in [r.lower() for r in required_roles]
134
+
135
+ @classmethod
136
+ def is_admin(cls, user_role):
137
+ """Check if user has admin role"""
138
+ return cls.is_user_authorized(user_role, cls.ADMIN_ROLES)
139
+
140
+ @classmethod
141
+ def get_user_info_from_request(cls, request):
142
+ """
143
+ Extract user ID and role from request headers.
144
+
145
+ Returns:
146
+ dict: {'user_id': str, 'role': str}
147
+ """
148
+ user_id = request.META.get(f'HTTP_{cls.USER_ID_HEADER.replace("-", "_").upper()}')
149
+ user_role = request.META.get(f'HTTP_{cls.USER_ROLE_HEADER.replace("-", "_").upper()}')
150
+
151
+ return {
152
+ 'user_id': user_id,
153
+ 'role': user_role
154
+ }
@@ -0,0 +1,111 @@
1
+ """
2
+ Dashboard security middleware and utilities
3
+ """
4
+ from django.http import HttpResponseForbidden, JsonResponse
5
+ from django.utils.deprecation import MiddlewareMixin
6
+ from django.views.decorators.csrf import csrf_exempt
7
+ from django.views.decorators.clickjacking import xframe_options_deny
8
+ from django.views.decorators.cache import never_cache
9
+ from functools import wraps
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class DashboardSecurityMiddleware(MiddlewareMixin):
16
+ """Security middleware specifically for dashboard endpoints"""
17
+
18
+ def process_request(self, request):
19
+ # Only apply to analytics endpoints
20
+ if not request.path.startswith('/analytics/') and not request.path.startswith('/api/analytics/'):
21
+ return None
22
+
23
+ # Add security headers
24
+ return None
25
+
26
+ def process_response(self, request, response):
27
+ # Only apply to analytics endpoints
28
+ if not request.path.startswith('/analytics/') and not request.path.startswith('/api/analytics/'):
29
+ return response
30
+
31
+ # Security headers for dashboard
32
+ response['X-Content-Type-Options'] = 'nosniff'
33
+ response['X-Frame-Options'] = 'DENY'
34
+ response['X-XSS-Protection'] = '1; mode=block'
35
+ response['Referrer-Policy'] = 'strict-origin-when-cross-origin'
36
+
37
+ # CSP for dashboard
38
+ csp = (
39
+ "default-src 'self'; "
40
+ "script-src 'self' 'unsafe-inline'; "
41
+ "style-src 'self' 'unsafe-inline'; "
42
+ "img-src 'self' data:; "
43
+ "font-src 'self'; "
44
+ "connect-src 'self'; "
45
+ "frame-ancestors 'none';"
46
+ )
47
+ response['Content-Security-Policy'] = csp
48
+
49
+ return response
50
+
51
+
52
+ def dashboard_auth_required(view_func):
53
+ """
54
+ Decorator for dashboard views requiring authentication
55
+ """
56
+ @wraps(view_func)
57
+ def wrapper(request, *args, **kwargs):
58
+ from trafficmonitor.conf import TrafficMonitorConfig
59
+
60
+ # Get user info from headers
61
+ user_info = TrafficMonitorConfig.get_user_info_from_request(request)
62
+
63
+ if not TrafficMonitorConfig.is_user_authorized(user_info.get('role')):
64
+ logger.warning(f"Unauthorized dashboard access attempt from {request.META.get('REMOTE_ADDR')}")
65
+
66
+ if request.path.startswith('/api/'):
67
+ return JsonResponse({'error': 'Unauthorized'}, status=401)
68
+ else:
69
+ return HttpResponseForbidden("Access denied. Required role not found in headers.")
70
+
71
+ # Add user info to request for logging
72
+ request.trafficmonitor_user = user_info
73
+
74
+ return view_func(request, *args, **kwargs)
75
+
76
+ return wrapper
77
+
78
+
79
+ def rate_limit_dashboard(max_requests=100, window_seconds=3600):
80
+ """
81
+ Simple rate limiting for dashboard endpoints
82
+ """
83
+ def decorator(view_func):
84
+ @wraps(view_func)
85
+ def wrapper(request, *args, **kwargs):
86
+ from django.core.cache import cache
87
+
88
+ # Get client identifier
89
+ client_ip = request.META.get('REMOTE_ADDR', 'unknown')
90
+ user_id = getattr(request, 'trafficmonitor_user', {}).get('user_id', 'anonymous')
91
+
92
+ cache_key = f"dashboard_rate_limit:{client_ip}:{user_id}"
93
+
94
+ # Get current request count
95
+ current_requests = cache.get(cache_key, 0)
96
+
97
+ if current_requests >= max_requests:
98
+ logger.warning(f"Rate limit exceeded for {client_ip}:{user_id}")
99
+
100
+ if request.path.startswith('/api/'):
101
+ return JsonResponse({'error': 'Rate limit exceeded'}, status=429)
102
+ else:
103
+ return HttpResponseForbidden("Rate limit exceeded. Please try again later.")
104
+
105
+ # Increment counter
106
+ cache.set(cache_key, current_requests + 1, timeout=window_seconds)
107
+
108
+ return view_func(request, *args, **kwargs)
109
+
110
+ return wrapper
111
+ return decorator
@@ -0,0 +1,37 @@
1
+ """
2
+ Database utilities for enterprise-grade performance
3
+ """
4
+ from django.db import transaction
5
+ from django.core.cache import cache
6
+ from typing import List, Dict, Any
7
+ import logging
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class DatabaseManager:
13
+ """Handles database operations with connection pooling and bulk operations"""
14
+
15
+ @staticmethod
16
+ @transaction.atomic
17
+ def bulk_create_logs(log_data: List[Dict[str, Any]], batch_size: int = 1000):
18
+ """Bulk create request logs with proper transaction handling"""
19
+ from trafficmonitor.models import RequestLog
20
+
21
+ logs = []
22
+ for data in log_data:
23
+ logs.append(RequestLog(**data))
24
+
25
+ # Use bulk_create with ignore_conflicts for high concurrency
26
+ RequestLog.objects.bulk_create(logs, batch_size=batch_size, ignore_conflicts=True)
27
+
28
+ @staticmethod
29
+ def get_cached_analytics(cache_key: str, query_func, ttl: int = 300):
30
+ """Cache analytics queries with proper invalidation"""
31
+ cached_result = cache.get(cache_key)
32
+ if cached_result is not None:
33
+ return cached_result
34
+
35
+ result = query_func()
36
+ cache.set(cache_key, result, ttl)
37
+ return result
@@ -0,0 +1,93 @@
1
+ """
2
+ Custom exceptions and error handling for TrafficMonitor
3
+ """
4
+ import time
5
+ import functools
6
+ from typing import Callable, Any, Type, Tuple
7
+ import logging
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class TrafficMonitorException(Exception):
13
+ """Base exception for TrafficMonitor"""
14
+ pass
15
+
16
+
17
+ class DatabaseConnectionError(TrafficMonitorException):
18
+ """Database connection related errors"""
19
+ pass
20
+
21
+
22
+ class ConfigurationError(TrafficMonitorException):
23
+ """Configuration validation errors"""
24
+ pass
25
+
26
+
27
+ class AuthenticationError(TrafficMonitorException):
28
+ """Authentication/authorization errors"""
29
+ pass
30
+
31
+
32
+ def retry_on_exception(
33
+ exceptions: Tuple[Type[Exception], ...] = (Exception,),
34
+ max_attempts: int = 3,
35
+ delay: float = 1.0,
36
+ backoff: float = 2.0
37
+ ):
38
+ """
39
+ Decorator for retrying functions on specific exceptions
40
+
41
+ Args:
42
+ exceptions: Tuple of exception types to retry on
43
+ max_attempts: Maximum number of retry attempts
44
+ delay: Initial delay between retries (seconds)
45
+ backoff: Multiplier for delay on each retry
46
+ """
47
+ def decorator(func: Callable) -> Callable:
48
+ @functools.wraps(func)
49
+ def wrapper(*args, **kwargs) -> Any:
50
+ current_delay = delay
51
+
52
+ for attempt in range(max_attempts):
53
+ try:
54
+ return func(*args, **kwargs)
55
+ except exceptions as e:
56
+ if attempt == max_attempts - 1:
57
+ logger.error(f"Function {func.__name__} failed after {max_attempts} attempts: {e}")
58
+ raise
59
+
60
+ logger.warning(f"Attempt {attempt + 1} failed for {func.__name__}: {e}. Retrying in {current_delay}s")
61
+ time.sleep(current_delay)
62
+ current_delay *= backoff
63
+
64
+ return None # Should never reach here
65
+ return wrapper
66
+ return decorator
67
+
68
+
69
+ class ErrorHandler:
70
+ """Centralized error handling"""
71
+
72
+ @staticmethod
73
+ def handle_database_error(error: Exception, context: dict) -> None:
74
+ """Handle database-related errors"""
75
+ logger.error("Database error occurred", extra={
76
+ "error_type": type(error).__name__,
77
+ "error_message": str(error),
78
+ "context": context
79
+ }, exc_info=True)
80
+
81
+ # Could integrate with alerting system here
82
+
83
+ @staticmethod
84
+ def handle_middleware_error(error: Exception, request) -> None:
85
+ """Handle middleware errors gracefully"""
86
+ logger.error("Middleware error occurred", extra={
87
+ "error_type": type(error).__name__,
88
+ "error_message": str(error),
89
+ "path": getattr(request, 'path', 'unknown'),
90
+ "method": getattr(request, 'method', 'unknown')
91
+ }, exc_info=True)
92
+
93
+ # Don't let middleware errors break the request flow
@@ -0,0 +1,66 @@
1
+ """
2
+ Health checks for TrafficMonitor
3
+ """
4
+ from django.db import connection
5
+ from django.core.cache import cache
6
+ from django.http import JsonResponse
7
+ from django.views import View
8
+ from typing import Dict, Any
9
+ import time
10
+
11
+
12
+ class HealthCheckView(View):
13
+ """Health check endpoint for load balancers"""
14
+
15
+ def get(self, request):
16
+ """Perform health checks"""
17
+ health_status = {
18
+ 'status': 'healthy',
19
+ 'timestamp': time.time(),
20
+ 'checks': {}
21
+ }
22
+
23
+ # Database check
24
+ try:
25
+ with connection.cursor() as cursor:
26
+ cursor.execute("SELECT 1")
27
+ health_status['checks']['database'] = {'status': 'healthy'}
28
+ except Exception as e:
29
+ health_status['checks']['database'] = {'status': 'unhealthy', 'error': str(e)}
30
+ health_status['status'] = 'unhealthy'
31
+
32
+ # Cache check
33
+ try:
34
+ cache.set('health_check', 'ok', 30)
35
+ cache.get('health_check')
36
+ health_status['checks']['cache'] = {'status': 'healthy'}
37
+ except Exception as e:
38
+ health_status['checks']['cache'] = {'status': 'unhealthy', 'error': str(e)}
39
+ health_status['status'] = 'unhealthy'
40
+
41
+ # Configuration check
42
+ from trafficmonitor.conf import TrafficMonitorConfig
43
+ config_issues = TrafficMonitorConfig.validate_configuration()
44
+ if config_issues:
45
+ health_status['checks']['configuration'] = {'status': 'unhealthy', 'issues': config_issues}
46
+ health_status['status'] = 'unhealthy'
47
+ else:
48
+ health_status['checks']['configuration'] = {'status': 'healthy'}
49
+
50
+ status_code = 200 if health_status['status'] == 'healthy' else 503
51
+ return JsonResponse(health_status, status=status_code)
52
+
53
+
54
+ class ReadinessCheckView(View):
55
+ """Readiness check for Kubernetes"""
56
+
57
+ def get(self, request):
58
+ """Check if application is ready to serve traffic"""
59
+ from trafficmonitor.models import RequestLog
60
+
61
+ try:
62
+ # Check if we can query the database
63
+ RequestLog.objects.first()
64
+ return JsonResponse({'status': 'ready'})
65
+ except Exception as e:
66
+ return JsonResponse({'status': 'not_ready', 'error': str(e)}, status=503)