api.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. from anonymizeip import anonymize_ip
  2. from django.conf import settings
  3. from django.core.cache import cache
  4. from django.http import HttpResponse
  5. from ipware import get_client_ip
  6. from ninja import Router, Schema
  7. from ninja.errors import ValidationError
  8. from .authentication import EventAuthHttpRequest, event_auth
  9. from .schema import (
  10. CSPIssueEventSchema,
  11. EnvelopeSchema,
  12. ErrorIssueEventSchema,
  13. EventIngestSchema,
  14. EventUser,
  15. IngestIssueEvent,
  16. InterchangeIssueEvent,
  17. IssueEventSchema,
  18. SecuritySchema,
  19. )
  20. from .tasks import ingest_event
  21. router = Router(auth=event_auth)
  22. class EventIngestOut(Schema):
  23. event_id: str
  24. task_id: str | None = None # For debug purposes only
  25. class EnvelopeIngestOut(Schema):
  26. id: str | None = None
  27. def get_issue_event_class(event: IngestIssueEvent):
  28. return ErrorIssueEventSchema if event.exception else IssueEventSchema
  29. def get_ip_address(request: EventAuthHttpRequest) -> str | None:
  30. """
  31. Get IP address from request. Anonymize it based on project settings.
  32. Keep this logic in the api view, we aim to anonymize data before storing
  33. on redis/postgres.
  34. """
  35. project = request.auth
  36. client_ip, is_routable = get_client_ip(request)
  37. if is_routable:
  38. if project.should_scrub_ip_addresses:
  39. client_ip = anonymize_ip(client_ip)
  40. return client_ip
  41. return None
  42. @router.post("/{project_id}/store/", response=EventIngestOut)
  43. def event_store(
  44. request: EventAuthHttpRequest,
  45. payload: EventIngestSchema,
  46. project_id: int,
  47. ):
  48. """
  49. Event store is the original event ingest API from OSS Sentry but is used less often
  50. Unlike Envelope, it accepts only one Issue event.
  51. """
  52. if cache.add("uuid" + payload.event_id.hex, True) is False:
  53. raise ValidationError([{"message": "Duplicate event id"}])
  54. if client_ip := get_ip_address(request):
  55. if payload.user:
  56. payload.user.ip_address = client_ip
  57. else:
  58. payload.user = EventUser(ip_address=client_ip)
  59. issue_event_class = get_issue_event_class(payload)
  60. issue_event = InterchangeIssueEvent(
  61. event_id=payload.event_id,
  62. project_id=project_id,
  63. organization_id=request.auth.organization_id,
  64. payload=issue_event_class(**payload.dict()),
  65. )
  66. task_result = ingest_event.delay(issue_event.dict())
  67. result = {"event_id": payload.event_id.hex}
  68. if settings.IS_LOAD_TEST:
  69. result["task_id"] = task_result.task_id
  70. return result
  71. @router.post("/{project_id}/envelope/", response=EnvelopeIngestOut)
  72. def event_envelope(
  73. request: EventAuthHttpRequest,
  74. payload: EnvelopeSchema,
  75. project_id: int,
  76. ):
  77. """
  78. Envelopes can contain various types of data.
  79. GlitchTip supports issue events and transaction events.
  80. Ignore other data types.
  81. Do support multiple valid events
  82. Make as few io calls as possible. Some language SDKs (PHP) cannot run async code
  83. and will block while waiting for GlitchTip to respond.
  84. """
  85. @router.post("/{project_id}/security/")
  86. def event_security(
  87. request: EventAuthHttpRequest,
  88. payload: SecuritySchema,
  89. project_id: int,
  90. ):
  91. """
  92. Accept Security (and someday other) issue events.
  93. Reformats event to make CSP browser format match more standard
  94. event format.
  95. """
  96. event = CSPIssueEventSchema(csp=payload.csp_report.dict(by_alias=True))
  97. if client_ip := get_ip_address(request):
  98. if event.user:
  99. event.user.ip_address = client_ip
  100. else:
  101. event.user = EventUser(ip_address=client_ip)
  102. issue_event = InterchangeIssueEvent(
  103. project_id=project_id,
  104. organization_id=request.auth.organization_id,
  105. payload=event.dict(by_alias=True),
  106. )
  107. ingest_event.delay(issue_event.dict(by_alias=True))
  108. return HttpResponse(status=201)