views.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import logging
  2. import orjson
  3. from django.core.cache import cache
  4. from django.http import HttpResponse, JsonResponse
  5. from django.views.decorators.csrf import csrf_exempt
  6. from ninja.errors import AuthenticationError
  7. from pydantic import ValidationError
  8. from sentry_sdk import capture_exception, set_context, set_level
  9. from glitchtip.api.exceptions import ThrottleException
  10. from .api import get_ip_address, get_issue_event_class
  11. from .authentication import EventAuthHttpRequest, event_auth
  12. from .schema import (
  13. EnvelopeHeaderSchema,
  14. IngestIssueEvent,
  15. InterchangeIssueEvent,
  16. ItemHeaderSchema,
  17. TransactionEventSchema,
  18. )
  19. from .tasks import ingest_event, ingest_transaction
  20. logger = logging.getLogger(__name__)
  21. def handle_validation_error(
  22. message: str, line: bytes, e: ValidationError, request: EventAuthHttpRequest
  23. ) -> JsonResponse:
  24. set_level("warning")
  25. try:
  26. set_context("incoming event", orjson.loads(line))
  27. except orjson.JSONDecodeError:
  28. pass
  29. capture_exception(e)
  30. logger.warning(f"{message} on {request.path}", exc_info=e)
  31. return JsonResponse({"detail": e.json()}, status=422)
  32. @csrf_exempt
  33. def event_envelope_view(request: EventAuthHttpRequest, project_id: int):
  34. """
  35. Envelopes can contain various types of data.
  36. GlitchTip supports issue events and transaction events.
  37. Ignore other data types.
  38. Do support multiple valid events
  39. Make as few io calls as possible. Some language SDKs (PHP) cannot run async code
  40. and will block while waiting for GlitchTip to respond.
  41. """
  42. if request.method != "POST":
  43. return JsonResponse({"detail": "Method not allowed"}, status=405)
  44. try:
  45. project = event_auth(request)
  46. except ThrottleException as e:
  47. response = HttpResponse("Too Many Requests", status=429)
  48. response["Retry-After"] = e.retry_after
  49. return response
  50. except AuthenticationError:
  51. return JsonResponse({"detail": "Denied"}, status=403)
  52. if project is None:
  53. return JsonResponse({"detail": "Denied"}, status=403)
  54. request.auth = project
  55. client_ip = get_ip_address(request)
  56. line = request.readline()
  57. try:
  58. header = EnvelopeHeaderSchema.model_validate_json(line)
  59. except ValidationError as e:
  60. return handle_validation_error(
  61. "Envelope Header validation error", line, e, request
  62. )
  63. for line in request:
  64. try:
  65. item_header = ItemHeaderSchema.model_validate_json(line)
  66. except ValidationError as e:
  67. handle_validation_error("Item Header validation error", line, e, request)
  68. request.readline() # Skip line
  69. continue
  70. line = request.readline()
  71. if item_header.type == "event":
  72. try:
  73. item = IngestIssueEvent.model_validate_json(line)
  74. except ValidationError as e:
  75. handle_validation_error("Event Item validation error", line, e, request)
  76. continue
  77. issue_event_class = get_issue_event_class(item)
  78. if item.user:
  79. item.user.ip_address = client_ip
  80. interchange_event_kwargs = {
  81. "project_id": project_id,
  82. "organization_id": project.organization_id,
  83. "payload": issue_event_class(**item.dict()),
  84. }
  85. if header.event_id:
  86. interchange_event_kwargs["event_id"] = header.event_id
  87. interchange_event = InterchangeIssueEvent(**interchange_event_kwargs)
  88. # Faux unique uuid as GlitchTip can accept duplicate UUIDs
  89. # The primary key of an event is uuid, received
  90. if cache.add("uuid" + interchange_event.event_id.hex, True) is True:
  91. ingest_event.delay(interchange_event.dict())
  92. elif item_header.type == "transaction":
  93. try:
  94. item = TransactionEventSchema.model_validate_json(line)
  95. except ValidationError as e:
  96. handle_validation_error(
  97. "Transaction Item validation error", line, e, request
  98. )
  99. continue
  100. interchange_event_kwargs = {
  101. "project_id": project_id,
  102. "organization_id": request.auth.organization_id,
  103. "payload": TransactionEventSchema(**item.dict()),
  104. }
  105. interchange_event = InterchangeIssueEvent(**interchange_event_kwargs)
  106. if cache.add("uuid" + interchange_event.event_id.hex, True) is True:
  107. ingest_transaction.delay(interchange_event.dict())
  108. if header.event_id:
  109. return JsonResponse({"id": header.event_id.hex})
  110. return JsonResponse({})