views.py 4.5 KB

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