models.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import uuid
  2. from django.contrib.postgres.fields import HStoreField
  3. from django.db import models
  4. from glitchtip.base_models import CreatedModel
  5. from glitchtip.model_utils import FromStringIntegerChoices
  6. from projects.tasks import update_event_project_hourly_statistic
  7. from user_reports.models import UserReport
  8. class AbstractEvent(CreatedModel):
  9. event_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
  10. data = models.JSONField(help_text="General event data that is searchable")
  11. timestamp = models.DateTimeField(
  12. blank=True,
  13. null=True,
  14. help_text="Date created as claimed by client it came from",
  15. )
  16. class Meta:
  17. abstract = True
  18. def __str__(self):
  19. return self.event_id_hex
  20. @property
  21. def event_id_hex(self):
  22. """The public key without dashes"""
  23. if self.event_id:
  24. if isinstance(self.event_id, str):
  25. return self.event_id
  26. return self.event_id.hex
  27. class LogLevel(FromStringIntegerChoices):
  28. NOTSET = 0, "sample"
  29. DEBUG = 1, "debug"
  30. INFO = 2, "info"
  31. WARNING = 3, "warning"
  32. ERROR = 4, "error"
  33. FATAL = 5, "fatal"
  34. @classmethod
  35. def from_string(cls, string: str):
  36. result = super().from_string(string)
  37. if result:
  38. return result
  39. if string == "critical":
  40. return cls.FATAL
  41. if string == "log":
  42. return cls.INFO
  43. return cls.ERROR
  44. class Event(AbstractEvent):
  45. """
  46. An individual event. An issue is a set of like-events.
  47. Most is stored in "data" but some fields benefit from being real
  48. relational data types such as dates and foreign keys
  49. """
  50. issue = models.ForeignKey(
  51. "issues.Issue",
  52. on_delete=models.CASCADE,
  53. help_text="Sentry calls this a group",
  54. )
  55. level = models.PositiveSmallIntegerField(
  56. choices=LogLevel.choices, default=LogLevel.ERROR
  57. )
  58. errors = models.JSONField(
  59. null=True,
  60. blank=True,
  61. help_text="Event processing errors from event intake, including validation errors",
  62. )
  63. tags = HStoreField(default=dict)
  64. release = models.ForeignKey(
  65. "releases.Release", blank=True, null=True, on_delete=models.SET_NULL
  66. )
  67. class Meta:
  68. ordering = ["-created"]
  69. def save(self, *args, **kwargs):
  70. is_new = self._state.adding
  71. super().save(*args, **kwargs)
  72. if is_new:
  73. update_event_project_hourly_statistic(
  74. args=[self.issue.project_id, self.created], countdown=60
  75. )
  76. def event_json(self):
  77. """
  78. OSS Sentry Compatible raw event JSON
  79. Effectively this combines data and relational data
  80. """
  81. event = self.data
  82. event["event_id"] = self.event_id_hex
  83. event["project"] = self.issue.project_id
  84. event["level"] = self.get_level_display()
  85. event["tags"] = self.tags.items()
  86. if self.errors:
  87. event["errors"] = self.errors
  88. if self.timestamp:
  89. event["datetime"] = self.timestamp.isoformat().replace("+00:00", "Z")
  90. if self.release:
  91. event["release"] = self.release.version
  92. return event
  93. @property
  94. def context(self):
  95. return self.data.get("extra")
  96. @property
  97. def contexts(self):
  98. return self.data.get("contexts")
  99. @property
  100. def culprit(self):
  101. return self.data.get("culprit")
  102. @property
  103. def message(self):
  104. return self.data.get("message")
  105. @property
  106. def user(self):
  107. return self.data.get("user")
  108. @property
  109. def user_report(self):
  110. return UserReport.objects.filter(event_id=self.pk).first()
  111. def _build_context(self, context: list, base_line_no: int, is_pre: bool):
  112. context_length = len(context)
  113. result = []
  114. for index, pre_context_line in enumerate(context):
  115. if is_pre:
  116. line_no = base_line_no - context_length + index
  117. else:
  118. line_no = base_line_no + 1 + index
  119. result.append(
  120. [
  121. line_no,
  122. pre_context_line,
  123. ]
  124. )
  125. return result
  126. @property
  127. def metadata(self):
  128. return self.data.get("metadata")
  129. @property
  130. def packages(self):
  131. return self.data.get("modules")
  132. @property
  133. def platform(self):
  134. return self.data.get("platform")
  135. @property
  136. def sdk(self):
  137. return self.data.get("sdk")
  138. @property
  139. def title(self):
  140. return self.data.get("title")
  141. @property
  142. def type(self):
  143. return self.data.get("type")
  144. def next(self, *args, **kwargs):
  145. try:
  146. return self.get_next_by_created(**kwargs)
  147. except Event.DoesNotExist:
  148. return None
  149. def previous(self, *args, **kwargs):
  150. """Get previous object by created date, pass filter kwargs"""
  151. try:
  152. return self.get_previous_by_created(**kwargs)
  153. except Event.DoesNotExist:
  154. return None