123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- import logging
- import orjson
- from django.core.cache import cache
- from django.http import HttpResponse, JsonResponse
- from django.views.decorators.csrf import csrf_exempt
- from pydantic import ValidationError
- from sentry_sdk import capture_exception, set_context, set_level
- from glitchtip.api.exceptions import ThrottleException
- from .api import get_ip_address, get_issue_event_class
- from .authentication import EventAuthHttpRequest, event_auth
- from .schema import (
- EnvelopeHeaderSchema,
- IngestIssueEvent,
- InterchangeIssueEvent,
- ItemHeaderSchema,
- TransactionEventSchema,
- )
- from .tasks import ingest_event, ingest_transaction
- logger = logging.getLogger(__name__)
- def handle_validation_error(
- message: str, line: bytes, e: ValidationError, request: EventAuthHttpRequest
- ) -> JsonResponse:
- set_level("warning")
- try:
- set_context("incoming event", orjson.loads(line))
- except orjson.JSONDecodeError:
- pass
- capture_exception(e)
- logger.warning(f"{message} on {request.path}", exc_info=e)
- return JsonResponse({"detail": e.json()}, status=422)
- @csrf_exempt
- def event_envelope_view(request: EventAuthHttpRequest, project_id: int):
- """
- Envelopes can contain various types of data.
- GlitchTip supports issue events and transaction events.
- Ignore other data types.
- Do support multiple valid events
- Make as few io calls as possible. Some language SDKs (PHP) cannot run async code
- and will block while waiting for GlitchTip to respond.
- """
- if request.method != "POST":
- return JsonResponse({"detail": "Method not allowed"}, status=405)
- try:
- project = event_auth(request)
- except ThrottleException as e:
- response = HttpResponse("Too Many Requests", status=429)
- response["Retry-After"] = e.retry_after
- return response
- if project is None:
- return JsonResponse({"detail": "Denied"}, status=403)
- request.auth = project
- client_ip = get_ip_address(request)
- line = request.readline()
- try:
- header = EnvelopeHeaderSchema.model_validate_json(line)
- except ValidationError as e:
- return handle_validation_error(
- "Envelope Header validation error", line, e, request
- )
- for line in request:
- try:
- item_header = ItemHeaderSchema.model_validate_json(line)
- except ValidationError as e:
- handle_validation_error("Item Header validation error", line, e, request)
- request.readline() # Skip line
- continue
- line = request.readline()
- if item_header.type == "event":
- try:
- item = IngestIssueEvent.model_validate_json(line)
- except ValidationError as e:
- handle_validation_error("Event Item validation error", line, e, request)
- continue
- issue_event_class = get_issue_event_class(item)
- if item.user:
- item.user.ip_address = client_ip
- interchange_event_kwargs = {
- "project_id": project_id,
- "organization_id": project.organization_id,
- "payload": issue_event_class(**item.dict()),
- }
- if header.event_id:
- interchange_event_kwargs["event_id"] = header.event_id
- interchange_event = InterchangeIssueEvent(**interchange_event_kwargs)
- # Faux unique uuid as GlitchTip can accept duplicate UUIDs
- # The primary key of an event is uuid, received
- if cache.add("uuid" + interchange_event.event_id.hex, True) is True:
- ingest_event.delay(interchange_event.dict())
- elif item_header.type == "transaction":
- try:
- item = TransactionEventSchema.model_validate_json(line)
- except ValidationError as e:
- handle_validation_error(
- "Transaction Item validation error", line, e, request
- )
- continue
- interchange_event_kwargs = {
- "project_id": project_id,
- "organization_id": request.auth.organization_id,
- "payload": TransactionEventSchema(**item.dict()),
- }
- interchange_event = InterchangeIssueEvent(**interchange_event_kwargs)
- if cache.add("uuid" + interchange_event.event_id.hex, True) is True:
- ingest_transaction.delay(interchange_event.dict())
- if header.event_id:
- return JsonResponse({"id": header.event_id.hex})
- return JsonResponse({})
|