Browse Source

feat(api): Generalize desecapi-specific exception handler, add OSError

This is to catch OSError, which includes socket.gaierror (from getaddrinfo()
syscall) and other error conditions, like I/O failures, disk full etc.
Peter Thomassen 6 years ago
parent
commit
33508369b6
2 changed files with 19 additions and 10 deletions
  1. 1 1
      api/api/settings.py
  2. 18 9
      api/desecapi/exception_handlers.py

+ 1 - 1
api/api/settings.py

@@ -88,7 +88,7 @@ REST_FRAMEWORK = {
         'desecapi.authentication.TokenAuthentication',
     ),
     'TEST_REQUEST_DEFAULT_FORMAT': 'json',
-    'EXCEPTION_HANDLER': 'desecapi.exception_handlers.handle_db_unavailable',
+    'EXCEPTION_HANDLER': 'desecapi.exception_handlers.exception_handler',
     'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
     'ALLOWED_VERSIONS': ['v1', 'v2'],
 }

+ 18 - 9
api/desecapi/exception_handlers.py

@@ -1,17 +1,27 @@
 from django.db.utils import OperationalError
 from rest_framework import status
 from rest_framework.response import Response
-from rest_framework.views import exception_handler
+from rest_framework.views import exception_handler as drf_exception_handler
 import logging
 
 
-def handle_db_unavailable(exc, context):
+def exception_handler(exc, context):
     """
     desecapi specific exception handling. If no special treatment is applied,
     we default to restframework's exception handling. See also
     https://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling
     """
 
+    def _perform_handling(name):
+        logger = logging.getLogger('django.request')
+        logger.error('{} Supplementary Information'.format(name),
+                     exc_info=exc, stack_info=False)
+
+        # Gracefully let clients know that we cannot connect to the database
+        return Response({'detail': 'Please try again later.'},
+                        status=status.HTTP_503_SERVICE_UNAVAILABLE)
+
+    # Catch DB exception and log an extra error for additional context
     if isinstance(exc, OperationalError):
         if isinstance(exc.args, (list, dict, tuple)) and exc.args and \
             exc.args[0] in (
@@ -22,12 +32,11 @@ def handle_db_unavailable(exc, context):
                 2009,  # Wrong host info
                 2026,  # SSL connection error
         ):
-            logging.getLogger('django.request').error('OperationalError Supplementary Information',
-                                                      exc_info=exc, stack_info=False)
-
-            # Gracefully let clients know that we cannot connect to the database
-            data = {'detail': 'Please try again later.'}
+            return _perform_handling('OperationalError')
 
-            return Response(data, status=status.HTTP_503_SERVICE_UNAVAILABLE)
+    # OSError happens on system-related errors, like full disk or getaddrinfo() failure.
+    # Catch it and log an extra error for additional context.
+    if isinstance(exc, OSError):
+        return _perform_handling('OSError')
 
-    return exception_handler(exc, context)
+    return drf_exception_handler(exc, context)