From fba043fedff8902d23c0c2ce631990bc8f96b358 Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Mon, 25 May 2020 09:16:37 +0200 Subject: [PATCH] Log APIExceptions on the server --- openslides/utils/rest_api.py | 27 ++++++++++++++++++++++++++- openslides/utils/views.py | 4 ++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/openslides/utils/rest_api.py b/openslides/utils/rest_api.py index 59edc07ae..e57c87e24 100644 --- a/openslides/utils/rest_api.py +++ b/openslides/utils/rest_api.py @@ -6,6 +6,7 @@ from django.db.models import Model from django.http import Http404 from rest_framework import status from rest_framework.decorators import detail_route, list_route +from rest_framework.exceptions import APIException from rest_framework.metadata import SimpleMetadata from rest_framework.mixins import ( CreateModelMixin as _CreateModelMixin, @@ -44,6 +45,7 @@ from rest_framework.viewsets import ( ModelViewSet as _ModelViewSet, ) +from . import logging from .access_permissions import BaseAccessPermissions from .cache import element_cache @@ -70,6 +72,7 @@ __all__ = [ router = DefaultRouter() +error_logger = logging.getLogger("openslides.requests.errors") class IdManyRelatedField(ManyRelatedField): @@ -129,6 +132,27 @@ class IdPrimaryKeyRelatedField(PrimaryKeyRelatedField): return IdManyRelatedField(**list_kwargs) +class ErrorLoggingMixin: + def handle_exception(self, exc: Any) -> Response: + user_id = self.request.user.pk or 0 # type: ignore + path = self.request._request.get_full_path() # type: ignore + prefix = f"{path} {user_id}" + if isinstance(exc, APIException): + detail = self._detail_to_string(exc.detail) + error_logger.warn(f"{prefix} {str(detail)}") + else: + error_logger.warn(f"{prefix} unknown exception: {exc}") + return super().handle_exception(exc) # type: ignore + + def _detail_to_string(self, detail: Any) -> Any: + if isinstance(detail, list): + return [self._detail_to_string(item) for item in detail] + elif isinstance(detail, dict): + return {key: self._detail_to_string(value) for key, value in detail.items()} + else: + return str(detail) + + class PermissionMixin: """ Mixin for subclasses of APIView like GenericViewSet and ModelViewSet. @@ -324,11 +348,12 @@ class UpdateModelMixin(_UpdateModelMixin): return response -class GenericViewSet(PermissionMixin, _GenericViewSet): +class GenericViewSet(ErrorLoggingMixin, PermissionMixin, _GenericViewSet): pass class ModelViewSet( + ErrorLoggingMixin, PermissionMixin, ListModelMixin, RetrieveModelMixin, diff --git a/openslides/utils/views.py b/openslides/utils/views.py index 015a1b492..78646244e 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -3,10 +3,10 @@ from typing import Any, Dict, List, Set from django.db import models, transaction from rest_framework.views import APIView as _APIView -from .rest_api import Response, ValidationError +from .rest_api import ErrorLoggingMixin, Response, ValidationError -class APIView(_APIView): +class APIView(ErrorLoggingMixin, _APIView): """ The Django Rest framework APIView with improvements for OpenSlides. """