api.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. from asgiref.sync import sync_to_async
  2. from django.conf import settings
  3. from django.http import HttpRequest, HttpResponse
  4. from django.utils.timezone import now
  5. from ninja import Router, Schema
  6. from .authentication import event_auth
  7. from .schema import (
  8. BaseEventIngestSchema,
  9. CSPIssueEventSchema,
  10. EnvelopeSchema,
  11. ErrorIssueEventSchema,
  12. EventIngestSchema,
  13. InterchangeIssueEvent,
  14. IssueEventSchema,
  15. SecuritySchema,
  16. )
  17. from .tasks import ingest_event
  18. router = Router(auth=event_auth)
  19. class EventIngestOut(Schema):
  20. event_id: str
  21. class EnvelopeIngestOut(Schema):
  22. id: str
  23. async def async_call_celery_task(task, *args):
  24. """
  25. Either dispatch the real celery task or run it with sync_to_async
  26. This can be used for testing or a celery-less operation.
  27. """
  28. if settings.CELERY_TASK_ALWAYS_EAGER:
  29. return await sync_to_async(task.delay)(*args)
  30. else:
  31. return task.delay(*args)
  32. def get_issue_event_class(event: BaseEventIngestSchema):
  33. return ErrorIssueEventSchema if event.exception else IssueEventSchema
  34. @router.post("/{project_id}/store/", response=EventIngestOut)
  35. async def event_store(
  36. request: HttpRequest,
  37. payload: EventIngestSchema,
  38. project_id: int,
  39. ):
  40. """
  41. Event store is the original event ingest API from OSS Sentry but is used less often
  42. Unlike Envelope, it accepts only one Issue event.
  43. """
  44. received_at = now()
  45. issue_event_class = get_issue_event_class(payload)
  46. issue_event = InterchangeIssueEvent(
  47. event_id=payload.event_id,
  48. project_id=project_id,
  49. received_at=received_at,
  50. payload=issue_event_class(**payload.dict()),
  51. )
  52. await async_call_celery_task(ingest_event, issue_event.dict())
  53. return {"event_id": payload.event_id.hex}
  54. @router.post("/{project_id}/envelope/", response=EnvelopeIngestOut)
  55. async def event_envelope(
  56. request: HttpRequest,
  57. payload: EnvelopeSchema,
  58. project_id: int,
  59. ):
  60. """
  61. Envelopes can contain various types of data.
  62. GlitchTip supports issue events and transaction events.
  63. Ignore other data types.
  64. Do support multiple valid events
  65. Make a few io calls as possible. Some language SDKs (PHP) cannot run async code
  66. and will block while waiting for GlitchTip to respond.
  67. """
  68. received_at = now()
  69. header = payload._header
  70. for item_header, item in payload._items:
  71. if item_header.type == "event":
  72. issue_event_class = get_issue_event_class(item)
  73. issue_event = InterchangeIssueEvent(
  74. event_id=header.event_id,
  75. project_id=project_id,
  76. received_at=received_at,
  77. payload=issue_event_class(**item.dict()),
  78. )
  79. await async_call_celery_task(ingest_event, issue_event.dict())
  80. elif item_header.type == "transaction":
  81. pass
  82. # ingest_transaction.delay(project_id, {})
  83. return {"id": header.event_id.hex}
  84. @router.post("/{project_id}/security/")
  85. async def event_security(
  86. request: HttpRequest,
  87. payload: SecuritySchema,
  88. project_id: int,
  89. ):
  90. """
  91. Accept Security (and someday other) issue events.
  92. Reformats event to make CSP browser format match more standard
  93. event format.
  94. """
  95. received_at = now()
  96. event = CSPIssueEventSchema(csp=payload.csp_report.dict(by_alias=True))
  97. issue_event = InterchangeIssueEvent(
  98. project_id=project_id,
  99. received_at=received_at,
  100. payload=event.dict(by_alias=True),
  101. )
  102. await async_call_celery_task(ingest_event, issue_event.dict(by_alias=True))
  103. return HttpResponse(status=201)