class TurboSMSOptionsForm(forms.Form): numbers = forms.CharField( label=_('Phone numbers'), widget=forms.Textarea(attrs={ 'class': 'span6', 'placeholder': '+380xxyyyyyyy'}), help_text=_('Enter phone numbers to send new events to (one per line).') ) login = forms.CharField( label=_('Login'), help_text=_('TurboSMS login'), validators=[RegexValidator(r'\w+')] ) password = forms.CharField( label=_('Password'), widget=forms.PasswordInput(), help_text=_('TurboSMS password') ) alphaname = forms.CharField( label=_('Alphaname'), help_text=_('TurboSMS alphaname'), validators=[RegexValidator(r'\w+')] ) level = forms.ChoiceField( label=_('Level'), choices=LOG_LEVELS.items() )
class GroupTombstone(Model): __core__ = False previous_group_id = BoundedPositiveIntegerField(unique=True) project = FlexibleForeignKey("sentry.Project") level = BoundedPositiveIntegerField( choices=LOG_LEVELS.items(), default=logging.ERROR, blank=True ) message = models.TextField() culprit = models.CharField(max_length=MAX_CULPRIT_LENGTH, blank=True, null=True) data = GzippedDictField(blank=True, null=True) actor_id = BoundedPositiveIntegerField(null=True) class Meta: app_label = "sentry" db_table = "sentry_grouptombstone" def get_event_type(self): """ Return the type of this issue. See ``sentry.eventtypes``. """ return self.data.get("type", "default") def get_event_metadata(self): """ Return the metadata of this issue. See ``sentry.eventtypes``. """ return self.data["metadata"]
class GroupTombstone(Model): __core__ = False previous_group_id = BoundedPositiveIntegerField(unique=True) project = FlexibleForeignKey('sentry.Project') level = BoundedPositiveIntegerField( choices=LOG_LEVELS.items(), default=logging.ERROR, blank=True ) message = models.TextField() culprit = models.CharField( max_length=MAX_CULPRIT_LENGTH, blank=True, null=True, ) data = GzippedDictField(blank=True, null=True) actor_id = BoundedPositiveIntegerField(null=True) class Meta: app_label = 'sentry' db_table = 'sentry_grouptombstone' def get_event_type(self): """ Return the type of this issue. See ``sentry.eventtypes``. """ return self.data.get('type', 'default') def get_event_metadata(self): """ Return the metadata of this issue. See ``sentry.eventtypes``. """ etype = self.data.get('type') if etype is None: etype = 'default' if 'metadata' not in self.data: data = self.data.copy() if self.data else {} data['message'] = self.message return eventtypes.get(etype)(data).get_metadata() return self.data['metadata']
:copyright: (c) 2010-2014 by the Sentry Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import absolute_import from collections import OrderedDict from django import forms from sentry.constants import LOG_LEVELS from sentry.rules.conditions.base import EventCondition LEVEL_CHOICES = OrderedDict([ ("{0}".format(k), "{0}".format(v.capitalize())) for k, v in sorted(LOG_LEVELS.items(), key=lambda x: x[0], reverse=True) ]) LOG_LEVEL_REVERSE_MAP = dict((v, k) for k, v in LOG_LEVELS.iteritems()) class LevelMatchType(object): EQUAL = 'eq' LESS_OR_EQUAL = 'lte' GREATER_OR_EQUAL = 'gte' class LevelEventForm(forms.Form): level = forms.ChoiceField(choices=LEVEL_CHOICES.items(), initial=30) match = forms.ChoiceField(choices=((LevelMatchType.EQUAL, 'equal'), (LevelMatchType.LESS_OR_EQUAL, 'less than or equal to'),
class Group(Model): """ Aggregated message which summarizes a set of Events. """ __core__ = False project = FlexibleForeignKey('sentry.Project', null=True) logger = models.CharField(max_length=64, blank=True, default=DEFAULT_LOGGER_NAME, db_index=True) level = BoundedPositiveIntegerField(choices=LOG_LEVELS.items(), default=logging.ERROR, blank=True, db_index=True) message = models.TextField() culprit = models.CharField(max_length=MAX_CULPRIT_LENGTH, blank=True, null=True, db_column='view') num_comments = BoundedPositiveIntegerField(default=0, null=True) platform = models.CharField(max_length=64, null=True) status = BoundedPositiveIntegerField(default=0, choices=( (GroupStatus.UNRESOLVED, _('Unresolved')), (GroupStatus.RESOLVED, _('Resolved')), (GroupStatus.IGNORED, _('Ignored')), ), db_index=True) times_seen = BoundedPositiveIntegerField(default=1, db_index=True) last_seen = models.DateTimeField(default=timezone.now, db_index=True) first_seen = models.DateTimeField(default=timezone.now, db_index=True) first_release = FlexibleForeignKey('sentry.Release', null=True, on_delete=models.PROTECT) resolved_at = models.DateTimeField(null=True, db_index=True) # active_at should be the same as first_seen by default active_at = models.DateTimeField(null=True, db_index=True) time_spent_total = BoundedIntegerField(default=0) time_spent_count = BoundedIntegerField(default=0) score = BoundedIntegerField(default=0) # deprecated, do not use. GroupShare has superseded is_public = models.NullBooleanField(default=False, null=True) data = GzippedDictField(blank=True, null=True) short_id = BoundedBigIntegerField(null=True) objects = GroupManager() class Meta: app_label = 'sentry' db_table = 'sentry_groupedmessage' verbose_name_plural = _('grouped messages') verbose_name = _('grouped message') permissions = (("can_view", "Can view"), ) index_together = (('project', 'first_release'), ) unique_together = (('project', 'short_id'), ) __repr__ = sane_repr('project_id') def __unicode__(self): return "(%s) %s" % (self.times_seen, self.error()) def save(self, *args, **kwargs): if not self.last_seen: self.last_seen = timezone.now() if not self.first_seen: self.first_seen = self.last_seen if not self.active_at: self.active_at = self.first_seen # We limit what we store for the message body self.message = strip(self.message) if self.message: self.message = truncatechars(self.message.splitlines()[0], 255) super(Group, self).save(*args, **kwargs) def get_absolute_url(self): return absolute_uri( reverse('sentry-group', args=[self.organization.slug, self.project.slug, self.id])) @property def qualified_short_id(self): if self.short_id is not None: return '%s-%s' % ( self.project.slug.upper(), base32_encode(self.short_id), ) @property def event_set(self): from sentry.models import Event return Event.objects.filter(group_id=self.id) def is_over_resolve_age(self): resolve_age = self.project.get_option('sentry:resolve_age', None) if not resolve_age: return False return self.last_seen < timezone.now() - timedelta( hours=int(resolve_age)) def is_ignored(self): return self.get_status() == GroupStatus.IGNORED # TODO(dcramer): remove in 9.0 / after plugins no long ref is_muted = is_ignored def is_resolved(self): return self.get_status() == GroupStatus.RESOLVED def get_status(self): # XXX(dcramer): GroupSerializer reimplements this logic from sentry.models import GroupSnooze status = self.status if status == GroupStatus.IGNORED: try: snooze = GroupSnooze.objects.get(group=self) except GroupSnooze.DoesNotExist: pass else: if not snooze.is_valid(group=self): status = GroupStatus.UNRESOLVED if status == GroupStatus.UNRESOLVED and self.is_over_resolve_age(): return GroupStatus.RESOLVED return status def get_share_id(self): from sentry.models import GroupShare try: return GroupShare.objects.filter(group_id=self.id, ).values_list( 'uuid', flat=True)[0] except IndexError: # Otherwise it has not been shared yet. return None @classmethod def from_share_id(cls, share_id): if not share_id or len(share_id) != 32: raise cls.DoesNotExist from sentry.models import GroupShare return cls.objects.get(id=GroupShare.objects.filter( uuid=share_id, ).values_list('group_id'), ) def get_score(self): return int( math.log(self.times_seen) * 600 + float(time.mktime(self.last_seen.timetuple()))) def get_latest_event(self): from sentry.models import Event if not hasattr(self, '_latest_event'): latest_events = sorted( Event.objects.filter( group_id=self.id, ).order_by('-datetime')[0:5], key=EVENT_ORDERING_KEY, reverse=True, ) try: self._latest_event = latest_events[0] except IndexError: self._latest_event = None return self._latest_event def get_oldest_event(self): from sentry.models import Event if not hasattr(self, '_oldest_event'): oldest_events = sorted( Event.objects.filter( group_id=self.id, ).order_by('datetime')[0:5], key=EVENT_ORDERING_KEY, ) try: self._oldest_event = oldest_events[0] except IndexError: self._oldest_event = None return self._oldest_event def get_first_release(self): if self.first_release_id is None: return tagstore.get_first_release(self.project_id, self.id) return self.first_release.version def get_last_release(self): return tagstore.get_last_release(self.project_id, self.id) def get_event_type(self): """ Return the type of this issue. See ``sentry.eventtypes``. """ return self.data.get('type', 'default') def get_event_metadata(self): """ Return the metadata of this issue. See ``sentry.eventtypes``. """ etype = self.data.get('type') if etype is None: etype = 'default' if 'metadata' not in self.data: data = self.data.copy() if self.data else {} data['message'] = self.message return eventtypes.get(etype)(data).get_metadata() return self.data['metadata'] @property def title(self): et = eventtypes.get(self.get_event_type())(self.data) return et.to_string(self.get_event_metadata()) def error(self): warnings.warn('Group.error is deprecated, use Group.title', DeprecationWarning) return self.title error.short_description = _('error') @property def message_short(self): warnings.warn('Group.message_short is deprecated, use Group.title', DeprecationWarning) return self.title @property def organization(self): return self.project.organization @property def checksum(self): warnings.warn('Group.checksum is no longer used', DeprecationWarning) return '' def get_email_subject(self): return '%s - %s' % (self.qualified_short_id.encode('utf-8'), self.title.encode('utf-8')) def count_users_seen(self): return tagstore.get_groups_user_counts(self.project_id, [self.id], environment_id=None)[self.id]
class Group(Model): """ Aggregated message which summarizes a set of Events. """ __include_in_export__ = False project = FlexibleForeignKey("sentry.Project") logger = models.CharField(max_length=64, blank=True, default=str(DEFAULT_LOGGER_NAME), db_index=True) level = BoundedPositiveIntegerField( choices=[(key, str(val)) for key, val in sorted(LOG_LEVELS.items())], default=logging.ERROR, blank=True, db_index=True, ) message = models.TextField() culprit = models.CharField(max_length=MAX_CULPRIT_LENGTH, blank=True, null=True, db_column="view") num_comments = BoundedPositiveIntegerField(default=0, null=True) platform = models.CharField(max_length=64, null=True) status = BoundedPositiveIntegerField( default=0, choices=( (GroupStatus.UNRESOLVED, _("Unresolved")), (GroupStatus.RESOLVED, _("Resolved")), (GroupStatus.IGNORED, _("Ignored")), ), db_index=True, ) times_seen = BoundedPositiveIntegerField(default=1, db_index=True) last_seen = models.DateTimeField(default=timezone.now, db_index=True) first_seen = models.DateTimeField(default=timezone.now, db_index=True) first_release = FlexibleForeignKey("sentry.Release", null=True, on_delete=models.PROTECT) resolved_at = models.DateTimeField(null=True, db_index=True) # active_at should be the same as first_seen by default active_at = models.DateTimeField(null=True, db_index=True) time_spent_total = BoundedIntegerField(default=0) time_spent_count = BoundedIntegerField(default=0) score = BoundedIntegerField(default=0) # deprecated, do not use. GroupShare has superseded is_public = models.NullBooleanField(default=False, null=True) data = GzippedDictField(blank=True, null=True) short_id = BoundedBigIntegerField(null=True) objects = GroupManager(cache_fields=("id", )) class Meta: app_label = "sentry" db_table = "sentry_groupedmessage" verbose_name_plural = _("grouped messages") verbose_name = _("grouped message") permissions = (("can_view", "Can view"), ) index_together = [ ("project", "first_release"), ("project", "id"), ("project", "status", "last_seen", "id"), ] unique_together = ( ("project", "short_id"), ("project", "id"), ) __repr__ = sane_repr("project_id") def __str__(self): return f"({self.times_seen}) {self.error()}" def save(self, *args, **kwargs): if not self.last_seen: self.last_seen = timezone.now() if not self.first_seen: self.first_seen = self.last_seen if not self.active_at: self.active_at = self.first_seen # We limit what we store for the message body self.message = strip(self.message) if self.message: self.message = truncatechars(self.message.splitlines()[0], 255) if self.times_seen is None: self.times_seen = 1 self.score = type(self).calculate_score(times_seen=self.times_seen, last_seen=self.last_seen) super().save(*args, **kwargs) def get_absolute_url( self, params: Mapping[str, str] | None = None, event_id: int | None = None, organization_slug: str | None = None, ) -> str: # Built manually in preference to django.urls.reverse, # because reverse has a measured performance impact. event_path = f"events/{event_id}/" if event_id else "" url = "organizations/{org}/issues/{id}/{event_path}{params}".format( # Pass organization_slug if this needs to be called multiple times to avoid n+1 queries org=urlquote(self.organization.slug if organization_slug is None else organization_slug), id=self.id, event_path=event_path, params="?" + urlencode(params) if params else "", ) return absolute_uri(url) @property def qualified_short_id(self): if self.short_id is not None: return f"{self.project.slug.upper()}-{base32_encode(self.short_id)}" def is_over_resolve_age(self): resolve_age = self.project.get_option("sentry:resolve_age", None) if not resolve_age: return False return self.last_seen < timezone.now() - timedelta( hours=int(resolve_age)) def is_ignored(self): return self.get_status() == GroupStatus.IGNORED def is_unresolved(self): return self.get_status() == GroupStatus.UNRESOLVED # TODO(dcramer): remove in 9.0 / after plugins no long ref is_muted = is_ignored def is_resolved(self): return self.get_status() == GroupStatus.RESOLVED def get_status(self): # XXX(dcramer): GroupSerializer reimplements this logic from sentry.models import GroupSnooze status = self.status if status == GroupStatus.IGNORED: try: snooze = GroupSnooze.objects.get_from_cache(group=self) except GroupSnooze.DoesNotExist: pass else: if not snooze.is_valid(group=self): status = GroupStatus.UNRESOLVED if status == GroupStatus.UNRESOLVED and self.is_over_resolve_age(): return GroupStatus.RESOLVED return status def get_share_id(self): from sentry.models import GroupShare try: return GroupShare.objects.filter(group_id=self.id).values_list( "uuid", flat=True)[0] except IndexError: # Otherwise it has not been shared yet. return None def get_score(self): return type(self).calculate_score(self.times_seen, self.last_seen) def get_latest_event(self) -> Event | None: if not hasattr(self, "_latest_event"): self._latest_event = self.get_latest_event_for_environments() return self._latest_event def get_latest_event_for_environments(self, environments=()): return get_oldest_or_latest_event_for_environments( EventOrdering.LATEST, environments=environments, issue_id=self.id, project_id=self.project_id, ) def get_oldest_event_for_environments(self, environments=()): return get_oldest_or_latest_event_for_environments( EventOrdering.OLDEST, environments=environments, issue_id=self.id, project_id=self.project_id, ) def _get_cache_key(self, project_id, group_id, first): return f"g-r:{group_id}-{project_id}-{first}" def __get_release(self, project_id, group_id, first=True, use_cache=True): from sentry.models import GroupRelease, Release orderby = "first_seen" if first else "-last_seen" cache_key = self._get_cache_key(project_id, group_id, first) try: release_version = cache.get(cache_key) if use_cache else None if release_version is None: release_version = Release.objects.get( id__in=GroupRelease.objects.filter(group_id=group_id). order_by(orderby).values("release_id")[:1]).version cache.set(cache_key, release_version, 3600) elif release_version is False: release_version = None return release_version except Release.DoesNotExist: cache.set(cache_key, False, 3600) return None def get_first_release(self): if self.first_release_id is None: first_release = self.__get_release(self.project_id, self.id, True) return first_release return self.first_release.version def get_last_release(self, use_cache=True): return self.__get_release(self.project_id, self.id, False, use_cache=use_cache) def get_event_type(self): """ Return the type of this issue. See ``sentry.eventtypes``. """ return self.data.get("type", "default") def get_event_metadata(self) -> Mapping[str, str]: """ Return the metadata of this issue. See ``sentry.eventtypes``. """ return self.data["metadata"] @property def title(self) -> str: et = eventtypes.get(self.get_event_type())() return et.get_title(self.get_event_metadata()) def location(self): et = eventtypes.get(self.get_event_type())() return et.get_location(self.get_event_metadata()) def error(self): warnings.warn("Group.error is deprecated, use Group.title", DeprecationWarning) return self.title error.short_description = _("error") @property def message_short(self): warnings.warn("Group.message_short is deprecated, use Group.title", DeprecationWarning) return self.title @property def organization(self): return self.project.organization @property def checksum(self): warnings.warn("Group.checksum is no longer used", DeprecationWarning) return "" def get_email_subject(self): return f"{self.qualified_short_id} - {self.title}" def count_users_seen(self): return tagstore.get_groups_user_counts([self.project_id], [self.id], environment_ids=None, start=self.first_seen)[self.id] @classmethod def calculate_score(cls, times_seen, last_seen): return math.log(float(times_seen or 1)) * 600 + float( last_seen.strftime("%s")) @staticmethod def issues_mapping(group_ids, project_ids, organization): """Create a dictionary of group_ids to their qualified_short_ids""" return { i.id: i.qualified_short_id for i in Group.objects.filter(id__in=group_ids, project_id__in=project_ids, project__organization=organization) } def get_assignee(self) -> Team | User | None: from sentry.models import GroupAssignee try: group_assignee = GroupAssignee.objects.get(group=self) except GroupAssignee.DoesNotExist: return None assigned_actor = group_assignee.assigned_actor() try: return assigned_actor.resolve() except assigned_actor.type.DoesNotExist: return None
class Group(Model): """ Aggregated message which summarizes a set of Events. """ __core__ = False project = FlexibleForeignKey("sentry.Project") logger = models.CharField(max_length=64, blank=True, default=DEFAULT_LOGGER_NAME, db_index=True) level = BoundedPositiveIntegerField(choices=LOG_LEVELS.items(), default=logging.ERROR, blank=True, db_index=True) message = models.TextField() culprit = models.CharField(max_length=MAX_CULPRIT_LENGTH, blank=True, null=True, db_column="view") num_comments = BoundedPositiveIntegerField(default=0, null=True) platform = models.CharField(max_length=64, null=True) status = BoundedPositiveIntegerField( default=0, choices=( (GroupStatus.UNRESOLVED, _("Unresolved")), (GroupStatus.RESOLVED, _("Resolved")), (GroupStatus.IGNORED, _("Ignored")), ), db_index=True, ) times_seen = BoundedPositiveIntegerField(default=1, db_index=True) last_seen = models.DateTimeField(default=timezone.now, db_index=True) first_seen = models.DateTimeField(default=timezone.now, db_index=True) first_release = FlexibleForeignKey("sentry.Release", null=True, on_delete=models.PROTECT) resolved_at = models.DateTimeField(null=True, db_index=True) # active_at should be the same as first_seen by default active_at = models.DateTimeField(null=True, db_index=True) time_spent_total = BoundedIntegerField(default=0) time_spent_count = BoundedIntegerField(default=0) score = BoundedIntegerField(default=0) # deprecated, do not use. GroupShare has superseded is_public = models.NullBooleanField(default=False, null=True) data = GzippedDictField(blank=True, null=True) short_id = BoundedBigIntegerField(null=True) objects = GroupManager(cache_fields=("id", )) class Meta: app_label = "sentry" db_table = "sentry_groupedmessage" verbose_name_plural = _("grouped messages") verbose_name = _("grouped message") permissions = (("can_view", "Can view"), ) index_together = [("project", "first_release"), ("project", "id")] unique_together = (("project", "short_id"), ) __repr__ = sane_repr("project_id") def __unicode__(self): return "(%s) %s" % (self.times_seen, self.error()) def save(self, *args, **kwargs): if not self.last_seen: self.last_seen = timezone.now() if not self.first_seen: self.first_seen = self.last_seen if not self.active_at: self.active_at = self.first_seen # We limit what we store for the message body self.message = strip(self.message) if self.message: self.message = truncatechars(self.message.splitlines()[0], 255) if self.times_seen is None: self.times_seen = 1 self.score = type(self).calculate_score(times_seen=self.times_seen, last_seen=self.last_seen) super(Group, self).save(*args, **kwargs) def get_absolute_url(self, params=None): url = reverse("sentry-organization-issue", args=[self.organization.slug, self.id]) if params: url = url + "?" + urlencode(params) return absolute_uri(url) @property def qualified_short_id(self): if self.short_id is not None: return "%s-%s" % (self.project.slug.upper(), base32_encode(self.short_id)) def is_over_resolve_age(self): resolve_age = self.project.get_option("sentry:resolve_age", None) if not resolve_age: return False return self.last_seen < timezone.now() - timedelta( hours=int(resolve_age)) def is_ignored(self): return self.get_status() == GroupStatus.IGNORED def is_unresolved(self): return self.get_status() == GroupStatus.UNRESOLVED # TODO(dcramer): remove in 9.0 / after plugins no long ref is_muted = is_ignored def is_resolved(self): return self.get_status() == GroupStatus.RESOLVED def get_status(self): # XXX(dcramer): GroupSerializer reimplements this logic from sentry.models import GroupSnooze status = self.status if status == GroupStatus.IGNORED: try: snooze = GroupSnooze.objects.get_from_cache(group=self) except GroupSnooze.DoesNotExist: pass else: if not snooze.is_valid(group=self): status = GroupStatus.UNRESOLVED if status == GroupStatus.UNRESOLVED and self.is_over_resolve_age(): return GroupStatus.RESOLVED return status def get_share_id(self): from sentry.models import GroupShare try: return GroupShare.objects.filter(group_id=self.id).values_list( "uuid", flat=True)[0] except IndexError: # Otherwise it has not been shared yet. return None @classmethod def from_share_id(cls, share_id): if not share_id or len(share_id) != 32: raise cls.DoesNotExist from sentry.models import GroupShare return cls.objects.get(id=GroupShare.objects.filter( uuid=share_id).values_list("group_id")) def get_score(self): return type(self).calculate_score(self.times_seen, self.last_seen) def get_latest_event(self): if not hasattr(self, "_latest_event"): self._latest_event = self.get_latest_event_for_environments() return self._latest_event def get_latest_event_for_environments(self, environments=()): return get_oldest_or_latest_event_for_environments( EventOrdering.LATEST, environments=environments, issue_id=self.id, project_id=self.project_id, ) def get_oldest_event_for_environments(self, environments=()): return get_oldest_or_latest_event_for_environments( EventOrdering.OLDEST, environments=environments, issue_id=self.id, project_id=self.project_id, ) def get_first_release(self): if self.first_release_id is None: return tagstore.get_first_release(self.project_id, self.id) return self.first_release.version def get_last_release(self): return tagstore.get_last_release(self.project_id, self.id) def get_event_type(self): """ Return the type of this issue. See ``sentry.eventtypes``. """ return self.data.get("type", "default") def get_event_metadata(self): """ Return the metadata of this issue. See ``sentry.eventtypes``. """ return self.data["metadata"] @property def title(self): et = eventtypes.get(self.get_event_type())() return et.get_title(self.get_event_metadata()) def location(self): et = eventtypes.get(self.get_event_type())() return et.get_location(self.get_event_metadata()) def error(self): warnings.warn("Group.error is deprecated, use Group.title", DeprecationWarning) return self.title error.short_description = _("error") @property def message_short(self): warnings.warn("Group.message_short is deprecated, use Group.title", DeprecationWarning) return self.title @property def organization(self): return self.project.organization @property def checksum(self): warnings.warn("Group.checksum is no longer used", DeprecationWarning) return "" def get_email_subject(self): return "%s - %s" % (self.qualified_short_id.encode("utf-8"), self.title.encode("utf-8")) def count_users_seen(self): return tagstore.get_groups_user_counts([self.project_id], [self.id], environment_ids=None, start=self.first_seen)[self.id] @classmethod def calculate_score(cls, times_seen, last_seen): return math.log(float(times_seen or 1)) * 600 + float( last_seen.strftime("%s"))
:copyright: (c) 2010-2014 by the Sentry Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import absolute_import from collections import OrderedDict from django import forms from sentry.constants import LOG_LEVELS, LOG_LEVELS_MAP from sentry.rules.conditions.base import EventCondition LEVEL_CHOICES = OrderedDict( [("{0}".format(k), v) for k, v in sorted(LOG_LEVELS.items(), key=lambda x: x[0], reverse=True)] ) class MatchType(object): EQUAL = "eq" LESS_OR_EQUAL = "lte" GREATER_OR_EQUAL = "gte" MATCH_CHOICES = OrderedDict( [ (MatchType.EQUAL, "equal to"), (MatchType.LESS_OR_EQUAL, "less than or equal to"), (MatchType.GREATER_OR_EQUAL, "greater than or equal to"), ]
class Group(Model): """ Aggregated message which summarizes a set of Events. """ __core__ = False project = FlexibleForeignKey('sentry.Project', null=True) logger = models.CharField(max_length=64, blank=True, default=DEFAULT_LOGGER_NAME, db_index=True) level = BoundedPositiveIntegerField(choices=LOG_LEVELS.items(), default=logging.ERROR, blank=True, db_index=True) message = models.TextField() culprit = models.CharField(max_length=MAX_CULPRIT_LENGTH, blank=True, null=True, db_column='view') num_comments = BoundedPositiveIntegerField(default=0, null=True) platform = models.CharField(max_length=64, null=True) status = BoundedPositiveIntegerField(default=0, choices=( (GroupStatus.UNRESOLVED, _('Unresolved')), (GroupStatus.RESOLVED, _('Resolved')), (GroupStatus.MUTED, _('Muted')), ), db_index=True) times_seen = BoundedPositiveIntegerField(default=1, db_index=True) last_seen = models.DateTimeField(default=timezone.now, db_index=True) first_seen = models.DateTimeField(default=timezone.now, db_index=True) first_release = FlexibleForeignKey('sentry.Release', null=True, on_delete=models.PROTECT) resolved_at = models.DateTimeField(null=True, db_index=True) # active_at should be the same as first_seen by default active_at = models.DateTimeField(null=True, db_index=True) time_spent_total = BoundedIntegerField(default=0) time_spent_count = BoundedIntegerField(default=0) score = BoundedIntegerField(default=0) is_public = models.NullBooleanField(default=False, null=True) data = GzippedDictField(blank=True, null=True) short_id = BoundedBigIntegerField(null=True) objects = GroupManager() class Meta: app_label = 'sentry' db_table = 'sentry_groupedmessage' verbose_name_plural = _('grouped messages') verbose_name = _('grouped message') permissions = (("can_view", "Can view"), ) index_together = (('project', 'first_release'), ) unique_together = (('project', 'short_id'), ) __repr__ = sane_repr('project_id') def __unicode__(self): return "(%s) %s" % (self.times_seen, self.error()) def save(self, *args, **kwargs): if not self.last_seen: self.last_seen = timezone.now() if not self.first_seen: self.first_seen = self.last_seen if not self.active_at: self.active_at = self.first_seen # We limit what we store for the message body self.message = strip(self.message) if self.message: self.message = truncatechars(self.message.splitlines()[0], 255) super(Group, self).save(*args, **kwargs) def get_absolute_url(self): return absolute_uri( reverse('sentry-group', args=[self.organization.slug, self.project.slug, self.id])) @property def qualified_short_id(self): if self.short_id is not None: return '%s-%s' % ( self.project.slug.upper(), base32_encode(self.short_id), ) @property def event_set(self): from sentry.models import Event return Event.objects.filter(group_id=self.id) def is_over_resolve_age(self): resolve_age = self.project.get_option('sentry:resolve_age', None) if not resolve_age: return False return self.last_seen < timezone.now() - timedelta( hours=int(resolve_age)) def is_muted(self): return self.get_status() == GroupStatus.MUTED def is_resolved(self): return self.get_status() == GroupStatus.RESOLVED def get_status(self): # XXX(dcramer): GroupSerializer reimplements this logic from sentry.models import GroupSnooze if self.status == GroupStatus.MUTED: try: snooze = GroupSnooze.objects.get(group=self) except GroupSnooze.DoesNotExist: pass else: # XXX(dcramer): if the snooze row exists then we need # to confirm its still valid if snooze.until > timezone.now(): return GroupStatus.MUTED else: return GroupStatus.UNRESOLVED if self.status == GroupStatus.UNRESOLVED and self.is_over_resolve_age( ): return GroupStatus.RESOLVED return self.status def get_share_id(self): return b16encode( ('{}.{}'.format(self.project_id, self.id)).encode('utf-8')).lower().decode('utf-8') @classmethod def from_share_id(cls, share_id): if not share_id: raise cls.DoesNotExist try: project_id, group_id = b16decode( share_id.upper()).decode('utf-8').split('.') except (ValueError, TypeError): raise cls.DoesNotExist if not (project_id.isdigit() and group_id.isdigit()): raise cls.DoesNotExist return cls.objects.get(project=project_id, id=group_id) def get_score(self): return int( math.log(self.times_seen) * 600 + float(time.mktime(self.last_seen.timetuple()))) def get_latest_event(self): from sentry.models import Event if not hasattr(self, '_latest_event'): latest_events = sorted( Event.objects.filter( group_id=self.id, ).order_by('-datetime')[0:5], key=EVENT_ORDERING_KEY, reverse=True, ) try: self._latest_event = latest_events[0] except IndexError: self._latest_event = None return self._latest_event def get_oldest_event(self): from sentry.models import Event if not hasattr(self, '_oldest_event'): oldest_events = sorted( Event.objects.filter( group_id=self.id, ).order_by('datetime')[0:5], key=EVENT_ORDERING_KEY, ) try: self._oldest_event = oldest_events[0] except IndexError: self._oldest_event = None return self._oldest_event def get_unique_tags(self, tag, since=None, order_by='-times_seen'): # TODO(dcramer): this has zero test coverage and is a critical path from sentry.models import GroupTagValue queryset = GroupTagValue.objects.filter( group=self, key=tag, ) if since: queryset = queryset.filter(last_seen__gte=since) return queryset.values_list( 'value', 'times_seen', 'first_seen', 'last_seen', ).order_by(order_by) def get_tags(self, with_internal=True): from sentry.models import GroupTagKey, TagKey if not hasattr(self, '_tag_cache'): group_tags = GroupTagKey.objects.filter( group=self, project=self.project, ) if not with_internal: group_tags = group_tags.exclude(key__startswith='sentry:') group_tags = list(group_tags.values_list('key', flat=True)) tag_keys = dict( (t.key, t) for t in TagKey.objects.filter(project=self.project, key__in=group_tags)) results = [] for key in group_tags: try: tag_key = tag_keys[key] except KeyError: label = key.replace('_', ' ').title() else: label = tag_key.get_label() results.append({ 'key': key, 'label': label, }) self._tag_cache = sorted(results, key=lambda x: x['label']) return self._tag_cache def get_event_type(self): """ Return the type of this issue. See ``sentry.eventtypes``. """ return self.data.get('type', 'default') def get_event_metadata(self): """ Return the metadata of this issue. See ``sentry.eventtypes``. """ etype = self.data.get('type') if etype is None: etype = 'default' if 'metadata' not in self.data: data = self.data.copy() if self.data else {} data['message'] = self.message return eventtypes.get(etype)(data).get_metadata() return self.data['metadata'] @property def title(self): et = eventtypes.get(self.get_event_type())(self.data) return et.to_string(self.get_event_metadata()) def error(self): warnings.warn('Group.error is deprecated, use Group.title', DeprecationWarning) return self.title error.short_description = _('error') @property def message_short(self): warnings.warn('Group.message_short is deprecated, use Group.title', DeprecationWarning) return self.title def has_two_part_message(self): warnings.warn('Group.has_two_part_message is no longer used', DeprecationWarning) return False @property def organization(self): return self.project.organization @property def team(self): return self.project.team @property def checksum(self): warnings.warn('Group.checksum is no longer used', DeprecationWarning) return '' def get_email_subject(self): return '[%s] %s: %s' % (self.project.get_full_name().encode('utf-8'), six.text_type(self.get_level_display()).upper( ).encode('utf-8'), self.title.encode('utf-8'))
class Group(Model): """ Aggregated message which summarizes a set of Events. """ project = models.ForeignKey('sentry.Project', null=True) logger = models.CharField(max_length=64, blank=True, default='root', db_index=True) level = BoundedPositiveIntegerField(choices=LOG_LEVELS.items(), default=logging.ERROR, blank=True, db_index=True) message = models.TextField() culprit = models.CharField(max_length=MAX_CULPRIT_LENGTH, blank=True, null=True, db_column='view') checksum = models.CharField(max_length=32, db_index=True) num_comments = BoundedPositiveIntegerField(default=0, null=True) platform = models.CharField(max_length=64, null=True) status = BoundedPositiveIntegerField(default=0, choices=STATUS_LEVELS, db_index=True) times_seen = BoundedPositiveIntegerField(default=1, db_index=True) last_seen = models.DateTimeField(default=timezone.now, db_index=True) first_seen = models.DateTimeField(default=timezone.now, db_index=True) resolved_at = models.DateTimeField(null=True, db_index=True) # active_at should be the same as first_seen by default active_at = models.DateTimeField(null=True, db_index=True) time_spent_total = BoundedIntegerField(default=0) time_spent_count = BoundedIntegerField(default=0) score = BoundedIntegerField(default=0) is_public = models.NullBooleanField(default=False, null=True) data = GzippedDictField(blank=True, null=True) objects = GroupManager() class Meta: app_label = 'sentry' db_table = 'sentry_groupedmessage' unique_together = (('project', 'checksum'), ) verbose_name_plural = _('grouped messages') verbose_name = _('grouped message') permissions = (("can_view", "Can view"), ) __repr__ = sane_repr('project_id', 'checksum') def __unicode__(self): return "(%s) %s" % (self.times_seen, self.error()) def save(self, *args, **kwargs): if not self.last_seen: self.last_seen = timezone.now() if not self.first_seen: self.first_seen = self.last_seen if not self.active_at: self.active_at = self.first_seen if self.message: # We limit what we store for the message body self.message = self.message.splitlines()[0][:255] super(Group, self).save(*args, **kwargs) def get_absolute_url(self): return absolute_uri( reverse('sentry-group', args=[self.organization.slug, self.project.slug, self.id])) @property def avg_time_spent(self): if not self.time_spent_count: return return float(self.time_spent_total) / self.time_spent_count def natural_key(self): return (self.project, self.checksum) def is_over_resolve_age(self): resolve_age = self.project.get_option('sentry:resolve_age', None) if not resolve_age: return False return self.last_seen < timezone.now() - timedelta( hours=int(resolve_age)) def is_muted(self): return self.get_status() == STATUS_MUTED def is_resolved(self): return self.get_status() == STATUS_RESOLVED def get_status(self): if self.status == STATUS_UNRESOLVED and self.is_over_resolve_age(): return STATUS_RESOLVED return self.status def get_score(self): return int( math.log(self.times_seen) * 600 + float(time.mktime(self.last_seen.timetuple()))) def get_latest_event(self): from sentry.models import Event if not hasattr(self, '_latest_event'): try: self._latest_event = Event.objects.filter( group=self, ).order_by('-datetime')[0] except IndexError: self._latest_event = None return self._latest_event def get_unique_tags(self, tag, since=None, order_by='-times_seen'): # TODO(dcramer): this has zero test coverage and is a critical path from sentry.models import GroupTagValue queryset = GroupTagValue.objects.filter( group=self, key=tag, ) if since: queryset = queryset.filter(last_seen__gte=since) return queryset.values_list( 'value', 'times_seen', 'first_seen', 'last_seen', ).order_by(order_by) def get_tags(self, with_internal=True): from sentry.models import GroupTagKey if not hasattr(self, '_tag_cache'): self._tag_cache = sorted([ t for t in GroupTagKey.objects.filter( group=self, project=self.project, ).values_list('key', flat=True) if with_internal or not t.startswith('sentry:') ]) return self._tag_cache def error(self): return self.message error.short_description = _('error') def has_two_part_message(self): message = strip(self.message) return '\n' in message or len(message) > 100 @property def title(self): culprit = strip(self.culprit) if culprit: return culprit return self.message @property def message_short(self): message = strip(self.message) if not message: message = '<unlabeled message>' else: message = truncatechars(message.splitlines()[0], 100) return message @property def organization(self): return self.project.organization @property def team(self): return self.project.team def get_email_subject(self): return '[%s %s] %s: %s' % ( self.team.name.encode('utf-8'), self.project.name.encode('utf-8'), six.text_type(self.get_level_display()).upper().encode('utf-8'), self.message_short.encode('utf-8'))
class EventBase(Model): """ Abstract base class for both Event and Group. """ project = models.ForeignKey(Project, null=True) logger = models.CharField(max_length=64, blank=True, default='root', db_index=True) level = BoundedPositiveIntegerField(choices=LOG_LEVELS.items(), default=logging.ERROR, blank=True, db_index=True) message = models.TextField() culprit = models.CharField(max_length=MAX_CULPRIT_LENGTH, blank=True, null=True, db_column='view') checksum = models.CharField(max_length=32, db_index=True) data = GzippedDictField(blank=True, null=True) num_comments = BoundedPositiveIntegerField(default=0, null=True) platform = models.CharField(max_length=64, null=True) class Meta: abstract = True def save(self, *args, **kwargs): if len(self.logger) > 64: self.logger = self.logger[0:61] + u"..." super(EventBase, self).save(*args, **kwargs) def error(self): message = strip(self.message) if message: message = truncatechars(message, 100) else: message = '<unlabeled message>' return message error.short_description = _('error') def has_two_part_message(self): message = strip(self.message) return '\n' in message or len(message) > 100 def message_top(self): culprit = strip(self.culprit) if culprit: return culprit message = strip(self.message) if not strip(message): return '<unlabeled message>' return truncatechars(message.splitlines()[0], 100) @property def team(self): return self.project.team @property def user_ident(self): """ The identifier from a user is considered from several interfaces. In order: - User.id - User.email - User.username - Http.env.REMOTE_ADDR """ user_data = self.data.get('sentry.interfaces.User') if user_data: ident = user_data.get('id') if ident: return 'id:%s' % (ident, ) ident = user_data.get('email') if ident: return 'email:%s' % (ident, ) ident = user_data.get('username') if ident: return 'username:%s' % (ident, ) http_data = self.data.get('sentry.interfaces.Http') if http_data: if 'env' in http_data: ident = http_data['env'].get('REMOTE_ADDR') if ident: return 'ip:%s' % (ident, ) return None
from __future__ import annotations from collections import OrderedDict from typing import Any, Callable, Tuple from django import forms from sentry.constants import LOG_LEVELS, LOG_LEVELS_MAP from sentry.eventstore.models import Event from sentry.rules import LEVEL_MATCH_CHOICES as MATCH_CHOICES from sentry.rules import EventState, MatchType from sentry.rules.conditions.base import EventCondition key: Callable[[Tuple[int, str]], int] = lambda x: x[0] LEVEL_CHOICES = OrderedDict([ (f"{k}", v) for k, v in sorted(LOG_LEVELS.items(), key=key, reverse=True) ]) class LevelEventForm(forms.Form): # type: ignore level = forms.ChoiceField(choices=list(LEVEL_CHOICES.items())) match = forms.ChoiceField(choices=list(MATCH_CHOICES.items())) class LevelCondition(EventCondition): form_cls = LevelEventForm label = "The event's level is {match} {level}" form_fields = { "level": { "type": "choice", "choices": list(LEVEL_CHOICES.items())
class Group(Model): """ Aggregated message which summarizes a set of Events. """ __core__ = False project = FlexibleForeignKey('sentry.Project', null=True) logger = models.CharField(max_length=64, blank=True, default=DEFAULT_LOGGER_NAME, db_index=True) level = BoundedPositiveIntegerField(choices=LOG_LEVELS.items(), default=logging.ERROR, blank=True, db_index=True) message = models.TextField() culprit = models.CharField(max_length=MAX_CULPRIT_LENGTH, blank=True, null=True, db_column='view') num_comments = BoundedPositiveIntegerField(default=0, null=True) platform = models.CharField(max_length=64, null=True) status = BoundedPositiveIntegerField(default=0, choices=( (GroupStatus.UNRESOLVED, _('Unresolved')), (GroupStatus.RESOLVED, _('Resolved')), (GroupStatus.MUTED, _('Muted')), ), db_index=True) times_seen = BoundedPositiveIntegerField(default=1, db_index=True) last_seen = models.DateTimeField(default=timezone.now, db_index=True) first_seen = models.DateTimeField(default=timezone.now, db_index=True) first_release = FlexibleForeignKey('sentry.Release', null=True) resolved_at = models.DateTimeField(null=True, db_index=True) # active_at should be the same as first_seen by default active_at = models.DateTimeField(null=True, db_index=True) time_spent_total = BoundedIntegerField(default=0) time_spent_count = BoundedIntegerField(default=0) score = BoundedIntegerField(default=0) is_public = models.NullBooleanField(default=False, null=True) data = GzippedDictField(blank=True, null=True) objects = GroupManager() class Meta: app_label = 'sentry' db_table = 'sentry_groupedmessage' verbose_name_plural = _('grouped messages') verbose_name = _('grouped message') permissions = (("can_view", "Can view"), ) index_together = (('project', 'first_release'), ) __repr__ = sane_repr('project_id') def __unicode__(self): return "(%s) %s" % (self.times_seen, self.error()) def save(self, *args, **kwargs): if not self.last_seen: self.last_seen = timezone.now() if not self.first_seen: self.first_seen = self.last_seen if not self.active_at: self.active_at = self.first_seen if self.message: # We limit what we store for the message body self.message = self.message.splitlines()[0][:255] super(Group, self).save(*args, **kwargs) def get_absolute_url(self): return absolute_uri( reverse('sentry-group', args=[self.organization.slug, self.project.slug, self.id])) @property def avg_time_spent(self): if not self.time_spent_count: return return float(self.time_spent_total) / self.time_spent_count def is_over_resolve_age(self): resolve_age = self.project.get_option('sentry:resolve_age', None) if not resolve_age: return False return self.last_seen < timezone.now() - timedelta( hours=int(resolve_age)) def is_muted(self): return self.get_status() == GroupStatus.MUTED def is_resolved(self): return self.get_status() == GroupStatus.RESOLVED def get_status(self): if self.status == GroupStatus.UNRESOLVED and self.is_over_resolve_age( ): return GroupStatus.RESOLVED return self.status def get_share_id(self): return b16encode('{}.{}'.format(self.project_id, self.id)).lower() @classmethod def from_share_id(cls, share_id): try: project_id, group_id = b16decode(share_id.upper()).split('.') except ValueError: raise cls.DoesNotExist return cls.objects.get(project=project_id, id=group_id) def get_score(self): return int( math.log(self.times_seen) * 600 + float(time.mktime(self.last_seen.timetuple()))) def get_latest_event(self): from sentry.models import Event if not hasattr(self, '_latest_event'): try: self._latest_event = Event.objects.filter( group=self, ).order_by('-datetime')[0] except IndexError: self._latest_event = None return self._latest_event def get_unique_tags(self, tag, since=None, order_by='-times_seen'): # TODO(dcramer): this has zero test coverage and is a critical path from sentry.models import GroupTagValue queryset = GroupTagValue.objects.filter( group=self, key=tag, ) if since: queryset = queryset.filter(last_seen__gte=since) return queryset.values_list( 'value', 'times_seen', 'first_seen', 'last_seen', ).order_by(order_by) def get_tags(self, with_internal=True): from sentry.models import GroupTagKey, TagKey if not hasattr(self, '_tag_cache'): group_tags = GroupTagKey.objects.filter( group=self, project=self.project, ) if not with_internal: group_tags = group_tags.exclude(key__startswith='sentry:') group_tags = list(group_tags.values_list('key', flat=True)) tag_keys = dict( (t.key, t) for t in TagKey.objects.filter(project=self.project, key__in=group_tags)) results = [] for key in group_tags: try: tag_key = tag_keys[key] except KeyError: label = key.replace('_', ' ').title() else: label = tag_key.get_label() results.append({ 'key': key, 'label': label, }) self._tag_cache = sorted(results, key=lambda x: x['label']) return self._tag_cache def error(self): return self.message error.short_description = _('error') def has_two_part_message(self): message = strip(self.message) return '\n' in message or len(message) > 100 @property def title(self): culprit = strip(self.culprit) if culprit: return culprit return self.message @property def message_short(self): message = strip(self.message) if not message: message = '<unlabeled message>' else: message = truncatechars(message.splitlines()[0], 100) return message @property def organization(self): return self.project.organization @property def team(self): return self.project.team @property def checksum(self): warnings.warn('Group.checksum is no longer used', DeprecationWarning) return '' def get_email_subject(self): return '[%s] %s: %s' % ( self.project.get_full_name().encode('utf-8'), six.text_type(self.get_level_display()).upper().encode('utf-8'), self.message_short.encode('utf-8'))
:copyright: (c) 2010-2014 by the Sentry Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import absolute_import from collections import OrderedDict from django import forms from sentry.constants import LOG_LEVELS, LOG_LEVELS_MAP from sentry.rules.conditions.base import EventCondition LEVEL_CHOICES = OrderedDict( [(u"{0}".format(k), v) for k, v in sorted(LOG_LEVELS.items(), key=lambda x: x[0], reverse=True)] ) class MatchType(object): EQUAL = 'eq' LESS_OR_EQUAL = 'lte' GREATER_OR_EQUAL = 'gte' MATCH_CHOICES = OrderedDict( [ (MatchType.EQUAL, 'equal to'), (MatchType.LESS_OR_EQUAL, 'less than or equal to'), (MatchType.GREATER_OR_EQUAL, 'greater than or equal to') ] )
:copyright: (c) 2010-2014 by the Sentry Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import absolute_import from collections import OrderedDict from django import forms from sentry.constants import LOG_LEVELS, LOG_LEVELS_MAP from sentry.rules.conditions.base import EventCondition LEVEL_CHOICES = OrderedDict([ ("{0}".format(k), "{0}".format(v.capitalize())) for k, v in sorted(LOG_LEVELS.items(), key=lambda x: x[0], reverse=True) ]) class LevelMatchType(object): EQUAL = 'eq' LESS_OR_EQUAL = 'lte' GREATER_OR_EQUAL = 'gte' class LevelEventForm(forms.Form): level = forms.ChoiceField( choices=LEVEL_CHOICES.items(), initial=30) match = forms.ChoiceField( choices=(
class Event(Model): """ An individual event. """ group = models.ForeignKey('sentry.Group', blank=True, null=True, related_name="event_set") event_id = models.CharField(max_length=32, null=True, db_column="message_id") project = models.ForeignKey('sentry.Project', null=True) logger = models.CharField( max_length=64, blank=True, default='root', db_index=True) level = BoundedPositiveIntegerField( choices=LOG_LEVELS.items(), default=logging.ERROR, blank=True, db_index=True) message = models.TextField() culprit = models.CharField( max_length=MAX_CULPRIT_LENGTH, blank=True, null=True, db_column='view') checksum = models.CharField(max_length=32, db_index=True) num_comments = BoundedPositiveIntegerField(default=0, null=True) platform = models.CharField(max_length=64, null=True) datetime = models.DateTimeField(default=timezone.now, db_index=True) time_spent = BoundedIntegerField(null=True) server_name = models.CharField(max_length=128, db_index=True, null=True) site = models.CharField(max_length=128, db_index=True, null=True) data = NodeField(blank=True, null=True) objects = BaseManager() class Meta: app_label = 'sentry' db_table = 'sentry_message' verbose_name = _('message') verbose_name_plural = _('messages') unique_together = ('project', 'event_id') __repr__ = sane_repr('project_id', 'group_id', 'checksum') def error(self): message = strip(self.message) if not message: message = '<unlabeled message>' else: message = truncatechars(message.splitlines()[0], 100) return message error.short_description = _('error') def has_two_part_message(self): message = strip(self.message) return '\n' in message or len(message) > 100 def message_top(self): culprit = strip(self.culprit) if culprit: return culprit return self.error() @property def team(self): return self.project.team @memoize def ip_address(self): http_data = self.data.get('sentry.interfaces.Http') if http_data and 'env' in http_data: value = http_data['env'].get('REMOTE_ADDR') if value: return value user_data = self.data.get('sentry.interfaces.User') if user_data: value = user_data.get('ip_address') if value: return value return None @memoize def user_ident(self): """ The identifier from a user is considered from several interfaces. In order: - User.id - User.email - User.username - Http.env.REMOTE_ADDR """ user_data = self.data.get('sentry.interfaces.User') if user_data: ident = user_data.get('id') if ident: return 'id:%s' % (ident,) ident = user_data.get('email') if ident: return 'email:%s' % (ident,) ident = user_data.get('username') if ident: return 'username:%s' % (ident,) ident = self.ip_address if ident: return 'ip:%s' % (ident,) return None @memoize def interfaces(self): result = [] for key, data in self.data.iteritems(): if '.' not in key: continue try: cls = import_string(key) except ImportError: continue # suppress invalid interfaces value = safe_execute(cls, **data) if not value: continue result.append((key, value)) return SortedDict((k, v) for k, v in sorted(result, key=lambda x: x[1].get_score(), reverse=True)) def get_version(self): if not self.data: return if '__sentry__' not in self.data: return if 'version' not in self.data['__sentry__']: return module = self.data['__sentry__'].get('module', 'ver') return module, self.data['__sentry__']['version'] def get_tags(self): try: return [ (t, v) for t, v in self.data.get('tags') or () if not t.startswith('sentry:') ] except ValueError: # at one point Sentry allowed invalid tag sets such as (foo, bar) # vs ((tag, foo), (tag, bar)) return [] def as_dict(self): # We use a SortedDict to keep elements ordered for a potential JSON serializer data = SortedDict() data['id'] = self.event_id data['checksum'] = self.checksum data['project'] = self.project.slug data['logger'] = self.logger data['level'] = self.get_level_display() data['culprit'] = self.culprit data['datetime'] = self.datetime data['time_spent'] = self.time_spent for k, v in sorted(self.data.iteritems()): data[k] = v return data @property def size(self): return len(unicode(vars(self)))