sync from local backup
This commit is contained in:
+234
@@ -0,0 +1,234 @@
|
||||
from django.db.models import Count, Avg, Max, Q, F
|
||||
from django.db.models.functions import TruncHour, TruncDate
|
||||
from django.utils import timezone
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated, IsAdminUser
|
||||
from rest_framework.response import Response
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||
|
||||
from .models import ApiRequestLog, ErrorLog, SystemEventLog
|
||||
from .serializers import (
|
||||
ApiRequestLogSerializer, ApiRequestLogListSerializer,
|
||||
ErrorLogSerializer, ErrorLogListSerializer, ErrorLogResolveSerializer,
|
||||
SystemEventLogSerializer, SystemEventLogListSerializer,
|
||||
)
|
||||
from .filters import ApiRequestLogFilter, ErrorLogFilter, SystemEventLogFilter
|
||||
|
||||
|
||||
class ApiRequestLogViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = ApiRequestLog.objects.all()
|
||||
permission_classes = [IsAuthenticated, IsAdminUser]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = ApiRequestLogFilter
|
||||
search_fields = ['path', 'username', 'ip_address']
|
||||
ordering_fields = ['timestamp', 'duration_ms', 'status_code']
|
||||
ordering = ['-timestamp']
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'list':
|
||||
return ApiRequestLogListSerializer
|
||||
return ApiRequestLogSerializer
|
||||
|
||||
|
||||
class ErrorLogViewSet(viewsets.ModelViewSet):
|
||||
queryset = ErrorLog.objects.all()
|
||||
permission_classes = [IsAuthenticated, IsAdminUser]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = ErrorLogFilter
|
||||
search_fields = ['message', 'module', 'exception_type', 'path']
|
||||
ordering_fields = ['timestamp', 'occurrence_count']
|
||||
ordering = ['-timestamp']
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'list':
|
||||
return ErrorLogListSerializer
|
||||
if self.action == 'resolve':
|
||||
return ErrorLogResolveSerializer
|
||||
return ErrorLogSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
if self.action in ('update', 'partial_update', 'destroy'):
|
||||
return ErrorLog.objects.all()
|
||||
return ErrorLog.objects.all()
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def resolve(self, request, pk=None):
|
||||
error_log = self.get_object()
|
||||
error_log.is_resolved = True
|
||||
error_log.resolved_at = timezone.now()
|
||||
error_log.save(update_fields=['is_resolved', 'resolved_at'])
|
||||
return Response({'status': 'resolved'})
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def resolve_all(self, request):
|
||||
ids = request.data.get('ids', [])
|
||||
if ids:
|
||||
ErrorLog.objects.filter(id__in=ids, is_resolved=False).update(
|
||||
is_resolved=True,
|
||||
resolved_at=timezone.now()
|
||||
)
|
||||
return Response({'status': 'resolved', 'count': len(ids)})
|
||||
return Response({'error': '请提供 ids 列表'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class SystemEventLogViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = SystemEventLog.objects.select_related('user', 'target_user').all()
|
||||
permission_classes = [IsAuthenticated, IsAdminUser]
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = SystemEventLogFilter
|
||||
search_fields = ['description', 'user__username']
|
||||
ordering_fields = ['timestamp']
|
||||
ordering = ['-timestamp']
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'list':
|
||||
return SystemEventLogListSerializer
|
||||
return SystemEventLogSerializer
|
||||
|
||||
|
||||
class LogStatsViewSet(viewsets.ViewSet):
|
||||
permission_classes = [IsAuthenticated, IsAdminUser]
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def summary(self, request):
|
||||
days = int(request.query_params.get('days', 7))
|
||||
start_date = timezone.now() - timezone.timedelta(days=days)
|
||||
|
||||
request_qs = ApiRequestLog.objects.filter(timestamp__gte=start_date)
|
||||
error_qs = ErrorLog.objects.filter(timestamp__gte=start_date)
|
||||
|
||||
total_requests = request_qs.count()
|
||||
total_errors = request_qs.filter(is_error=True).count()
|
||||
error_rate = (total_errors / total_requests * 100) if total_requests > 0 else 0
|
||||
|
||||
duration_stats = request_qs.aggregate(
|
||||
avg_duration=Avg('duration_ms'),
|
||||
max_duration=Max('duration_ms')
|
||||
)
|
||||
|
||||
unique_users = request_qs.exclude(user__isnull=True).values('user').distinct().count()
|
||||
unique_ips = request_qs.exclude(ip_address__isnull=True).values('ip_address').distinct().count()
|
||||
|
||||
top_status_codes = (
|
||||
request_qs.values('status_code')
|
||||
.annotate(count=Count('id'))
|
||||
.order_by('-count')[:10]
|
||||
)
|
||||
|
||||
today_start = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
error_count_today = error_qs.filter(timestamp__gte=today_start).count()
|
||||
request_count_today = request_qs.filter(timestamp__gte=today_start).count()
|
||||
|
||||
return Response({
|
||||
'total_requests': total_requests,
|
||||
'total_errors': total_errors,
|
||||
'error_rate': round(error_rate, 2),
|
||||
'avg_duration_ms': round(duration_stats['avg_duration'] or 0, 2),
|
||||
'max_duration_ms': round(duration_stats['max_duration'] or 0, 2),
|
||||
'unique_users': unique_users,
|
||||
'unique_ips': unique_ips,
|
||||
'top_status_codes': list(top_status_codes),
|
||||
'error_count_today': error_count_today,
|
||||
'request_count_today': request_count_today,
|
||||
})
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def by_path(self, request):
|
||||
days = int(request.query_params.get('days', 7))
|
||||
limit = int(request.query_params.get('limit', 20))
|
||||
start_date = timezone.now() - timezone.timedelta(days=days)
|
||||
|
||||
stats = (
|
||||
ApiRequestLog.objects
|
||||
.filter(timestamp__gte=start_date)
|
||||
.values('path')
|
||||
.annotate(
|
||||
request_count=Count('id'),
|
||||
error_count=Count('id', filter=Q(is_error=True)),
|
||||
avg_duration_ms=Avg('duration_ms'),
|
||||
)
|
||||
.order_by('-request_count')[:limit]
|
||||
)
|
||||
|
||||
result = []
|
||||
for item in stats:
|
||||
item['error_rate'] = round(
|
||||
(item['error_count'] / item['request_count'] * 100) if item['request_count'] > 0 else 0, 2
|
||||
)
|
||||
item['avg_duration_ms'] = round(item['avg_duration_ms'] or 0, 2)
|
||||
result.append(item)
|
||||
|
||||
return Response(result)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def by_hour(self, request):
|
||||
days = int(request.query_params.get('days', 1))
|
||||
start_date = timezone.now() - timezone.timedelta(days=days)
|
||||
|
||||
stats = (
|
||||
ApiRequestLog.objects
|
||||
.filter(timestamp__gte=start_date)
|
||||
.annotate(hour=TruncHour('timestamp'))
|
||||
.values('hour')
|
||||
.annotate(
|
||||
request_count=Count('id'),
|
||||
error_count=Count('id', filter=Q(is_error=True)),
|
||||
avg_duration_ms=Avg('duration_ms'),
|
||||
)
|
||||
.order_by('hour')
|
||||
)
|
||||
|
||||
result = []
|
||||
for item in stats:
|
||||
item['avg_duration_ms'] = round(item['avg_duration_ms'] or 0, 2)
|
||||
result.append(item)
|
||||
|
||||
return Response(result)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def slow_requests(self, request):
|
||||
days = int(request.query_params.get('days', 7))
|
||||
threshold = float(request.query_params.get('threshold', 1000))
|
||||
limit = int(request.query_params.get('limit', 50))
|
||||
start_date = timezone.now() - timezone.timedelta(days=days)
|
||||
|
||||
slow_requests = (
|
||||
ApiRequestLog.objects
|
||||
.filter(timestamp__gte=start_date, duration_ms__gte=threshold)
|
||||
.values('path', 'method')
|
||||
.annotate(
|
||||
count=Count('id'),
|
||||
avg_duration_ms=Avg('duration_ms'),
|
||||
max_duration_ms=Max('duration_ms'),
|
||||
)
|
||||
.order_by('-avg_duration_ms')[:limit]
|
||||
)
|
||||
|
||||
result = []
|
||||
for item in slow_requests:
|
||||
item['avg_duration_ms'] = round(item['avg_duration_ms'] or 0, 2)
|
||||
item['max_duration_ms'] = round(item['max_duration_ms'] or 0, 2)
|
||||
result.append(item)
|
||||
|
||||
return Response(result)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def error_types(self, request):
|
||||
days = int(request.query_params.get('days', 7))
|
||||
limit = int(request.query_params.get('limit', 20))
|
||||
start_date = timezone.now() - timezone.timedelta(days=days)
|
||||
|
||||
error_types = (
|
||||
ErrorLog.objects
|
||||
.filter(timestamp__gte=start_date)
|
||||
.values('exception_type', 'module')
|
||||
.annotate(
|
||||
count=Count('id'),
|
||||
last_occurred=Max('timestamp'),
|
||||
)
|
||||
.order_by('-count')[:limit]
|
||||
)
|
||||
|
||||
return Response(list(error_types))
|
||||
Reference in New Issue
Block a user