exception_handlers.py 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import logging
  2. from django.db.utils import IntegrityError, OperationalError
  3. from rest_framework import status
  4. from rest_framework.response import Response
  5. from rest_framework.views import exception_handler as drf_exception_handler
  6. from desecapi import metrics
  7. from desecapi.exceptions import PDNSException
  8. def exception_handler(exc, context):
  9. """
  10. desecapi specific exception handling. If no special treatment is applied,
  11. we default to restframework's exception handling. See also
  12. https://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling
  13. """
  14. class_path = f"{exc.__class__.__module__}.{exc.__class__.__name__}"
  15. def _log():
  16. logger = logging.getLogger("django.request")
  17. logger.error(
  18. f"{class_path} Supplementary Information",
  19. exc_info=exc,
  20. stack_info=False,
  21. )
  22. def _409():
  23. return Response({"detail": f"Conflict: {exc}"}, status=status.HTTP_409_CONFLICT)
  24. def _500():
  25. _log()
  26. return Response(
  27. {"detail": "Internal Server Error. We're on it!"},
  28. status=status.HTTP_500_INTERNAL_SERVER_ERROR,
  29. )
  30. def _503():
  31. _log()
  32. return Response(
  33. {"detail": "Please try again later."},
  34. status=status.HTTP_503_SERVICE_UNAVAILABLE,
  35. )
  36. # Catch DB OperationalError and log an extra error for additional context
  37. if (
  38. isinstance(exc, OperationalError)
  39. and isinstance(exc.args, (list, dict, tuple))
  40. and exc.args
  41. and exc.args[0]
  42. in (
  43. 2002, # Connection refused (Socket)
  44. 2003, # Connection refused (TCP)
  45. 2005, # Unresolved host name
  46. 2007, # Server protocol mismatch
  47. 2009, # Wrong host info
  48. 2026, # SSL connection error
  49. )
  50. ):
  51. metrics.get("desecapi_database_unavailable").inc()
  52. return _503()
  53. handlers = {
  54. IntegrityError: _409,
  55. OSError: _500, # OSError happens on system-related errors, like full disk or getaddrinfo() failure.
  56. PDNSException: _500, # nslord/nsmaster returned an error
  57. }
  58. for exception_class, handler in handlers.items():
  59. if isinstance(exc, exception_class):
  60. metrics.get("desecapi_exception").labels(class_path).inc()
  61. return handler()
  62. return drf_exception_handler(exc, context)