import time import json import threading from django.utils.deprecation import MiddlewareMixin from django.conf import settings SENSITIVE_FIELDS = { 'password', 'password1', 'password2', 'old_password', 'new_password', 'token', 'access_token', 'refresh_token', 'secret', 'api_key', 'authorization', 'credit_card', 'cvv', 'ssn', } EXCLUDE_PATHS = [ '/static/', '/media/', '/favicon.ico', '/admin/jsi18n/', '/swagger/', '/redoc/', ] def _get_client_ip(request): x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: return x_forwarded_for.split(',')[0].strip() return request.META.get('REMOTE_ADDR') def _mask_sensitive_data(data): if not data: return data try: if isinstance(data, str): try: data = json.loads(data) except (json.JSONDecodeError, TypeError): return data[:2000] if isinstance(data, dict): masked = {} for key, value in data.items(): if any(s in key.lower() for s in SENSITIVE_FIELDS): masked[key] = '***' elif isinstance(value, dict): masked[key] = _mask_sensitive_data(value) elif isinstance(value, list): masked[key] = [_mask_sensitive_data(item) if isinstance(item, dict) else item for item in value[:10]] else: masked[key] = value return json.dumps(masked, ensure_ascii=False)[:2000] return str(data)[:2000] except Exception: return '' def _extract_app_name(path): parts = path.strip('/').split('/') if parts and parts[0]: return parts[0] return 'root' def _save_log_async(log_data): def _save(): try: from .models import ApiRequestLog ApiRequestLog.objects.create(**log_data) except Exception: pass thread = threading.Thread(target=_save) thread.daemon = True thread.start() class RequestLoggingMiddleware(MiddlewareMixin): def __init__(self, get_response): super().__init__(get_response) self.get_response = get_response def __call__(self, request): if any(request.path.startswith(path) for path in EXCLUDE_PATHS): return self.get_response(request) start_time = time.time() try: user = request.user if hasattr(request, 'user') and request.user.is_authenticated else None except Exception: user = None response = self.get_response(request) try: duration_ms = (time.time() - start_time) * 1000 request_body = '' if request.method in ('POST', 'PUT', 'PATCH'): try: if hasattr(request, 'body'): body = request.body.decode('utf-8', errors='ignore') request_body = _mask_sensitive_data(body) except Exception: request_body = '' response_size = 0 if hasattr(response, 'content'): response_size = len(response.content) log_data = { 'user': user, 'username': user.username if user else (request.user.username if hasattr(request, 'user') and hasattr(request.user, 'username') else ''), 'ip_address': _get_client_ip(request), 'user_agent': request.META.get('HTTP_USER_AGENT', '')[:500], 'method': request.method, 'path': request.path[:255], 'query_string': request.META.get('QUERY_STRING', '')[:1000], 'request_body': request_body, 'status_code': response.status_code, 'response_size': response_size, 'duration_ms': round(duration_ms, 2), 'app_name': _extract_app_name(request.path), 'is_error': response.status_code >= 400, } _save_log_async(log_data) except Exception: pass return response