models.py 4.6 KB

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